MGeoAI is a preliminary, provenance-preserving pipeline for turning saved traffic-related news, social posts, and existing image/video analysis JSON into reviewable incident records. It clusters reports about the same real-world incident, retains conflicts and attribution, estimates location at defensible precision, aggregates aspect-based sentiment, and serves JSON, GeoJSON, Markdown, and a map-first operations dashboard.
The application fuses attributed evidence. It does not independently verify that an incident occurred, determine fault, or establish that a claim is true.
Requirements: Python 3.11+, Node 20+, and npm.
New to the project? Follow the complete beginner setup tutorial for environment configuration, offline and live runs, reviewer accounts, source submission, testing, and troubleshooting.
For the cross-disciplinary study design, curation rules, uncertainty model, evaluation plan, and ethical/legal boundaries, read the research methodology. The canonical international-corpus contract is documented in docs/CORPUS.md.
python -m venv .venv
source .venv/bin/activate
python -m pip install -e '.[dev]'
cd web && npm install && cd ..
mgeoai doctor --provider recorded
mgeoai run --input assets/scraps --provider recorded --output-dir outputs/demoThe recorded provider is deterministic, offline, costs nothing, and is labeled as non-live in every incident. Start development servers in separate terminals:
mgeoai serve --data-dir outputs/demo --host 127.0.0.1 --port 8000
cd web && npm run devOpen http://127.0.0.1:5173. API documentation is at
http://127.0.0.1:8000/docs.
For a production frontend build served by FastAPI:
cd web && npm run build && cd ..
mgeoai serve --data-dir outputs/demo --host 0.0.0.0 --port 8000Open http://127.0.0.1:8000.
Release v0.2.4 includes the production frontend, the validated DeepSeek Flash
thinking-mode output, and the complete recorded global-v1 visualization. Hosting
these checked-in outputs does not require Node, a provider key, or data regeneration.
Install the Python package and run either dataset:
mgeoai serve \
--data-dir outputs/deepseek-refusion \
--host 0.0.0.0 \
--port 8000
# Or serve all 51 collection-country groups and 510 news sources.
mgeoai serve \
--data-dir outputs/global-v1 \
--host 0.0.0.0 \
--port 8000The bundled output contains the API records, GeoJSON, source/evidence views, per-incident reports, and provider-run metadata required by the dashboard. Transient provider-response caches and cost ledgers are intentionally excluded.
Copy .env.example to an ignored .env or export values from a secret manager.
The CLI intentionally does not auto-load arbitrary .env files; activate them
in your shell or deployment system.
export FUSION_PROVIDER=deepseek
export DEEPSEEK_API_KEY='...'
export DEEPSEEK_MODEL='deepseek-v4-pro'
export DEEPSEEK_API_MODE=chat_completions
export DEEPSEEK_THINKING=enabled
export DEEPSEEK_INPUT_COST_PER_MILLION_USD='current-provider-price'
export DEEPSEEK_OUTPUT_COST_PER_MILLION_USD='current-provider-price'
mgeoai doctor --provider deepseek
mgeoai smoke-live --data-dir outputs/demo --output-dir outputs/live-smoke
mgeoai run --input assets/scraps --provider deepseek --output-dir outputs/live-demoThe model is not hard-coded. Check the current provider documentation and choose
an available JSON-output-capable model. In auto, the adapter currently chooses
the broadly supported Chat Completions JSON mode. responses explicitly requests
JSON Schema output. A failed live call produces a failed/retryable cluster; it
never activates recorded or deterministic fusion silently.
The adapter caps timeouts, retries, concurrency configuration, and output tokens. It records endpoint mode, prompt/schema/provider/request hashes, latency, usage, retry and validation state. Successful calls are cached using canonical evidence, prompt, schema, provider, mode, and model. Credentials are never included in requests logs, outputs, API data, or the browser.
The dashboard includes a public Submit source form and an authenticated Review queue. Runtime intake accepts the same two source shapes as the supplied scraps:
- an HTML scrap ZIP containing exactly one UTF-8
.htmlpage, with optionalSOURCE_INFO.mdandimage_01.jsonin the same folder; or - one UTF-8 video-analysis
.jsonobject matching the files underassets/scraps/youtube.
Raw images and raw video are not accepted; image information accompanies HTML
as image_01.json. Uploads and extracted ZIP contents are capped at 25 MB.
Packages are quarantined under outputs/<run>/moderation/uploads/. Reviewers can
inspect each HTML, metadata, and analysis file as inert text, verify hashes and
the file manifest, download the original package, and review its audit history
before rejection or approval. Approval copies the package into
outputs/<run>/runtime_scraps/, combines it with the configured base scraps,
and reruns the pipeline with the configured provider. Submitted HTML is parsed
as untrusted data and is never executed or sent raw to the fusion model.
The multipart submission fields are submission_type (html_bundle or
video_json), package, and optional submitter_name.
Configure any number of reviewers with PBKDF2 password hashes. Generate each hash interactively, then provide a JSON username-to-hash mapping and a random session-signing secret of at least 32 characters:
mgeoai hash-reviewer-password
export MGEOAI_REVIEWERS_JSON='{"alice":"pbkdf2_sha256$...","bob":"pbkdf2_sha256$..."}'
export MGEOAI_SESSION_SECRET='replace-with-a-random-secret-at-least-32-characters'
export MGEOAI_DATA_DIR='outputs/demo'
export MGEOAI_BASE_SCRAPS_DIR='assets/scraps'
mgeoai serve --data-dir outputs/demoSet MGEOAI_SECURE_COOKIES=true when serving over HTTPS. Reviewer sessions use
an eight-hour, HTTP-only, SameSite cookie and mutating actions also require a
CSRF token. In an internet-facing deployment, terminate TLS and apply request
size/rate limits at the reverse proxy. Reviewer passwords and the signing secret
belong in a secret manager, not in the repository or generated data.
Approval is queued in-process and returns immediately with processing; the
review page polls until the pipeline finishes, avoiding reverse-proxy timeouts
during long live runs. A live provider failure stays visible as an
ingest_failed submission that can be retried; MGeoAI never silently changes to
the recorded provider. Keep the single server process running while an approval
is processing.
mgeoai doctor --provider recorded
mgeoai hash-reviewer-password
mgeoai ingest assets/scraps --output outputs/demo/manifest.jsonl
mgeoai export-schemas --output-dir schemas
mgeoai run --input assets/scraps --provider recorded --output-dir outputs/demo
mgeoai evaluate --data-dir outputs/demo --output outputs/demo/evaluation.json
mgeoai run --input assets/scraps --provider deepseek --output-dir outputs/live-demo
mgeoai smoke-live --data-dir outputs/demo --output-dir outputs/live-smoke
mgeoai serve --data-dir outputs/demo
mgeoai corpus-validate --corpus-dir corpus/global-v1
mgeoai corpus-materialize --corpus-dir corpus/global-v1 --output-dir build/global-scrapsrun is the end-to-end command: discovery, HTML conversion, modality adapters,
normalization, incident splitting, candidate matching, provider adjudication,
constraint-aware clustering, provider fusion, provenance validation, geolocation,
sentiment, and reporting.
The paper-ready evaluator keeps human-labelled accuracy separate from full-corpus
descriptive analysis. The gold subset is defined in
evaluation/gold/gold_incidents.json with
stable paths into the immutable assets/scraps corpus. It contains six manually
verified incidents; HTML source 12 is split into distinct Sylhet and Bogura mentions.
Regenerate all tables, CSVs, figures, validity checks, Markdown, and LaTeX with:
python -m pip install -e '.[dev,evaluation]'
mgeoai paper-evaluate \
--run outputs/deepseek-refusion \
--gold evaluation/gold/gold_incidents.json \
--corpus-run outputs/global-v1 \
--output evaluation \
--seed 42The gold run supplies predictions compared with human labels. The global run supplies
only corpus composition, completeness, provenance, conflict, geolocation, modality,
and efficiency statistics; those are never called accuracy. Collection-country
fallback markers count as map-visible records, not resolved incident locations.
Canonical manuscript numbers are in
evaluation/results/paper_metrics.json and
must only be used when
evaluation/results/validation_report.json
reports valid. The full report is
evaluation/PAPER_RESULTS.md.
Supported inputs are local saved HTML, SOURCE_INFO.md metadata next to HTML,
the supplied video/general analysis JSON, and the supplied image-analysis JSON.
The international-corpus layer adds bounded GDELT discovery and robots-aware
HTML capture helpers. Discovery is metadata-only until a source passes the
corpus review contract; blocked, authenticated, paywalled, or unsupported pages
are skipped and recorded. Raw media inference and unauthorized social-platform
scraping are not implemented.
corpus/global-v1 is the canonical validated metadata/excerpt seed corpus. It keeps
the publisher URL for every source, SHA-256 hashes each committed payload,
records access/review decisions, and separates curation-only multi-source labels
from model input. The current snapshot contains 51 collection-country groups, 510
sources, and 53 automated distinct-domain multi-source candidates. These are
automated screening results, not human confirmation of incident truth, event country,
or upstream editorial independence. To check quotas and reproduce the pipeline input:
mgeoai corpus-validate --corpus-dir corpus/global-v1
mgeoai corpus-materialize \
--corpus-dir corpus/global-v1 \
--output-dir build/global-scraps
mgeoai run \
--input build/global-scraps \
--provider recorded \
--output-dir outputs/global-v1
cp corpus/global-v1/reports/coverage.geojson outputs/global-v1/coverage.geojson
mgeoai serve --data-dir outputs/global-v1 --host 127.0.0.1 --port 8000The dashboard's Global coverage view displays source counts at conservative
country centroids. Those markers are collection-country coverage symbols and are
explicitly not incident coordinates. The Overview map continues to use only
source-grounded incident geolocation. Direct links, article titles, discovery links,
and pair keys for manual checking are in
NEWS_SOURCES.md and
corpus/global-v1/reports/source_links.csv; the country/quota table is in
corpus/global-v1/reports/countries.md. Regenerate the Markdown catalog with:
mgeoai corpus-source-catalog \
--corpus-dir corpus/global-v1 \
--output NEWS_SOURCES.mdBest-effort social discovery is retained separately under
work/global_candidates/group_social/. It contains public Bluesky AppView metadata
for eight countries, stores no media or full thread capture, and is excluded from the
accepted corpus until a human verifies event location, authorship, and source
independence.
Generated data is ignored under outputs/<run>/:
manifest.jsonl discovered artifacts and hashes
sources/<source_id>/content.md clean model-readable Markdown
sources/<source_id>/blocks.json reversible block/provenance sidecar
sources.jsonl canonical source records
evidence.jsonl normalized evidence items
mentions.jsonl incident-level mentions (one source may have many)
sentiment.jsonl holder/target/aspect sentiment evidence
matches.json features and provider adjudications
clusters.json accepted incident clusters
incidents/<id>/incident.json canonical validated incident
incidents/<id>/report.md deterministic human-readable report
incidents.json API collection
incidents.geojson WGS84 [longitude, latitude] mapped collection
provider_runs.json operational metadata without hidden reasoning
failed_clusters.json terminal failures retained for retry/review
run.json batch summary and recorded/live mode
cache/ successful request cache
moderation/submissions/ pending/reviewed submissions and audit history
moderation/uploads/ quarantined original packages and inspected files
runtime_scraps/ approved HTML/image-analysis/video-analysis scraps
Versioned JSON Schemas are exported to schemas/.
Checked-in defaults live in configs/default.toml; environment values override
provider secrets and deployment-specific settings. The major controls are
documented in .env.example: endpoint, model, API mode, timeout, retry count,
concurrency, token budget, and cost guardrails. Manual must-link/cannot-link pairs
can be placed in the TOML [overrides] section.
The offline gazetteer stores source-named representative intersections, road segments,
areas, and city/district centroids; its coordinates never claim rooftop precision.
When the 51-country seed corpus contains an unresolved place, the dashboard uses a
low-confidence amber collection-country fallback so the record remains visible. That
fallback is explicitly labeled collection_country_fallback, is not treated as
incident-location evidence during matching, and is not a reported crash coordinate.
The incident detail preserves the distinction and withholds the “nearby roads” link
for these fallback markers.
The FastAPI service provides:
GET /api/incidentswith bbox, date, type, severity, confidence, source-count, sentiment, search, and pagination filters;GET /api/incidents/{incident_id};GET /api/incidents/{incident_id}/evidencewith full cited claims and provenance;GET /api/incidents.geojsonwith server-side spatial/filter handling;GET /api/coverage.geojsonwith explicitly non-incident country coverage markers;GET /api/sourcesandGET /api/sources/{source_id}with safe extracted content, plusGET /api/runsandGET /api/health;- multipart
POST /api/submissionsandGET /api/submissions/{submission_id}/statusfor public runtime package intake and status receipts; POST /api/reviewer/login,GET /api/reviewer/me, and reviewer-only queue, inert file preview, original-package download, logout, approval, rejection, retry, and audit endpoints under/api/reviewer.
The React interface starts on a world map and includes marker clustering,
map/list synchronization, URL-preserved filters and selected incident, location
precision and named uncertainty regions, labeled roads and administrative
boundaries, informative fused summaries, a detailed fact/conflict/source review,
inspectable evidence and source content, unmapped records, source and run tables,
loading/error/empty states, a system/light/dark theme selector, and a small-screen
map/list toggle. Override the light map with
VITE_MAP_STYLE_LIGHT_URL (or the legacy VITE_MAP_STYLE_URL) and the dark map
with VITE_MAP_STYLE_DARK_URL; selected styles must provide their required
attribution.
pytest
ruff check .
mypy src/traffic_fusion
cd web
npm test
npm run lint
npm run buildTests use the recorded provider and mocked network responses. They must not make live paid calls. Use a short, explicitly configured smoke run for DeepSeek.
To add a modality, implement an adapter that maps the new extraction contract to
EvidenceItem, preserving unknown fields and a source locator. Then register the
artifact in discovery and add sample-derived fixtures.
To add a provider (including the planned OpenAI provider), implement the small
FusionProvider protocol: adjudicate, fuse, runs, name, and model.
Canonical models, orchestration, CLI, reports, and API do not need to change.
Candidate matching is intentionally transparent. The deterministic score weighs time (30%), normalized location (35%), named entities (15%), vehicles (5%), and distinctive lexical overlap (15%). Generic crash terms are excluded. Specific date/location conflicts become hard cannot-links before the provider is called; distinctive shared anchors raise recall, while the provider still adjudicates the bounded candidate. Cluster construction refuses a transitive merge that would violate any cannot-link.
Recorded fusion confidence starts from direct normalized support, adds repeated independent agreement, and subtracts conflicting alternatives; it is capped at 0.90. Live DeepSeek confidence is accepted only after schema, ID, quantity, and provenance validation. Extraction confidence never changes an attributed allegation into an observation.
- HTML extraction is reproducible and provenance-preserving but heuristic; large site redesigns need publisher-specific selectors.
- The incident splitter uses transparent lexical/location anchors suited to the supplied Bangladesh corpus. Broader deployment needs learned extraction plus a larger evaluated gazetteer.
- No raw image/audio/video inference, live scraping, external geocoder, or multi-tenant retention service is included.
- The moderation queue and reviewer sessions use local files and process-local locking. Multi-instance deployment needs a shared database, distributed job queue, centralized rate limiting, and coordinated pipeline publication.
- The small evaluation corpus is a development fixture, not an accuracy claim.
- The production frontend is functional but MapLibre keeps the initial JavaScript bundle above Vite's 500 kB advisory threshold; route/map code splitting is a performance follow-up, not a build failure.
