spb/tools-spboucher-ai
Public
1import type { ToolRecord } from "./content/schema";2import { WORD } from "./content/schema";34/** Deterministic editorial quality score 0–100 (completeness × evidence × freshness). Documented in docs/editorial-guide.md. */5export function qualityScore(r: Pick<ToolRecord, "description" | "capabilities" | "workflows" | "advantages" | "limitations" | "alternatives" | "sources" | "confidence" | "license_spdx" | "pricing_url" | "docs_url" | "repo_url" | "comparison_notes" | "tags" | "use_cases" | "source_model" | "pricing_model">, linkState: string = "unchecked"): number {6 let s = 0;7 const w = WORD(r.description);8 s += Math.min(25, Math.round((w / 200) * 25)); // description depth (≈200 words = full marks)9 s += Math.min(10, r.capabilities.length * 2); // capabilities10 s += Math.min(5, r.limitations.length * 2); // honesty: limitations11 s += Math.min(5, r.workflows.length + r.advantages.length); // workflows/advantages12 s += Math.min(6, r.alternatives.length * 2); // graph connectivity13 s += r.comparison_notes ? 4 : 0;14 s += Math.min(5, r.tags.length + r.use_cases.length); // discoverability15 s += Math.min(15, r.sources.length * 5); // evidence16 s += r.docs_url ? 3 : 0; s += r.repo_url || r.source_model === "proprietary" ? 2 : 0;17 s += r.license_spdx || r.source_model === "proprietary" ? 3 : 0;18 s += r.pricing_url || r.pricing_model.every((p) => ["free", "open-source"].includes(p)) ? 3 : 0;19 s += r.confidence === "high" ? 8 : r.confidence === "medium" ? 4 : 0;20 s += linkState === "ok" ? 6 : linkState === "redirected" ? 3 : linkState === "unchecked" ? 2 : 0;21 return Math.max(0, Math.min(100, s));22}23