Files
2026-04-20 16:45:15 -06:00

67 lines
2.3 KiB
TypeScript

import { methodSlugFromTitle } from "../create/methodSlugFromTitle";
import type { SectionId } from "./validation/methodFacetsSchemas";
/**
* Extracts the `(section, slug)` pairs that a curated `RuleTemplate.body`
* composes. Used by `/api/templates` to score templates by facet match
* (CR-88, §9.1).
*
* `body.sections[].categoryName` is mapped to the canonical recommendation
* `section` id; `entries[].title` is slugified the same way the messages
* ingest produced `methods[].id` (kebab-case, ASCII-folded, lowercase) so
* the slugs line up with `MethodFacet.slug`.
*
* "Values" entries are intentionally skipped — values are out of scope for
* the facet matrix (§11).
*/
const CATEGORY_NAME_TO_SECTION: Record<string, SectionId> = {
Communication: "communication",
Membership: "membership",
"Decision-making": "decisionApproaches",
"Conflict management": "conflictManagement",
};
export { methodSlugFromTitle };
type RuleTemplateBodySection = {
categoryName?: unknown;
entries?: unknown;
};
type RuleTemplateBody = { sections?: unknown };
export type TemplateMethodRef = { section: SectionId; slug: string };
export function templateMethodsFromBody(
body: unknown,
): TemplateMethodRef[] {
if (!body || typeof body !== "object") return [];
const sections = (body as RuleTemplateBody).sections;
if (!Array.isArray(sections)) return [];
const out: TemplateMethodRef[] = [];
const seen = new Set<string>();
for (const raw of sections) {
if (!raw || typeof raw !== "object") continue;
const sec = raw as RuleTemplateBodySection;
const categoryName =
typeof sec.categoryName === "string" ? sec.categoryName : null;
if (!categoryName) continue;
const section = CATEGORY_NAME_TO_SECTION[categoryName];
if (!section) continue; // Values, or any future category we don't score.
if (!Array.isArray(sec.entries)) continue;
for (const entry of sec.entries) {
if (!entry || typeof entry !== "object") continue;
const title = (entry as { title?: unknown }).title;
if (typeof title !== "string" || title.trim() === "") continue;
const slug = methodSlugFromTitle(title);
if (!slug) continue;
const key = `${section}:${slug}`;
if (seen.has(key)) continue;
seen.add(key);
out.push({ section, slug });
}
}
return out;
}