Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
911 changes: 317 additions & 594 deletions SPEC.md

Large diffs are not rendered by default.

36 changes: 33 additions & 3 deletions apps/demo/data/pipeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -222,9 +222,36 @@ const dcgWeight = pos => 1 / Math.log2(pos + 2);

const mscpBand = mscp => mscp >= 0.8 ? "HIGH" : mscp >= 0.5 ? "MEDIUM" : "LOW";

// Deterministic 0..255 token hash — mock-only stand-in for a residual codebook
// level. Never compared against real Phase 1 artifacts.
function hashToken(token) {
let hash = 0;
const str = String(token ?? "any");
for (let i = 0; i < str.length; i++) {
hash = (hash * 31 + str.charCodeAt(i)) >>> 0;
}
return hash & 0xff;
}

function buildSid(recipe) {
const cuisine = recipe.cuisine.toLowerCase();
const dish = recipe.dish;
const flavor = recipe.flavors?.[0] ?? "default";
return `SID::${recipe.cuisine.toLowerCase()}::${recipe.dish}::${flavor}::${recipe.id}`;
return {
sid_string: `SID::${cuisine}::${dish}::${flavor}::${recipe.id}`,
sid_path: [hashToken(cuisine), hashToken(dish), hashToken(flavor)],
};
}

function buildQuerySid(sketch) {
const pick = facet => (sketch.positive_facets?.[facet]?.[0] ?? "any");
const cuisine = pick("cuisine");
const dish = pick("dish_type");
const flavor = pick("flavor_profile");
return {
sid_string: `QSID::${cuisine}::${dish}::${flavor}`,
sid_path: [hashToken(cuisine), hashToken(dish), hashToken(flavor)],
};
}

window.computeConfidenceAndGround = function (candidates, rerankResult, topK = 3) {
Expand All @@ -251,6 +278,7 @@ window.computeConfidenceAndGround = function (candidates, rerankResult, topK = 3
const appearTopK = rerankResult.passes.filter(p => p.slice(0, topK).includes(entry.idx)).length;
const bootstrap = rerankResult.passes.filter(p => p.includes(entry.idx)).length;
const mscp = appearTopK / totalPasses;
const sid = buildSid(recipe);

return {
rank: rank + 1,
Expand All @@ -262,7 +290,8 @@ window.computeConfidenceAndGround = function (candidates, rerankResult, topK = 3
confidence_band: mscpBand(mscp),
bootstrap_support: bootstrap,
mapping_mode: "direct (id_map.jsonl)",
sid: buildSid(recipe),
sid_string: sid.sid_string,
sid_path: sid.sid_path,
};
})
.filter(Boolean);
Expand Down Expand Up @@ -296,7 +325,8 @@ window.runPipeline = function ({ query, liked, disliked, hardFilters, topK = 3 }
const rerank = window.zeroShotRerank(search.top30, sketch, { passes: 5 });
const t3 = performance.now();

const conf = window.computeConfidenceAndGround(search.top30, rerank, topK);
const confResult = window.computeConfidenceAndGround(search.top30, rerank, topK);
const conf = { ...confResult, query_sid: buildQuerySid(sketch) };
const t4 = performance.now();

return {
Expand Down
6 changes: 4 additions & 2 deletions apps/demo/src/components.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ function FinalDelivery({ L, conf }) {
</div>
<div className="final-bottom">
<span>{L.final.bootstrap(item.bootstrap_support)}</span>
<span className="final-sid">{item.sid}</span>
<span className="final-sid">{item.sid_string}</span>
</div>
</div>
</div>
Expand Down Expand Up @@ -454,7 +454,8 @@ function JsonTab({ L, result, topK }) {
},
items: result.conf.items.map(i => ({
recipe_id: i.recipe.id,
sid_string: i.sid,
sid_string: i.sid_string,
sid_path: i.sid_path,
rank: i.rank,
title: i.recipe.title,
rationale: i.rationale,
Expand All @@ -466,6 +467,7 @@ function JsonTab({ L, result, topK }) {
popularity: i.recipe.popularity,
cooccurrence_with_history: i.recipe.cooccurrence,
})),
query_sid: result.conf.query_sid,
rerank_summary: L.json.rerankSummary(topK),
confidence_summary: result.conf.items.every(i => i.confidence_band === "HIGH") ? L.json.confAllHigh : L.json.confMixed,
selected_candidate_indices: result.conf.items.map(i => i.idx),
Expand Down
33 changes: 31 additions & 2 deletions apps/demo/tests/pipeline.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ test("buildSid via runPipeline: recipe with empty flavors falls back to 'default
query: "fresh light lunch", liked: [113], disliked: [], hardFilters: {}, topK: 3,
});
for (const it of res.conf.items) {
assert.ok(!it.sid.includes("undefined"), `SID has undefined: ${it.sid}`);
assert.ok(!it.sid_string.includes("undefined"), `SID has undefined: ${it.sid_string}`);
}
} finally {
target.flavors = saved;
Expand Down Expand Up @@ -160,7 +160,12 @@ test("computeConfidenceAndGround: SID matches `SID::cuisine::dish::flavor::id` f
const rr = W.zeroShotRerank(search.top30, sk);
const conf = W.computeConfidenceAndGround(search.top30, rr, 3);
for (const it of conf.items) {
assert.match(it.sid, /^SID::[a-z-]+::[a-z-]+::[a-z-]+::\d+$/);
assert.match(it.sid_string, /^SID::[a-z-]+::[a-z-]+::[a-z-]+::\d+$/);
assert.ok(Array.isArray(it.sid_path) && it.sid_path.length === 3);
for (const level of it.sid_path) {
assert.ok(Number.isInteger(level) && level >= 0 && level <= 255,
`sid_path level out of range: ${level}`);
}
}
});

Expand Down Expand Up @@ -212,3 +217,27 @@ test("runPipeline: returns sketch/search/rerank/conf/timings; timings include si
assert.ok(result.timings[k] > 0);
}
});

test("runPipeline: conf.query_sid exposes sid_string and sid_path with integer levels", () => {
const result = W.runPipeline({
query: "I want hearty but not too heavy comfort food for a weeknight. Vegetarian.",
liked: [110, 111], disliked: [], hardFilters: { dietary_style: ["vegetarian"] }, topK: 3,
});
const qs = result.conf.query_sid;
assert.ok(qs && typeof qs === "object", "conf.query_sid must be an object");
assert.match(qs.sid_string, /^QSID::/);
assert.ok(Array.isArray(qs.sid_path) && qs.sid_path.length === 3);
for (const level of qs.sid_path) {
assert.ok(Number.isInteger(level) && level >= 0 && level <= 255,
`query_sid level out of range: ${level}`);
}
});

test("runPipeline: buildQuerySid falls back to 'any' when a sketch facet is missing", () => {
// Empty query and no liked items → no positive_facets beyond defaults.
const result = W.runPipeline({
query: "", liked: [], disliked: [], hardFilters: {}, topK: 3,
});
assert.ok(result.conf.query_sid.sid_string.includes("any"),
`expected 'any' fallback, got ${result.conf.query_sid.sid_string}`);
});
2 changes: 1 addition & 1 deletion graphify-out/BUILD_INFO.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"graphify_version": "0.4.23",
"mode": "code_update",
"command": "uvx --from graphifyy==0.4.23 graphify update .",
"generated_at": "2026-04-21T10:06:10.142990+00:00"
"generated_at": "2026-04-23T03:18:21.870587+00:00"
}
Loading
Loading