Import the remade decks on stable ids and recommend one card per job, so a saved draft still loads after a chip is renamed.
The real-group profiles stay as a test, so a change that moves a pick fails. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
96ca15d8e3
commit
bcbc6f30d9
89 files changed
+36555
-2828
No files matched your search
@@ -1,3 +1,5 @@
|
||||
import { groupAnswersFromRequestedFacets } from "../create/groupAnswers";
|
||||
import { deckData, recommendDeck } from "../create/deckRecommend";
|
||||
import { prisma } from "./db";
|
||||
import { isDatabaseConfigured } from "./env";
|
||||
import {
|
||||
@@ -27,57 +29,40 @@ export type ListMethodRecommendationsResult = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the per-method match scores for `section`, given `facets`.
|
||||
* Returns `null` so callers can fall back to messages-file order when DB
|
||||
* is unavailable or the query fails.
|
||||
*
|
||||
* Notes:
|
||||
* - Empty facets ⇒ `rankedSlugs: []`, `matchesBySlug: {}` (caller falls back
|
||||
* to authoring order).
|
||||
* - Sort is `score` desc only — re-stabilising into authoring order is the
|
||||
* caller's job (the wizard already iterates the on-disk `methods[]` array).
|
||||
* Ranks one deck with the job rule. Empty facets return no scores so callers
|
||||
* keep deck order. The first maturity value is the only one that scores.
|
||||
* Scores decrease along the engine's display order so a score sort matches it.
|
||||
*/
|
||||
export async function listMethodRecommendations(args: {
|
||||
section: SectionId;
|
||||
facets: RequestedFacets;
|
||||
}): Promise<ListMethodRecommendationsResult | null> {
|
||||
if (!isDatabaseConfigured()) return null;
|
||||
|
||||
const requested = flattenRequestedFacets(args.facets);
|
||||
if (requested.length === 0) {
|
||||
return { rankedSlugs: [], matchesBySlug: {} };
|
||||
}
|
||||
|
||||
try {
|
||||
const rows = await prisma.methodFacet.findMany({
|
||||
where: {
|
||||
section: args.section,
|
||||
matches: true,
|
||||
OR: requested.map(({ group, value }) => ({ group, value })),
|
||||
},
|
||||
select: { slug: true, group: true, value: true },
|
||||
});
|
||||
|
||||
const matchesBySlug: Record<string, MethodRanking["matches"]> = {};
|
||||
for (const row of rows) {
|
||||
const key = `${row.group}:${row.value}`;
|
||||
const entry =
|
||||
matchesBySlug[row.slug] ??
|
||||
(matchesBySlug[row.slug] = { score: 0, matchedFacets: [] });
|
||||
if (!entry.matchedFacets.includes(key)) {
|
||||
entry.matchedFacets.push(key);
|
||||
entry.score += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const rankedSlugs = Object.entries(matchesBySlug)
|
||||
.sort(([, a], [, b]) => b.score - a.score)
|
||||
.map(([slug]) => slug);
|
||||
|
||||
return { rankedSlugs, matchesBySlug };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
const answers = groupAnswersFromRequestedFacets(args.facets);
|
||||
const recommendation = recommendDeck(deckData(args.section), answers);
|
||||
const orderedIds = recommendation.fallback
|
||||
? recommendation.seeAll
|
||||
: [
|
||||
...recommendation.shown.map((card) => card.id),
|
||||
...recommendation.seeAll,
|
||||
];
|
||||
const matchesBySlug: Record<string, MethodRanking["matches"]> = {};
|
||||
orderedIds.forEach((id, index) => {
|
||||
const card = recommendation.recommended.find((row) => row.id === id);
|
||||
const matched =
|
||||
card?.reason.kind === "fit" || card?.reason.kind === "opened"
|
||||
? card.reason.matched
|
||||
: [];
|
||||
matchesBySlug[id] = {
|
||||
score: orderedIds.length - index,
|
||||
matchedFacets: matched,
|
||||
};
|
||||
});
|
||||
return { rankedSlugs: orderedIds, matchesBySlug };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user