A scoring model that lives as scattered contact.score += 10 calls across twelve event handlers cannot be audited, cannot be versioned, and cannot be changed without grepping the entire codebase for every increment site. iso-25010:2011 maintainability.analysability formalizes the problem: the system is not analyzable without exhaustive code review. In practice, this means sales teams receive qualified leads whose score no one can explain, scoring bugs are introduced by developers who miss a call site, and there is no record of what the model was when a particular lead was scored — critical when a lead disputes their classification or when an audit demands a scoring rationale.
High because an undocumented scoring model cannot be audited, versioned, or safely changed — scoring bugs propagate silently and lead qualification decisions lack traceable rationale.
Create a single scoring config module at src/lib/scoring/lead-score-model.ts (or equivalent). Every score-modification call site must reference it — no magic numbers in event handlers.
// src/lib/scoring/lead-score-model.ts
export const SCORE_MODEL_VERSION = '1.2'
export const SCORE_EVENTS = {
email_opened: { points: 1, reason: 'MPP unreliable — low weight' },
email_clicked: { points: 5, reason: 'Reliable intent signal' },
email_replied: { points: 10, reason: 'Highest engagement signal' },
page_visit: { points: 2, reason: 'Tracked via UTM' },
pricing_page_visit: { points: 8, reason: 'High-intent page' },
demo_booked: { points: 20, reason: 'Explicit intent' },
email_unsubscribed: { points: -50, reason: 'Hard negative — suppress' },
} as const satisfies Record<string, { points: number; reason: string }>
Bump SCORE_MODEL_VERSION every time a point value or event type changes. Audit that at least 90% of score-modification call sites import from this module rather than hardcoding values.
ID: campaign-orchestration-sequencing.lead-scoring.model-documented
Severity: high
What to look for: Check whether the lead scoring model is explicitly defined in code or documentation. A documented model means the score factors and weights are readable and reviewable — not buried in ad-hoc increment calls scattered across event handlers. Look for: a centralized scoring configuration (an object or constants file that defines which behaviors add/subtract points and by how much), inline code comments explaining scoring rationale, and a version or changelog for the model. Red flags: score increments scattered across 10+ files with no central definition, magic numbers with no explanatory context.
Pass criteria: Lead scoring rules are defined in one place (a config object or documented module). The model is readable and its logic is understandable without tracing through multiple files. Version or changelog is present. Quote the actual scoring config object or constants found. Count all score-modification call sites and verify at least 90% reference the central model.
Fail criteria: Score changes are scattered across the codebase with no central model definition. Points are added/subtracted via magic numbers with no explanation. The current state of the scoring model cannot be determined without reading every event handler.
Skip (N/A) when: The project does not implement lead scoring.
Detail on fail: "Score increments scattered across 12 event handler files — no central scoring config" or "No scoring model documentation or versioning — cannot determine total possible score or what behaviors are tracked"
Remediation: Centralize the scoring model:
// lib/scoring/lead-score-model.ts
export const SCORE_MODEL_VERSION = '1.2'
export const SCORE_EVENTS = {
// Engagement
email_opened: { points: 1, reason: 'MPP unreliable, low weight' },
email_clicked: { points: 5, reason: 'Reliable intent signal' },
email_replied: { points: 10, reason: 'Highest engagement signal' },
// Website
page_visit: { points: 2, reason: 'Tracked via UTM' },
pricing_page_visit: { points: 8, reason: 'High-intent page' },
demo_booked: { points: 20, reason: 'Explicit intent' },
// Negative
email_unsubscribed: { points: -50, reason: 'Hard negative — suppress' },
} as const satisfies Record<string, { points: number; reason: string }>