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:
adilalloandCursor committed 2026-11-04 14:22:54 -07:00
1 parent 96ca15d8e3
commit bcbc6f30d9
89 files changed
+36555 -2828

No files matched your search

+160
View File
@@ -0,0 +1,160 @@
import { readFileSync } from "node:fs";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { DECK_IDS } from "../../lib/create/deckTypes";
import type { DeckId, RecommendedCard } from "../../lib/create/deckTypes";
import { deckData, recommendDeck } from "../../lib/create/deckRecommend";
import { variantsOf } from "../../lib/create/reviewProfiles";
import type { ReviewProfile } from "../../lib/create/reviewProfiles";
import { methodSlugFromTitle } from "../../lib/create/methodSlugFromTitle";
import { resolveMethodId } from "../../lib/create/idAliases";
import useCasesCompletedRules from "../../messages/en/pages/useCasesCompletedRules.json";
const ROOT = path.resolve(__dirname, "..", "..");
function readJson<T>(rel: string): T {
return JSON.parse(readFileSync(path.join(ROOT, rel), "utf8")) as T;
}
const profiles = readJson<ReviewProfile[]>("data/create/profiles/real-group-profiles.json");
const founders = readJson<ReviewProfile[]>("data/create/profiles/one-member-founders.json");
function compact(card: RecommendedCard) {
const reason =
card.reason.kind === "added"
? `added:${card.reason.answer}`
: card.reason.kind === "opened"
? `opened:${card.reason.answer}`
: card.reason.answer
? `fit+${card.reason.answer}`
: "fit";
return {
id: card.id,
tag: card.tag,
tier: card.tier,
reason,
...(card.decidedBy ? { decidedBy: card.decidedBy } : {}),
...(card.alternative ? { alternative: card.alternative } : {}),
};
}
function picksFor(set: ReviewProfile[]) {
return set.flatMap(variantsOf).map((variant) => ({
profile: variant.profile,
variant: variant.variant,
decks: Object.fromEntries(
DECK_IDS.map((deck) => {
const result = recommendDeck(deckData(deck), variant.answers);
return [
deck,
{
shown: result.shown.map(compact),
waiting: result.waiting.map((card) => card.id),
},
];
}),
),
}));
}
function rowOrderSentence(set: ReviewProfile[]): string {
const base = set.flatMap(variantsOf).filter((variant) => variant.variant === "base");
const totals = { only: 0, score: 0, otherKinds: 0, rowOrder: 0 };
for (const deck of DECK_IDS) {
const jobs = deckData(deck).engine.jobs.filter((job) => job.displayTier !== "none");
for (const job of jobs) {
for (const variant of base) {
const winner = recommendDeck(deckData(deck), variant.answers).recommended.find(
(card) => card.job === job.id && card.tier !== "answers",
);
if (winner?.decidedBy) totals[winner.decidedBy] += 1;
}
}
}
const picks = totals.only + totals.score + totals.otherKinds + totals.rowOrder;
return `Of ${picks} picks for the 92 groups, row order settled ${totals.rowOrder}, other kinds ${totals.otherKinds} and fit ${totals.score}, and ${totals.only} had one eligible card.`;
}
const EXAMPLE_DECK_BY_CATEGORY: Record<string, DeckId> = {
Membership: "membership",
"Decision-Making": "decisionApproaches",
"Decision-making": "decisionApproaches",
"Conflict Management": "conflictManagement",
Communication: "communication",
};
function chosenCards(documentKey: string): { total: number; ids: string[] } {
const doc = (
useCasesCompletedRules as Record<
string,
{ document: { sections: { categoryName: string; entries: { title: string }[] }[] } }
>
)[documentKey];
const live = new Map(
DECK_IDS.map((deck) => [deck, new Set(Object.keys(deckData(deck).engine.cards))]),
);
let total = 0;
const ids: string[] = [];
for (const section of doc.document.sections) {
const deck = EXAMPLE_DECK_BY_CATEGORY[section.categoryName];
if (!deck) continue;
for (const entry of section.entries) {
total += 1;
const slug = methodSlugFromTitle(entry.title);
const resolved = resolveMethodId(deck, slug);
if (resolved && live.get(resolved.deck)?.has(resolved.id)) ids.push(resolved.id);
}
}
return { total, ids };
}
describe("real-group profiles", () => {
it("matches the reviewed 2 Nov package run", () => {
const expected = readJson<{ picks: unknown[] }>("tests/fixtures/profiles-picks.json");
expect(picksFor([...profiles, ...founders])).toEqual(expected.picks);
});
it("reports how often row order settled a pick", () => {
const summary = readFileSync(
path.join(ROOT, "tests/fixtures/profiles-summary.md"),
"utf8",
);
expect(summary).toContain(rowOrderSentence(profiles));
expect(profiles).toHaveLength(92);
expect(founders.length).toBeGreaterThan(0);
expect(JSON.stringify(profiles)).not.toMatch(/[A-Z][a-z]+ [A-Z][a-z]+/);
expect(JSON.stringify(founders)).not.toMatch(/[A-Z][a-z]+ [A-Z][a-z]+/);
});
it("counts how many example-rule cards the engine recommends", () => {
const base = profiles.flatMap(variantsOf).filter((variant) => variant.variant === "base");
const report: Record<string, { chosen: number; resolved: number; recommendedOnMost: number }> = {};
for (const key of ["mutualAidColorado", "foodNotBombs", "boulderCountyStreetMedics"] as const) {
const chosen = chosenCards(key);
let best = 0;
for (const variant of base) {
const recommended = new Set<string>();
for (const deck of DECK_IDS) {
for (const card of recommendDeck(deckData(deck), variant.answers).recommended) {
recommended.add(card.id);
}
}
let hit = 0;
for (const id of chosen.ids) if (recommended.has(id)) hit += 1;
if (hit > best) best = hit;
}
report[key] = {
chosen: chosen.total,
resolved: chosen.ids.length,
recommendedOnMost: best,
};
}
// The team's own Rule is not in the repo, so this locks the three example
// Rules. A change that moves a recommendation changes recommendedOnMost.
expect(report).toEqual({
mutualAidColorado: { chosen: 7, resolved: 6, recommendedOnMost: 4 },
foodNotBombs: { chosen: 8, resolved: 8, recommendedOnMost: 5 },
boulderCountyStreetMedics: { chosen: 9, resolved: 8, recommendedOnMost: 5 },
});
});
});