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
@@ -9,6 +9,12 @@ import {
|
||||
loadFacetScores,
|
||||
} from "../../../../lib/create/facetRecommendationsLoad";
|
||||
import { useCreateFlow } from "../context/CreateFlowContext";
|
||||
import { groupAnswersFromCreateFlowState } from "../../../../lib/create/groupAnswers";
|
||||
import {
|
||||
deckData,
|
||||
recommendDeck,
|
||||
} from "../../../../lib/create/deckRecommend";
|
||||
import type { GroupAnswers } from "../../../../lib/create/deckTypes";
|
||||
|
||||
/**
|
||||
* Card-deck section ids served by `/api/create-flow/methods` (CR-88 §9.2).
|
||||
@@ -125,63 +131,46 @@ export function useFacetRecommendations(
|
||||
}
|
||||
|
||||
/**
|
||||
* Stable comparator for re-ranking a messages-file `methods[]` array. Higher
|
||||
* `scoresBySlug[id]` first; ties fall back to authoring index, so a
|
||||
* zero-facet user sees the original ordering verbatim.
|
||||
* One card per job, then the display rule for the top `limit` cards.
|
||||
* Cards marked every-rule carry no Recommended tag. With no facets, the
|
||||
* first `limit` cards in deck order show and nothing is tagged.
|
||||
*/
|
||||
export function rankMethodsByScore<T extends { id: string }>(
|
||||
methods: readonly T[],
|
||||
scoresBySlug: Record<string, number>,
|
||||
): T[] {
|
||||
const indexById = new Map<string, number>();
|
||||
methods.forEach((m, i) => indexById.set(m.id, i));
|
||||
return [...methods].sort((a, b) => {
|
||||
const sa = scoresBySlug[a.id] ?? 0;
|
||||
const sb = scoresBySlug[b.id] ?? 0;
|
||||
if (sa !== sb) return sb - sa;
|
||||
return (indexById.get(a.id) ?? 0) - (indexById.get(b.id) ?? 0);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Picks (a) which method ids fill the compact card stack and (b) which of
|
||||
* those should render with the "Recommended" tag. The messages JSON no
|
||||
* longer carries a static `recommended` flag — both selections come
|
||||
* entirely from facet scores (CR-88 §10).
|
||||
*
|
||||
* Behavior:
|
||||
* - Facets selected & at least one method scored > 0 →
|
||||
* `compactCardIds` = up to `limit` top-scored methods (1..limit cards;
|
||||
* never padded with unrecommended fillers). All shown cards get the
|
||||
* "Recommended" badge.
|
||||
* - No facets selected, or every method scored 0 → `compactCardIds` =
|
||||
* first `limit` in ranked/authoring order, `recommendedIds` empty (no
|
||||
* badges shown — honest "no signal yet" fallback).
|
||||
*
|
||||
* `CardStack.view` is responsible for laying out variable-length compact
|
||||
* arrays gracefully (uses `.map`/`.slice` and length-guarded indexing).
|
||||
*/
|
||||
export function deriveCompactCards<T extends { id: string }>(
|
||||
rankedMethods: readonly T[],
|
||||
scoresBySlug: Record<string, number>,
|
||||
hasAnyFacets: boolean,
|
||||
export function deriveCompactCards(
|
||||
section: RecommendationSection,
|
||||
answers: GroupAnswers,
|
||||
limit: number,
|
||||
): { compactCardIds: string[]; recommendedIds: Set<string> } {
|
||||
const fallback = () => ({
|
||||
compactCardIds: rankedMethods.slice(0, limit).map((m) => m.id),
|
||||
recommendedIds: new Set<string>(),
|
||||
});
|
||||
|
||||
if (!hasAnyFacets) return fallback();
|
||||
|
||||
const matched = rankedMethods.filter(
|
||||
(m) => (scoresBySlug[m.id] ?? 0) > 0,
|
||||
);
|
||||
if (matched.length === 0) return fallback();
|
||||
|
||||
const top = matched.slice(0, limit);
|
||||
): {
|
||||
compactCardIds: string[];
|
||||
recommendedIds: Set<string>;
|
||||
orderedIds: string[];
|
||||
} {
|
||||
const recommendation = recommendDeck(deckData(section), answers, { limit });
|
||||
const orderedIds = recommendation.fallback
|
||||
? recommendation.seeAll
|
||||
: [
|
||||
...recommendation.shown.map((card) => card.id),
|
||||
...recommendation.seeAll,
|
||||
];
|
||||
if (recommendation.fallback) {
|
||||
return {
|
||||
compactCardIds: orderedIds.slice(0, limit),
|
||||
recommendedIds: new Set<string>(),
|
||||
orderedIds,
|
||||
};
|
||||
}
|
||||
return {
|
||||
compactCardIds: top.map((m) => m.id),
|
||||
recommendedIds: new Set(top.map((m) => m.id)),
|
||||
compactCardIds: recommendation.shown.map((card) => card.id),
|
||||
recommendedIds: new Set(
|
||||
recommendation.recommended
|
||||
.filter((card) => card.tag === "recommended")
|
||||
.map((card) => card.id),
|
||||
),
|
||||
orderedIds,
|
||||
};
|
||||
}
|
||||
|
||||
export function groupAnswersForRecommendations(
|
||||
state: Parameters<typeof groupAnswersFromCreateFlowState>[0],
|
||||
): GroupAnswers {
|
||||
return groupAnswersFromCreateFlowState(state);
|
||||
}
|
||||
Reference in new issue
Block a user