Template recommendation implemented
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "TemplateFacet" (
|
||||
"id" TEXT NOT NULL,
|
||||
"templateSlug" TEXT NOT NULL,
|
||||
"group" TEXT NOT NULL,
|
||||
"value" TEXT NOT NULL,
|
||||
"matches" BOOLEAN NOT NULL,
|
||||
|
||||
CONSTRAINT "TemplateFacet_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "TemplateFacet_templateSlug_group_value_key" ON "TemplateFacet"("templateSlug", "group", "value");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "TemplateFacet_templateSlug_idx" ON "TemplateFacet"("templateSlug");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "TemplateFacet_group_value_matches_idx" ON "TemplateFacet"("group", "value", "matches");
|
||||
@@ -111,3 +111,24 @@ model MethodFacet {
|
||||
@@index([section])
|
||||
@@index([group, value, matches])
|
||||
}
|
||||
|
||||
/// Template-level recommendation matrix (Template Composition, cols G–Y). Canonical
|
||||
/// JSON in `data/templates/templateFacet.json`; rebuilt at seed like
|
||||
/// `MethodFacet`. One row per `(templateSlug, group, value)` where the matrix
|
||||
/// marks a fit (✓). `GET /api/templates?facet.*` joins these rows to user facets.
|
||||
/// See `docs/guides/template-recommendation-matrix.md` (parallel to `MethodFacet` §7).
|
||||
model TemplateFacet {
|
||||
id String @id @default(cuid())
|
||||
/// `RuleTemplate.slug` (e.g. `consensus`, `do-ocracy`).
|
||||
templateSlug String
|
||||
/// `size` | `orgType` | `scale` | `maturity` — same as `MethodFacet.group`.
|
||||
group String
|
||||
/// Canonical facet value id, e.g. `workersCoop`, `local`.
|
||||
value String
|
||||
/// `true` iff the JSON marks a fit; seed only writes `true` rows.
|
||||
matches Boolean
|
||||
|
||||
@@unique([templateSlug, group, value])
|
||||
@@index([templateSlug])
|
||||
@@index([group, value, matches])
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { PrismaClient, type Prisma } from "@prisma/client";
|
||||
import { seedMethodFacets } from "./seed/methodFacets";
|
||||
import { seedTemplateFacets } from "./seed/templateFacets";
|
||||
|
||||
/**
|
||||
* Curated rule templates for GET /api/templates.
|
||||
@@ -393,6 +394,12 @@ async function main() {
|
||||
.map(([section, count]) => `${section}=${count}`)
|
||||
.join(", ")}`,
|
||||
);
|
||||
|
||||
const templateFacetSeed = await seedTemplateFacets(prisma);
|
||||
// eslint-disable-next-line no-console -- seed CLI feedback
|
||||
console.log(
|
||||
`Seeded TemplateFacet rows: ${templateFacetSeed.rowCount}`,
|
||||
);
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import type { PrismaClient } from "@prisma/client";
|
||||
import { FACET_GROUP_IDS } from "../../lib/server/validation/methodFacetsSchemas";
|
||||
import { templateFacetFileSchema } from "../../lib/server/validation/templateFacetSchema";
|
||||
|
||||
const REPO_ROOT = path.resolve(__dirname, "..", "..");
|
||||
const TEMPLATE_FACET_FILE = path.join(
|
||||
REPO_ROOT,
|
||||
"data",
|
||||
"templates",
|
||||
"templateFacet.json",
|
||||
);
|
||||
|
||||
type TemplateFacetRow = {
|
||||
templateSlug: string;
|
||||
group: string;
|
||||
value: string;
|
||||
matches: boolean;
|
||||
};
|
||||
|
||||
async function loadTemplateFacets() {
|
||||
const raw = await readFile(TEMPLATE_FACET_FILE, "utf8");
|
||||
const parsed = JSON.parse(raw) as unknown;
|
||||
const result = templateFacetFileSchema.safeParse(parsed);
|
||||
if (!result.success) {
|
||||
throw new Error(
|
||||
`Invalid template facet file ${TEMPLATE_FACET_FILE}: ${JSON.stringify(
|
||||
result.error.flatten(),
|
||||
null,
|
||||
2,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
return result.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* One row per `(templateSlug, group, value)` where the matrix lists a fit (✓).
|
||||
* Sparse: omitted cells are not stored (unlike `MethodFacet`, which materializes
|
||||
* all cells for constant table density).
|
||||
*/
|
||||
function flattenTemplateFacets(
|
||||
data: Awaited<ReturnType<typeof loadTemplateFacets>>,
|
||||
): TemplateFacetRow[] {
|
||||
const rows: TemplateFacetRow[] = [];
|
||||
for (const [templateSlug, row] of Object.entries(data)) {
|
||||
for (const group of FACET_GROUP_IDS) {
|
||||
for (const value of row[group]) {
|
||||
rows.push({ templateSlug, group, value, matches: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and re-seeds the `TemplateFacet` table from
|
||||
* `data/templates/templateFacet.json` (Template Composition-2, cols G–Y).
|
||||
*/
|
||||
export async function seedTemplateFacets(
|
||||
prisma: PrismaClient,
|
||||
): Promise<{ rowCount: number }> {
|
||||
const data = await loadTemplateFacets();
|
||||
const rows = flattenTemplateFacets(data);
|
||||
await prisma.$transaction([
|
||||
prisma.templateFacet.deleteMany(),
|
||||
prisma.templateFacet.createMany({ data: rows }),
|
||||
]);
|
||||
return { rowCount: rows.length };
|
||||
}
|
||||
Reference in New Issue
Block a user