Scope

The “Enterprise Enrichment Plan” carried out in three labelled phases over two days, 2026-03-24 → 2026-03-25.

Plan document referenced: plans/ENTERPRISE_ENRICHMENT_PLAN.md (in repo, not in vault).

Commits

2f1119d — 2026-03-24 — Phase 1

“feat: Phase 1 critical fixes — reklamspärr gate, real validation layers, Art.14 at collection, email confidence”

Co-author: Claude Sonnet 4.6.

What changed:

  • src/compliance/reklamsparre.ts — new Gate 0. isScbAdvertisingBlocked() checks the SCB advertising_block column (Reklam=2). See Reklamspärr.
  • src/queues/workers.ts — replaced broken isOptedOut(orgNr) with the new gate. Triggered Article 14 in updateWorker.
  • src/api/export.ts — removed Article 14 trigger from the export handler. GDPR Art.14(3)(b) requires notification at collection time, not at export. See Article 14.
  • src/workers/art14Worker.ts — new BullMQ Art14_Job worker for async notification.
  • src/fetchers/bolagsverket/openApi.ts — real BV Öppen API client with OAuth2 token caching (replaced mock).
  • src/validation/layers/registryLayer.ts — real Layer 1, BV active-status check (replaced hash(orgNr) % 2 mock).
  • src/validation/layers/newsLayer.ts — real Layer 4, Serper news check (replaced random < 0.2 mock).
  • enrichmentEngine.v7.ts:286generateEmailGuesses() confidence demoted mediumlow, source set to generated_guess.
  • enrichmentEngine.v7.ts:765 — fixed LinkedIn regex: escaped special chars and a \bat\b backspace bug.

Before this commit, two of the four validation layers returned random or hash-based mock data. Lead scores from before 2026-03-24 are not comparable to scores after.

714a500 — 2026-03-24 — Phase 2

“feat: Phase 2 — decompose enrichmentEngine.v7.ts god file into modules”

Broke the 1176-line monolith into single-responsibility modules:

src/enrichment/
  types.ts           — EnrichmentInput, EnrichedContact, EnrichmentResult, RoleType, SearchResult
  config.ts          — INVALID_DOMAINS, INVALID_NAME_WORDS, INVALID_SUFFIXES
  processors/
    nameUtils.ts     — inferRoleType, isValidPersonName, normalizeCompanyName (pure)
    emailUtils.ts    — normSe, extractEmails, generateEmailGuesses, detectEmailPattern (pure)
    phoneUtils.ts    — extractPhones (pure)
    scoring.ts       — calculateLeadScore (pure)
  sources/
    serper.ts        — serperSearch() — replaced axios with native fetch
    domain.ts        — findCompanyDomain(), validateCompanyDomain()
    maps.ts          — queryGoogleMaps()

The current layout in Repository Layout descends from this commit. The pure annotation indicates processors/ modules have no I/O and are unit-testable.

c006048 — 2026-03-25 — Phase 3

“feat: Phase 3 — scale infrastructure for 1.4M enrichment runs/year”

Infrastructure for scale, not worker isolation. What landed:

  • migrations/002_enrichment_scale.sql — promoted JSONB fields (domain, lead_score, contacts_count, has_vd_contact, last_enriched_at) to real columns. Indexed lead_score DESC, has_vd_contact, domain, last_enriched_at. Added idx_companies_needs_refresh (partial index, stale > 6 months). idx_art14_email_org for Art.14 dedup. Added RoPA_Log.month_bucket column + index. Added enrichment_queue_state table.
  • src/lib/redisClients.ts — six named Redis logical DBs (BullMQ, enrich-cache, rate-limit, bv-token, domain-cache, serper-cache).
  • src/workers/enrichDispatcher.ts — rolling refresh at ~160 companies/hour (13 per batch every 5 min).

This commit does NOT decouple Playwright from the enrich worker. Memory MEMORY.md flags this as a still-open critical blocker before raising concurrency above 40. Phase 3 closed the data-layer scaling gap; the runtime-layer isolation work has not landed.

95694e5 — 2026-03-25 — Phase 3 follow-up

“fix: migration 002 — remove partial index with NOW(), move Art14 index to runtime, fix GENERATED column”

Fix-up to the migration. Postgres rejects NOW() inside a partial index predicate (the index would freeze at create time). Patch moved the timestamp comparison out of the index WHERE clause and into runtime queries.

Significance

The refactor era is the single biggest architectural pivot in the visible history. The current shape of src/enrichment/ and the migrations/ numbering scheme both originate here.

See also

EnrichV7, Lead Scoring, Reklamspärr, Article 14, Database Schema, History Overview.

See also