fix: deduplicate related notes and normalize scores per section
- Deduplicate MemPalace results by source (keep highest score) - Filter cross-section duplicates (vault removes notes already in MemPalace) - Add per-section max-normalization so both sections display on 0–100% scale Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -33427,7 +33427,14 @@ var RelatedNotesView = class extends import_obsidian4.ItemView {
|
|||||||
useMempalace ? this.queryMempalace(mpQuery, topK, file.basename) : Promise.resolve([]),
|
useMempalace ? this.queryMempalace(mpQuery, topK, file.basename) : Promise.resolve([]),
|
||||||
embedReady ? es.searchSimilarToFile(file) : Promise.resolve([])
|
embedReady ? es.searchSimilarToFile(file) : Promise.resolve([])
|
||||||
]);
|
]);
|
||||||
this.render(mpResults, nativeResults, file.basename, embedReady ? modelShort : null);
|
const mpNames = new Set(mpResults.map((r) => r.source));
|
||||||
|
const dedupedNative = nativeResults.filter((r) => !mpNames.has(r.file.basename));
|
||||||
|
this.render(
|
||||||
|
this.normalizeScores(mpResults),
|
||||||
|
this.normalizeScores(dedupedNative),
|
||||||
|
file.basename,
|
||||||
|
embedReady ? modelShort : null
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// ─── MemPalace ────────────────────────────────────────────────────────────
|
// ─── MemPalace ────────────────────────────────────────────────────────────
|
||||||
/** Build a semantic query from the note: title + stripped body (first ~300 chars). */
|
/** Build a semantic query from the note: title + stripped body (first ~300 chars). */
|
||||||
@@ -33492,7 +33499,24 @@ var RelatedNotesView = class extends import_obsidian4.ItemView {
|
|||||||
const excerpt = afterScore.replace(/\n{3,}/g, "\n\n").trim().slice(0, 240);
|
const excerpt = afterScore.replace(/\n{3,}/g, "\n\n").trim().slice(0, 240);
|
||||||
results.push({ source, location, score, excerpt });
|
results.push({ source, location, score, excerpt });
|
||||||
}
|
}
|
||||||
return results;
|
const seen = /* @__PURE__ */ new Map();
|
||||||
|
for (const r of results) {
|
||||||
|
const existing = seen.get(r.source);
|
||||||
|
if (!existing || r.score > existing.score)
|
||||||
|
seen.set(r.source, r);
|
||||||
|
}
|
||||||
|
return [...seen.values()].sort((a, b) => b.score - a.score);
|
||||||
|
}
|
||||||
|
// ─── Score normalization ──────────────────────────────────────────────────
|
||||||
|
/** Max-normalize scores within a result set so the best item = 1.0.
|
||||||
|
* Preserves relative proportions; each section independently scaled. */
|
||||||
|
normalizeScores(items) {
|
||||||
|
if (items.length === 0)
|
||||||
|
return items;
|
||||||
|
const max2 = Math.max(...items.map((i) => i.score));
|
||||||
|
if (max2 === 0)
|
||||||
|
return items;
|
||||||
|
return items.map((i) => ({ ...i, score: i.score / max2 }));
|
||||||
}
|
}
|
||||||
// ─── Rendering ────────────────────────────────────────────────────────────
|
// ─── Rendering ────────────────────────────────────────────────────────────
|
||||||
renderStatus(msg) {
|
renderStatus(msg) {
|
||||||
|
|||||||
+28
-2
@@ -64,7 +64,15 @@ export class RelatedNotesView extends ItemView {
|
|||||||
embedReady ? es!.searchSimilarToFile(file) : Promise.resolve([]),
|
embedReady ? es!.searchSimilarToFile(file) : Promise.resolve([]),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
this.render(mpResults, nativeResults, file.basename, embedReady ? modelShort : null);
|
const mpNames = new Set(mpResults.map((r) => r.source));
|
||||||
|
const dedupedNative = nativeResults.filter((r) => !mpNames.has(r.file.basename));
|
||||||
|
|
||||||
|
this.render(
|
||||||
|
this.normalizeScores(mpResults),
|
||||||
|
this.normalizeScores(dedupedNative),
|
||||||
|
file.basename,
|
||||||
|
embedReady ? modelShort : null,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── MemPalace ────────────────────────────────────────────────────────────
|
// ─── MemPalace ────────────────────────────────────────────────────────────
|
||||||
@@ -136,7 +144,25 @@ export class RelatedNotesView extends ItemView {
|
|||||||
|
|
||||||
results.push({ source, location, score, excerpt });
|
results.push({ source, location, score, excerpt });
|
||||||
}
|
}
|
||||||
return results;
|
|
||||||
|
// Deduplicate by source, keeping highest-scoring entry
|
||||||
|
const seen = new Map<string, MpResult>();
|
||||||
|
for (const r of results) {
|
||||||
|
const existing = seen.get(r.source);
|
||||||
|
if (!existing || r.score > existing.score) seen.set(r.source, r);
|
||||||
|
}
|
||||||
|
return [...seen.values()].sort((a, b) => b.score - a.score);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Score normalization ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Max-normalize scores within a result set so the best item = 1.0.
|
||||||
|
* Preserves relative proportions; each section independently scaled. */
|
||||||
|
private normalizeScores<T extends { score: number }>(items: T[]): T[] {
|
||||||
|
if (items.length === 0) return items;
|
||||||
|
const max = Math.max(...items.map((i) => i.score));
|
||||||
|
if (max === 0) return items;
|
||||||
|
return items.map((i) => ({ ...i, score: i.score / max }));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Rendering ────────────────────────────────────────────────────────────
|
// ─── Rendering ────────────────────────────────────────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user