fix(recipe-generation): BRAND.name-interpolation för creatorDisplayName + rubriker/filnamn
feat(recommendation-engine,api): S4 Smak/Hälsa/Lager-vyer för 'Vad ska vi äta?' - Ersätter hårdkodat 'Cibello' i scale-batch, scale-smoke-test och export-verified. - DB-backfill: 224 recept hade 'Cibello AI' i creator_display_name (värdena motsvarar nuvarande BRAND.name, ingen rad ändrades men kontrollen är gjord). - Lägger till view-query-param (default|taste|health|pantry) med fördefinierade ScoringWeights och samtyckesgrind. - Unit-tester för vyer; integrationstester för vy-param, validering och fallback utan personalization-samtycke. - brand-guard grön; pnpm typecheck 19/19; pnpm test --force x2 grönt (34 tasks, 275 tester).
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { createDatabase, closeDatabase } from "@app/database";
|
||||
import { SEED_INGREDIENTS } from "@app/database/seed";
|
||||
import { BRAND } from "@app/shared-types";
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
|
||||
@@ -58,17 +59,19 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
const coverageMatrix = mainIds.map((id) => {
|
||||
const ing = SEED_INGREDIENTS.find((i) => i.id === id);
|
||||
const cell = matrix.get(id) ?? new Map<string, number>();
|
||||
return {
|
||||
ingredientId: id,
|
||||
nameSv: ing?.nameSv ?? id,
|
||||
category: ing?.category ?? "unknown",
|
||||
total: Array.from(cell.values()).reduce((s, v) => s + v, 0),
|
||||
byDiet: Object.fromEntries(cell),
|
||||
};
|
||||
}).sort((a, b) => a.total - b.total);
|
||||
const coverageMatrix = mainIds
|
||||
.map((id) => {
|
||||
const ing = SEED_INGREDIENTS.find((i) => i.id === id);
|
||||
const cell = matrix.get(id) ?? new Map<string, number>();
|
||||
return {
|
||||
ingredientId: id,
|
||||
nameSv: ing?.nameSv ?? id,
|
||||
category: ing?.category ?? "unknown",
|
||||
total: Array.from(cell.values()).reduce((s, v) => s + v, 0),
|
||||
byDiet: Object.fromEntries(cell),
|
||||
};
|
||||
})
|
||||
.sort((a, b) => a.total - b.total);
|
||||
|
||||
const thinCells = coverageMatrix.filter((c) => c.total <= 2);
|
||||
|
||||
@@ -76,7 +79,11 @@ async function main() {
|
||||
const slugs = recipes.map((r) => r.slug);
|
||||
const uniqueSlugs = new Set(slugs);
|
||||
const titleWords = recipes.map((r) =>
|
||||
r.titleSv.toLowerCase().replace(/[^\w\s]/g, "").split(/\s+/).filter((w) => w.length > 3),
|
||||
r.titleSv
|
||||
.toLowerCase()
|
||||
.replace(/[^\w\s]/g, "")
|
||||
.split(/\s+/)
|
||||
.filter((w) => w.length > 3),
|
||||
);
|
||||
const titleSignatures = titleWords.map((words) => words.slice(0, 4).sort().join(" "));
|
||||
const sigCounts = new Map<string, number>();
|
||||
@@ -117,26 +124,30 @@ async function main() {
|
||||
};
|
||||
|
||||
await fs.writeFile(
|
||||
path.join(publicDir, "cibello-verified-final-result.json"),
|
||||
JSON.stringify({ exportedAt: new Date().toISOString(), count: enriched.length, recipes: enriched }, null, 2),
|
||||
path.join(publicDir, `${BRAND.slug}-verified-final-result.json`),
|
||||
JSON.stringify(
|
||||
{ exportedAt: new Date().toISOString(), count: enriched.length, recipes: enriched },
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
await fs.writeFile(
|
||||
path.join(publicDir, "cibello-verified-final-coverage-matrix.json"),
|
||||
path.join(publicDir, `${BRAND.slug}-verified-final-coverage-matrix.json`),
|
||||
JSON.stringify({ exportedAt: new Date().toISOString(), coverageMatrix }, null, 2),
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
await fs.writeFile(
|
||||
path.join(publicDir, "cibello-verified-final-dedup-stats.json"),
|
||||
path.join(publicDir, `${BRAND.slug}-verified-final-dedup-stats.json`),
|
||||
JSON.stringify({ exportedAt: new Date().toISOString(), ...summary.dedupStats }, null, 2),
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
await fs.writeFile(
|
||||
path.join(publicDir, "cibello-verified-final-summary.md"),
|
||||
`# Cibello Receptkatalog – Final Verified Set
|
||||
path.join(publicDir, `${BRAND.slug}-verified-final-summary.md`),
|
||||
`# ${BRAND.name} Receptkatalog – Final Verified Set
|
||||
|
||||
- **Exporterad:** ${summary.exportedAt}
|
||||
- **Totalt godkända recept (verified + editorial):** ${summary.totalVerified}
|
||||
@@ -154,7 +165,10 @@ async function main() {
|
||||
|
||||
| Huvudingrediens | Kategori | Totalt | Fördelning |
|
||||
|---|---|---:|---|
|
||||
${thinCells.slice(0, 15).map((c) => `| ${c.nameSv} | ${c.category} | ${c.total} | ${JSON.stringify(c.byDiet)} |`).join("\n")}
|
||||
${thinCells
|
||||
.slice(0, 15)
|
||||
.map((c) => `| ${c.nameSv} | ${c.category} | ${c.total} | ${JSON.stringify(c.byDiet)} |`)
|
||||
.join("\n")}
|
||||
|
||||
## Granskning
|
||||
|
||||
@@ -164,12 +178,23 @@ Setet är redo för allergen- + närings-sanity av granskare.
|
||||
);
|
||||
|
||||
console.error(`[export-verified] Exported ${recipes.length} verified recipes`);
|
||||
console.error(` result.json -> ${path.join(publicDir, "cibello-verified-final-result.json")}`);
|
||||
console.error(` coverage-matrix -> ${path.join(publicDir, "cibello-verified-final-coverage-matrix.json")}`);
|
||||
console.error(` dedup-stats -> ${path.join(publicDir, "cibello-verified-final-dedup-stats.json")}`);
|
||||
console.error(` summary.md -> ${path.join(publicDir, "cibello-verified-final-summary.md")}`);
|
||||
console.error(
|
||||
` result.json -> ${path.join(publicDir, `${BRAND.slug}-verified-final-result.json`)}`,
|
||||
);
|
||||
console.error(
|
||||
` coverage-matrix -> ${path.join(publicDir, `${BRAND.slug}-verified-final-coverage-matrix.json`)}`,
|
||||
);
|
||||
console.error(
|
||||
` dedup-stats -> ${path.join(publicDir, `${BRAND.slug}-verified-final-dedup-stats.json`)}`,
|
||||
);
|
||||
console.error(
|
||||
` summary.md -> ${path.join(publicDir, `${BRAND.slug}-verified-final-summary.md`)}`,
|
||||
);
|
||||
|
||||
await closeDatabase();
|
||||
}
|
||||
|
||||
main().catch((err) => { console.error(err); process.exit(1); });
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user