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
@@ -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 },
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user