export type Industry = | 'ecommerce' | 'banking' | 'fintech' | 'healthcare' | 'pharma' | 'agriculture' | 'legacy-enterprise' | 'saas' | 'unknown'; export interface DomainProfile { industry: Industry; confidence: number; importantScreenPatterns: string[]; workflowGoals: string[]; piiRules: string[]; riskyActions: string[]; safeProbeRules: string[]; requiredArtifacts: string[]; evidence: string[]; } const COMMON_REQUIRED_ARTIFACTS = [ 'screenshot', 'dom', 'markdown', 'accessibility', 'network', 'performance', ]; const PROFILES: Record> = { ecommerce: { industry: 'ecommerce', importantScreenPatterns: ['catalog', 'product', 'search', 'filter', 'cart', 'checkout', 'payment', 'order', 'returns'], workflowGoals: ['discover product catalogue', 'map cart and checkout', 'capture order confirmation and failure states', 'map search/filter/sort'], piiRules: ['mask email', 'mask phone', 'mask address', 'mask card-like numbers'], riskyActions: ['place order', 'submit payment', 'save card', 'apply real coupon'], safeProbeRules: ['do not click final payment/order buttons', 'use invalid test card data only', 'stop before irreversible purchase'], requiredArtifacts: COMMON_REQUIRED_ARTIFACTS, }, banking: { industry: 'banking', importantScreenPatterns: ['login', 'mfa', 'dashboard', 'accounts', 'transactions', 'transfer', 'beneficiary', 'statement', 'dispute'], workflowGoals: ['map account overview', 'map transactions and statements', 'map transfer setup without submitting', 'map MFA/session timeout states'], piiRules: ['mask account numbers', 'mask balances', 'mask names', 'mask addresses', 'mask tax identifiers'], riskyActions: ['transfer money', 'add beneficiary', 'submit dispute', 'download sensitive statement'], safeProbeRules: ['read-only by default', 'never submit transfer confirmation', 'never alter beneficiary data'], requiredArtifacts: COMMON_REQUIRED_ARTIFACTS, }, fintech: { industry: 'fintech', importantScreenPatterns: ['kyc', 'wallet', 'transactions', 'cards', 'payments', 'payouts', 'risk', 'compliance', 'disputes'], workflowGoals: ['map KYC onboarding', 'map wallet/payment states', 'map failed payment and dispute flows', 'capture compliance screens'], piiRules: ['mask card numbers', 'mask bank accounts', 'mask identity documents', 'mask balances'], riskyActions: ['send payment', 'withdraw funds', 'change card status', 'submit KYC with real data'], safeProbeRules: ['sandbox data only', 'stop before money movement', 'never upload real identity documents'], requiredArtifacts: COMMON_REQUIRED_ARTIFACTS, }, healthcare: { industry: 'healthcare', importantScreenPatterns: ['patient', 'appointment', 'claims', 'provider', 'prescription', 'records', 'lab', 'consent'], workflowGoals: ['map patient lookup safely', 'map appointment booking before final submit', 'map claims and prescription screens', 'capture consent and records UX'], piiRules: ['mask PHI', 'mask patient names', 'mask MRN', 'mask DOB', 'mask diagnosis', 'mask prescriptions'], riskyActions: ['schedule real appointment', 'submit claim', 'change medication', 'message provider with PHI'], safeProbeRules: ['use synthetic patients only', 'do not submit clinical changes', 'avoid external notifications'], requiredArtifacts: COMMON_REQUIRED_ARTIFACTS, }, pharma: { industry: 'pharma', importantScreenPatterns: ['product', 'dosage', 'safety', 'batch', 'adverse-event', 'regulatory', 'trial', 'label'], workflowGoals: ['map product and safety content', 'map adverse event intake before submit', 'capture regulatory and batch workflows'], piiRules: ['mask patient reports', 'mask reporter contact', 'mask trial participant identifiers'], riskyActions: ['submit adverse event', 'change safety record', 'publish regulatory content'], safeProbeRules: ['stop before regulatory or safety submission', 'use synthetic reports only'], requiredArtifacts: COMMON_REQUIRED_ARTIFACTS, }, agriculture: { industry: 'agriculture', importantScreenPatterns: ['crop', 'field', 'weather', 'inventory', 'marketplace', 'logistics', 'sensor', 'subsidy'], workflowGoals: ['map crop and field planning', 'map inventory/marketplace flows', 'capture logistics and sensor dashboards'], piiRules: ['mask farmer identity', 'mask location precision', 'mask subsidy identifiers'], riskyActions: ['place market order', 'submit subsidy application', 'change sensor/device control'], safeProbeRules: ['avoid irreversible orders', 'avoid device actuation', 'use test farm data'], requiredArtifacts: COMMON_REQUIRED_ARTIFACTS, }, 'legacy-enterprise': { industry: 'legacy-enterprise', importantScreenPatterns: ['frameset', 'iframe', 'popup', 'table', 'search', 'admin', 'report', 'export'], workflowGoals: ['map menu trees', 'capture iframe and popup states', 'map table filters and reports', 'detect session timeout'], piiRules: ['mask table cells that look like PII', 'mask exported identifiers'], riskyActions: ['bulk update', 'delete record', 'export production data', 'submit admin change'], safeProbeRules: ['read-only table probing', 'never click delete/bulk actions', 'capture popups without committing'], requiredArtifacts: COMMON_REQUIRED_ARTIFACTS, }, saas: { industry: 'saas', importantScreenPatterns: ['dashboard', 'settings', 'users', 'billing', 'integrations', 'reports', 'admin', 'workspace'], workflowGoals: ['map workspace navigation', 'map settings and integrations', 'map role/user management without changing state', 'capture dashboards and reports'], piiRules: ['mask emails', 'mask tokens', 'mask API keys', 'mask billing data'], riskyActions: ['delete workspace', 'invite real user', 'rotate key', 'change billing plan'], safeProbeRules: ['never submit destructive settings', 'redact secrets immediately', 'use sandbox users'], requiredArtifacts: COMMON_REQUIRED_ARTIFACTS, }, unknown: { industry: 'unknown', importantScreenPatterns: ['login', 'dashboard', 'settings', 'search', 'detail', 'report', 'form'], workflowGoals: ['map top-level navigation', 'capture forms and validation states', 'discover hidden menus', 'classify domain after first pages'], piiRules: ['mask emails', 'mask phone numbers', 'mask long identifiers', 'mask card-like numbers'], riskyActions: ['delete', 'submit payment', 'transfer', 'publish', 'send notification'], safeProbeRules: ['read-only by default', 'stop before irreversible actions', 'use synthetic data only'], requiredArtifacts: COMMON_REQUIRED_ARTIFACTS, }, }; const BASE_INDUSTRY_HINTS: Array<{ industry: Industry; terms: RegExp[] }> = [ { industry: 'ecommerce', terms: [/cart/i, /checkout/i, /product/i, /catalog/i, /sku/i, /wishlist/i, /order/i] }, { industry: 'banking', terms: [/account/i, /statement/i, /beneficiar/i, /transfer/i, /iban/i, /routing/i, /balance/i] }, { industry: 'fintech', terms: [/wallet/i, /kyc/i, /payout/i, /card/i, /payment/i, /risk/i, /dispute/i] }, { industry: 'healthcare', terms: [/patient/i, /provider/i, /appointment/i, /claim/i, /prescription/i, /medical/i, /lab result/i] }, { industry: 'pharma', terms: [/pharma/i, /dosage/i, /adverse/i, /batch/i, /regulatory/i, /clinical trial/i, /label/i] }, { industry: 'agriculture', terms: [/crop/i, /field/i, /farm/i, /harvest/i, /sensor/i, /weather/i, /subsidy/i] }, { industry: 'legacy-enterprise', terms: [/frameset/i, /iframe/i, /popup/i, /legacy/i, /report export/i, /mainframe/i] }, { industry: 'saas', terms: [/workspace/i, /tenant/i, /integration/i, /billing/i, /api key/i, /admin/i, /dashboard/i] }, ]; // Allow additional industry hint keywords via ENV without replacing the baseline. // Format: JSON array of { industry: string; terms: string[] } — terms are treated as case-insensitive regex. // Example: EXTRA_INDUSTRY_HINTS_JSON='[{"industry":"saas","terms":["platform","multitenant"]}]' const _extraHints: Array<{ industry: Industry; terms: RegExp[] }> = (() => { const raw = process.env.EXTRA_INDUSTRY_HINTS_JSON; if (!raw) return []; try { const parsed: Array<{ industry: string; terms: string[] }> = JSON.parse(raw); return parsed.map(h => ({ industry: h.industry as Industry, terms: h.terms.map(t => new RegExp(t, 'i')) })); } catch { return []; } })(); const INDUSTRY_HINTS = [...BASE_INDUSTRY_HINTS, ..._extraHints]; // Allow patching profile fields (riskyActions, piiRules, etc.) per industry via ENV. // Format: JSON object keyed by industry, value is partial DomainProfile fields to merge. // Example: DOMAIN_PROFILE_OVERRIDES_JSON='{"saas":{"riskyActions":["delete workspace","export all data"]}}' const _profileOverrides: Partial>>> = (() => { const raw = process.env.DOMAIN_PROFILE_OVERRIDES_JSON; if (!raw) return {}; try { return JSON.parse(raw); } catch { return {}; } })(); export function classifyDomainProfile(input: { appUrl: string; projectName?: string | null; appType?: string | null; seedText?: string; }): DomainProfile { const haystack = [input.appUrl, input.projectName ?? '', input.appType ?? '', input.seedText ?? ''].join(' '); const scores = new Map(); for (const { industry, terms } of INDUSTRY_HINTS) { for (const term of terms) { if (term.test(haystack)) { const prev = scores.get(industry) ?? { score: 0, evidence: [] }; prev.score += 1; prev.evidence.push(term.source); scores.set(industry, prev); } } } let selected: Industry = 'unknown'; let best = 0; let evidence: string[] = []; for (const [industry, result] of scores) { if (result.score > best) { selected = industry; best = result.score; evidence = result.evidence; } } const base = PROFILES[selected]; return { ...base, confidence: best === 0 ? 0.25 : Math.min(0.95, 0.35 + best * 0.15), evidence, }; } export function getProfile(industry: Industry): DomainProfile { const base = PROFILES[industry] ?? PROFILES.unknown; return { ...base, confidence: 1, evidence: ['explicit'] }; }