Skip to content

Architecture

Local Visibility Scorecard keeps collection, judgment, scoring, rendering, and verification separate. Each stage is independently runnable.

COLLECT
search / maps / AI / authority observations
        ↓
REVIEW
human or model fills only what needs judgment
        ↓
SCORE
validate · normalize · apply caps
        ↓
RENDER
one-page + full report
        ↓
VERIFY
invariants · provenance · mutations

Stage 1 — Collect

collect.mjs writes a pending worksheet to .audit/<slug>/<run-id>/:

  • audit.json — normalized observations, sources, metrics, and 52 checks (deterministic checks pre-filled, judgment checks pending),
  • query-universe.json — the locked universe,
  • run.json — manifest with audit mode, source mode, versions, and universe hash,
  • metrics.json — the deterministic metric block,
  • plan.json — task counts and ceilings, in live mode.

Fixture mode reads a synthetic normalized fixture. It performs no network access and needs no credentials.

Live mode uses DataForSEO through the provider layer. It never calls, texts, books, submits a form, or changes a profile.

Stage 2 — Review

A reviewer fills the five judgment checks in audit.filled.json:

  • LV-ORG-05 — ranking URL relevance,
  • LV-AI-04 — service relevance in AI answers,
  • LV-AI-05 — location relevance,
  • LV-AI-06 — entity accuracy,
  • LV-AUT-02 — local source relevance.

Only those checks use judgment. Ranks, coverage, competitor share, authority counts, and validity gates are deterministic.

A model proposal is merged with apply_judgment.mjs, which accepts only model-judgment checks and requires evidence references. The reviewer must not invent unavailable data.

Stage 3 — Score

score.py validates the filled audit, recomputes the query-universe hash, checks the validity gates, removes N/A checks, normalizes category weights to 100, applies the single deterministic cap, and writes scores.json.

The scorer never contacts the business. It is file-based and deterministic.

Stage 4 — Render

render.mjs combines scores.json with reviewer-written content.json. The renderer owns:

  • score strings,
  • category and journey tables,
  • the signature visibility matrix,
  • the visibility snapshot,
  • the geo-grid summary,
  • the AI surface breakdown,
  • check rows,
  • fix point limits,
  • confidence labels,
  • synthetic label,
  • provenance table.

The model owns prose: verdict, gaps, strengths, next steps, limitations. It cannot invent check IDs, owner-data IDs, or points.

Stage 5 — Verify

verify.mjs independently recomputes the math and checks:

  • check IDs and category weights,
  • query-universe hash and lock,
  • applicability and structural N/A,
  • score bounds, evidence, confidence, source references,
  • fact consistency and cap triggers,
  • category and total math,
  • owner-data exclusion,
  • metrics and deterministic check derivation,
  • validity gates,
  • AI source-type separation,
  • competitor cohort linkage,
  • target-match confidence,
  • report score, category rows, and finding rows,
  • the visibility matrix,
  • synthetic labels and required sections,
  • no placeholders or credential-like material.

It runs a mutation suite that intentionally breaks totals, category scores, confidence, evidence, caps, labels, report linkage, owner-data exclusion, the universe hash, deterministic scores, metrics, validity gates, AI source types, the competitor cohort, target matches, and N/A handling. If a mutation survives, verification fails.

Why the stages stay separate

A monolith is hard to audit. Separating files makes it possible to:

  • inspect observations without re-running collection;
  • change a report without changing a score;
  • reproduce a historical score;
  • test a cap in isolation;
  • replace the judgment layer with fixtures;
  • run CI with no credentials and no network.

npm run audit is a wrapper that prints the next commands. It is not an opaque scoring engine.

Audit modes

Public Mode is the only implemented mode. Connected Mode is a future, authorized read-only mode. Each run records audit_mode. The public score is never silently rewritten with connected performance data; connected diagnostics will be reported separately.

Non-destructive boundary

The collector:

  • uses search and data-provider requests only;
  • does not submit forms;
  • does not create bookings;
  • does not place calls or send messages;
  • does not create accounts;
  • does not enter payments;
  • does not mutate a profile;
  • uses explicit timeouts, bounded retries, and bounded response reads.

Repository layout

.opencode/skills/home-service-local-visibility-audit/
  SKILL.md
  references/
    rubric.json
    rubric.md
    coverage-matrix.json
    portable-checklist.md
    fix-library.md
    one-page-template.md
    report-template.md
    schema/
  scripts/
    build_universe.mjs
    collect.mjs
    score.py
    render.mjs
    verify.mjs
    apply_judgment.mjs
    eval.mjs
    providers/dataforseo/
    lib/
    fixtures/
examples/acme-plumbing/
docs/
scripts/

See Development for commands and test structure.


Related: The Scorecard Family · Website Scorecard · GBP Scorecard