Files
community-rule/prisma/seed/methodFacets.ts
T
2026-05-23 19:35:38 -06:00

116 lines
3.4 KiB
TypeScript

import { readFile } from "node:fs/promises";
import path from "node:path";
import type { PrismaClient } from "@prisma/client";
import {
FACET_GROUP_IDS,
FACET_VALUE_IDS_BY_GROUP,
SECTION_IDS,
type SectionId,
facetGroupsFileSchema,
resolveFacetMatch,
sectionFacetsSchema,
} from "../../lib/server/validation/methodFacetsSchemas";
import { methodFacetsJsonDir } from "./seedDataPaths";
/**
* Reads + Zod-validates `data/create/customRule/<section>.json`.
* Throws on schema failures so the seed aborts before any DB write.
*/
async function loadSectionFacets(section: SectionId) {
const file = path.join(methodFacetsJsonDir(), `${section}.json`);
const raw = await readFile(file, "utf8");
const parsed = JSON.parse(raw) as unknown;
const result = sectionFacetsSchema.safeParse(parsed);
if (!result.success) {
throw new Error(
`Invalid facet file ${file}: ${JSON.stringify(result.error.flatten(), null, 2)}`,
);
}
return result.data;
}
async function loadFacetGroups() {
const file = path.join(methodFacetsJsonDir(), "_facetGroups.json");
const raw = await readFile(file, "utf8");
const parsed = JSON.parse(raw) as unknown;
const result = facetGroupsFileSchema.safeParse(parsed);
if (!result.success) {
throw new Error(
`Invalid facet groups file ${file}: ${JSON.stringify(result.error.flatten(), null, 2)}`,
);
}
return result.data;
}
type MethodFacetRow = {
section: string;
slug: string;
group: string;
value: string;
matches: boolean;
weight: number | null;
};
/**
* Flattens `{ size: { oneMember: true, ... }, orgType: { ... } }` per slug
* into one row per `(section, slug, group, value)`. Omitted groups/values
* default to `false` so the table density is constant.
*/
function flattenSectionFacets(
section: SectionId,
facets: Awaited<ReturnType<typeof loadSectionFacets>>,
): MethodFacetRow[] {
const rows: MethodFacetRow[] = [];
for (const [slug, perMethod] of Object.entries(facets)) {
for (const group of FACET_GROUP_IDS) {
const groupValues = perMethod[group];
for (const value of FACET_VALUE_IDS_BY_GROUP[group]) {
const cell = groupValues?.[value as keyof typeof groupValues];
const { match, weight } = resolveFacetMatch(cell);
rows.push({
section,
slug,
group,
value,
matches: match,
weight,
});
}
}
}
return rows;
}
/**
* Validates and re-seeds the `MethodFacet` table from the JSON files.
* Per-section atomic swap so the table is never partially populated.
*
* `_facetGroups.json` is validated for schema correctness but not stored —
* its only runtime purpose is the chip-id ↔ canonical-id lookup, which is
* read directly from the JSON by the wizard ranker.
*/
export async function seedMethodFacets(prisma: PrismaClient): Promise<{
rowsBySection: Record<SectionId, number>;
}> {
await loadFacetGroups();
const rowsBySection: Record<SectionId, number> = {
communication: 0,
membership: 0,
decisionApproaches: 0,
conflictManagement: 0,
};
for (const section of SECTION_IDS) {
const facets = await loadSectionFacets(section);
const rows = flattenSectionFacets(section, facets);
rowsBySection[section] = rows.length;
await prisma.$transaction([
prisma.methodFacet.deleteMany({ where: { section } }),
prisma.methodFacet.createMany({ data: rows }),
]);
}
return { rowsBySection };
}