Platform · 2026
War Monitor
Public-source conflict intelligence, scored for trust before it is drawn.
46 Prisma models
Platform
2026
Key decisions
- Every event carries an independence-weighted trust score computed before it is eligible to render, so the map is a claim about confidence rather than volume.
- Three or more reports that trace back to a single source family cap the trust score at 20, so syndicated volume can never outrank genuinely independent corroboration.
- Both ends compile against packages/shared and the typed API client is regenerated from the OpenAPI hash under a CI drift check, so a contract break fails the build instead of shipping.
- Documentation counts are generated by a script that walks the codebase, and CI fails if the committed file drifts from what the code actually contains.
The problem with conflict data
Every open conflict feed carries the same defect: a single event reported by five outlets that all sourced it from one wire becomes five confirmations. Count naively and a rumour outranks a verified strike.
War Monitor treats that as the central problem rather than a cleanup step. Every event carries an independence-weighted trust score computed before it is eligible to render. Correlated sources collapse toward the weight of one; genuinely independent corroboration compounds. The globe only draws what clears the gate, so the map is a claim about confidence, not a claim about volume.
A map that plots everything reported is a map of how loud the reporting was. The gate is the difference between a feed and an assessment.
Architecture
A TypeScript monorepo of two apps and eight packages, orchestrated by Turborepo.
apps/web is Next.js 15 on React 19, apps/api is Fastify 5, packages/db is
Prisma 6 over PostgreSQL 16 across 46 models — plus a raw-managed PostGIS geom
column and a pgvector event_embeddings table that sit outside the schema on
purpose, behind a capability guard. packages/shared holds the types both ends
compile against, and the typed API client is regenerated from the OpenAPI hash —
CI runs the generator in check mode, so a hand-edited or stale client fails the
build.
Migrations do not run through prisma migrate. A checksummed engine in
packages/db/scripts journals a two-layer manifest of 47 entries, declares the
database capabilities it needs (pgcrypto, PostGIS, pgvector), and refuses
out-of-order application.
Ingest runs against six first-class sources — GDELT, UCDP, GDACS, ReliefWeb, Telegram and ACLED — plus a generic RSS adapter contract for the long tail. Every source name resolves to a family (a wire, a primary outlet, a dataset), and the trust spine uses distinct families, not report counts, to decide how much a second report is actually worth.
The four systems of the 2.0 rebuild
The trust spine (apps/api/src/services/trust/score.ts) is pure and
deterministic: one source family caps the score at 25, two score 50, three 70,
four or more 85, a primary outlet adds 12 — and three or more reports that
collapse to a single family are flagged echo-chamber and capped at 20. The
discrete ladder behind it reserves CONFIRMED for high-severity events with a
primary-family corroboration, and golden tests pin the bands so a tuning change
is a reviewed diff, not a vibe.
The ingest layer normalises six inconsistent source schemas into one event shape, then deduplicates on Unicode-aware token similarity inside geo-and-time blocks — a fix that exists because Arabic and Cyrillic titles once tokenised to nothing and could never corroborate. Corroboration is monotonic: late-arriving sources merge into the stored event, trust is recomputed over the union, and an event’s status only ever rises.
The globe renders scored events on CesiumJS, code-split into a lazy chunk that the 300 KB first-load bundle budget explicitly excludes, with a time scrubber for replay and horizon culling at altitude. The console is the authenticated analyst surface — review queues, source management, and adjudications that are themselves stored as append-only records freezing the evidence as the reviewer saw it, with a compare-and-set on status so two analysts cannot silently overwrite each other. Analyst-facing AI — briefings, entity extraction, natural-language query — runs through a local model gateway behind a CI-enforced local-only policy, with golden release evals and quality budgets as gates.
Verification
Structural counts in the documentation are generated, not typed: a script walks
the codebase and writes docs/generated/ARCHITECTURE-STATS.md — 57 API route
modules, 264 service modules, 46 Prisma models, 731 test files holding 5,983
test declarations, 23 architecture decision records — and CI fails if the
committed file drifts from what the code actually contains. The same pipeline
enforces gates on documentation, configuration, roadmap, migrations, policy,
performance contracts, types, tests, build and security.
That habit — making the docs a build artifact rather than a promise — is the part of this project I have reused most everywhere else.