// Central mock data + the live-estimate pricing model shared by the order flow. // No real backend yet — the configurator's price/timeline/confidence come from // computeEstimate(); replace with a call to the FastAPI /estimates later. export type ProvLevel = 0 | 1 | 2 | 3; // 0 licens/ej · 1 en källa · 2 delvis · 3 flera källor export interface Market { code: string; name: string; live: boolean; coverage: number; // % } // 20 European markets (Nordics live first, rest "kommande"). export const MARKETS: Market[] = [ { code: "SE", name: "Sverige", live: true, coverage: 98 }, { code: "NO", name: "Norge", live: true, coverage: 96 }, { code: "DK", name: "Danmark", live: true, coverage: 94 }, { code: "FI", name: "Finland", live: true, coverage: 89 }, { code: "IS", name: "Island", live: false, coverage: 0 }, { code: "NL", name: "Nederländerna", live: false, coverage: 0 }, { code: "DE", name: "Tyskland", live: false, coverage: 0 }, { code: "BE", name: "Belgien", live: false, coverage: 0 }, { code: "IE", name: "Irland", live: false, coverage: 0 }, { code: "FR", name: "Frankrike", live: false, coverage: 0 }, { code: "GB", name: "Storbritannien", live: false, coverage: 0 }, { code: "AT", name: "Österrike", live: false, coverage: 0 }, { code: "CH", name: "Schweiz", live: false, coverage: 0 }, { code: "IT", name: "Italien", live: false, coverage: 0 }, { code: "ES", name: "Spanien", live: false, coverage: 0 }, { code: "PT", name: "Portugal", live: false, coverage: 0 }, { code: "PL", name: "Polen", live: false, coverage: 0 }, { code: "CZ", name: "Tjeckien", live: false, coverage: 0 }, { code: "HU", name: "Ungern", live: false, coverage: 0 }, { code: "LU", name: "Luxemburg", live: false, coverage: 0 }, ]; export type EntityType = "Bolag" | "Beslutsfattare" | "Fordon"; export const ENTITY_TYPES: { id: EntityType; mult: number }[] = [ { id: "Bolag", mult: 1.0 }, { id: "Beslutsfattare", mult: 1.4 }, { id: "Fordon", mult: 1.2 }, ]; export interface Field { id: string; name: string; sub: string; level: ProvLevel; source: string; freshness: string; weight: number; locked?: boolean; } // Each field carries its honest provenance: source · freshness · confidence level. export const FIELDS: Field[] = [ { id: "identity", name: "Identitet & säte", sub: "org.nr, namn, adress", level: 3, source: "GLEIF · Brreg", freshness: "daglig", weight: 1.0, locked: true }, { id: "lei", name: "LEI / cross-border-nyckel", sub: "GLEIF golden copy", level: 3, source: "GLEIF", freshness: "daglig", weight: 0.8 }, { id: "financials", name: "Finansiellt", sub: "omsättning, resultat, nyckeltal", level: 2, source: "register", freshness: "årlig", weight: 1.6 }, { id: "ubo", name: "Verklig huvudman (UBO)", sub: "ägarkedja", level: 2, source: "register", freshness: "vid ändring", weight: 1.4 }, { id: "contact", name: "Kontakt: e-post & telefon", sub: "licens krävs i DE/FR", level: 1, source: "publik webb", freshness: "veckovis", weight: 1.8 }, { id: "intent", name: "Köpsignaler & intent", sub: "jobbannonser, nyheter, tech", level: 1, source: "webb", freshness: "veckovis", weight: 1.5 }, ]; export interface Enrichment { id: string; label: string; weight: number; } export const ENRICHMENTS: Enrichment[] = [ { id: "dedup", label: "Dedup mot min fil", weight: 0.1 }, { id: "crm", label: "Matcha & berika min CRM-export", weight: 0.2 }, { id: "sanctions", label: "Sanktioner / PEP-flagg", weight: 0.15 }, { id: "nace", label: "Branschklassning (NACE/SNI)", weight: 0.1 }, { id: "tech", label: "Webbplats & teknikstack", weight: 0.15 }, ]; export type Frequency = "Engångs" | "Veckovis" | "Dagligt flöde"; export const FREQUENCIES: Frequency[] = ["Engångs", "Veckovis", "Dagligt flöde"]; export const DELIVERY_FORMATS = [ "CSV", "Tabellvy (NocoDB)", "Parquet", "JSON", "Dashboard (Metabase)", "API / webhook", "Push → FiveCRM", ]; const FIELD_RATE = 240; // kr per field-weight per 1000 rows per market export interface EstimateInput { markets: string[]; type: EntityType; fieldIds: string[]; enrichmentIds: string[]; volume: number; frequency: Frequency; confidence: number; // target 80–99 } export interface Estimate { price: number; deposit: number; remainder: number; timelineDays: number; estConfidence: number; recurring: boolean; } export function computeEstimate(input: EstimateInput): Estimate { const markets = Math.max(1, input.markets.length); const fields = FIELDS.filter((f) => input.fieldIds.includes(f.id) || f.locked); const fieldWeight = fields.reduce((s, f) => s + f.weight, 0) || 1; const entityMult = ENTITY_TYPES.find((e) => e.id === input.type)?.mult ?? 1; const confidenceMult = 1 + Math.max(0, input.confidence - 90) * 0.03; const enrichWeight = ENRICHMENTS.filter((e) => input.enrichmentIds.includes(e.id)).reduce((s, e) => s + e.weight, 0); const base = fieldWeight * markets * FIELD_RATE * (input.volume / 1000) * entityMult * confidenceMult; const price = Math.round((base * (1 + enrichWeight)) / 5) * 5; // round to 5 kr const deposit = Math.round(price / 2); const timelineDays = Math.min( 14, Math.max(1, Math.round(1 + markets * 0.4 + input.volume / 6000 + fields.length * 0.25)), ); // Honest estimated confidence: bounded by the provenance of the chosen fields. const avgLevel = fields.reduce((s, f) => s + f.level, 0) / fields.length; // 0..3 const fieldCeiling = Math.round(82 + avgLevel * 5); // ~82..97 const estConfidence = Math.min(input.confidence, fieldCeiling); return { price, deposit, remainder: price - deposit, timelineDays, estConfidence, recurring: input.frequency !== "Engångs", }; } export function formatSEK(n: number): string { return new Intl.NumberFormat("sv-SE").format(Math.round(n)) + " kr"; } // Sample Nordic rows reused by the validation table. export interface SampleRow { orgnr: string; bolag: string; sate: string; sni: string; anstallda: number; oms: number; // tkr epost: string; epostLevel: ProvLevel; lei: string; } export const SAMPLE_ROWS: SampleRow[] = [ { orgnr: "556016-0680", bolag: "Volvo Group", sate: "Göteborg", sni: "29100", anstallda: 95000, oms: 553000000, epost: "info@volvo.com", epostLevel: 2, lei: "5493000XYZ..." }, { orgnr: "556036-0793", bolag: "Spotify AB", sate: "Stockholm", sni: "63110", anstallda: 9800, oms: 132000000, epost: "press@spotify.com", epostLevel: 2, lei: "549300ABC..." }, { orgnr: "915933149", bolag: "Equinor ASA", sate: "Stavanger", sni: "06100", anstallda: 22000, oms: 980000000, epost: "kontakt@equinor.no", epostLevel: 1, lei: "549300NOR..." }, { orgnr: "25052943", bolag: "Maersk A/S", sate: "København", sni: "50200", anstallda: 100000, oms: 510000000, epost: "info@maersk.dk", epostLevel: 1, lei: "549300DNK..." }, { orgnr: "0112038-9", bolag: "Nokia Oyj", sate: "Espoo", sni: "26300", anstallda: 86000, oms: 221000000, epost: "", epostLevel: 0, lei: "549300FIN..." }, ];