/*
RFC-1077: Canonical fact interface and normalization utility for cross-surface fact parity validation. Shared between werkstatt-shared (contract) and werkstatt-site (extraction logic).
- Do not import from werkstatt-site — this package must not depend on the site plugin.
- Do not implement fact extraction — that lives in werkstatt-site where PbpResolvedGraph is available.
- RFC-1077: initial implementation — CanonicalFact interface and normalizeFactValue.
*/
/** RFC-1077: A canonical fact extracted from the PBP resolved graph or a rendered surface. */
export interface CanonicalFact {
/** Fact type: price, address, email, foundingYear, representative, phone, name. */
type: string;
/** Entity ID the fact belongs to (e.g. offering ID, business ID, place ID). */
entityId: string;
/** Entity type: business, offering, place, contactPoint. */
entityType: string;
/** The canonical value as a string (normalized for comparison). */
value: string;
/** The surface where this fact was found: html, llms-full, jsonld, canonical. */
surface: string;
/** Source file path or URL where the fact was found. */
source: string;
}
/** RFC-1077: Normalize a fact value for comparison across surfaces. */
export function normalizeFactValue(type: string, value: string): string {
const trimmed = value.trim();
if (type === "email") return trimmed.toLowerCase();
if (type === "phone") return trimmed.replace(/^tel:/, "").replace(/[\s\-()]/g, "");
if (type === "price") {
let stripped = trimmed.replace(/\s*(EUR|USD|GBP|CHF|UAH|€|\$|£|₴)\s*$/i, "");
// Handle German thousands separator: "2.000" → "2000", "1.040" → "1040"
stripped = stripped.replace(/(\d)\.(\d{3})(?!\d)/g, "$1$2");
// Handle space thousands separator: "2 000" → "2000"
stripped = stripped.replace(/(\d)\s(\d{3})(?!\d)/g, "$1$2");
const num = parseFloat(stripped);
if (!isNaN(num)) return String(num);
return stripped;
}
return trimmed;
}