Skip to main content
Locus’s analytics tables are built by a dbt-core project (axiom_locus) that transforms raw ingestion tables into typed, tested, APRS-compliant marts. Marts are the layer downstream consumers — the dashboard heatmap, Explorer, scoring service, and API — read from. Staging and intermediate models are internal scaffolding; only marts are part of the public contract.
Locus uses dbt-core only. There is no dbt Cloud deployment. Local development materializes to DuckDB; production runs against the Locus Postgres database.

Project layers

The axiom_locus dbt project has three model layers with fixed materializations: Staging models normalize column names, cast types, and pass through the APRS envelope. Marts apply the aprs_envelope macro so every mart row carries record_id, source_uri, source_system, schema_version, normalization_version, acl_tier, occurred_at, ingested_at, and modified_at.

Sources

The dbt project reads from three source tables in the public schema:
cell_scores is owned by the scorer service. dbt reads it through stg_cell_scores but never writes to it. Do not point dbt models at cell_scores as a materialization target.

mart_axiom_events

mart_axiom_events is the unified event surface for Locus. It is the same axiom_events signal the dashboard heatmap and Explorer read from. Each row is one real-world event with H3-cell attribution and full APRS envelope.

Configuration

  • Incremental — only rows where ingested_at is newer than the current watermark are added on each run.
  • Append-onlyevent_id (an md5 of {source}:{record_id}) is unique and stable. Re-running dbt is idempotent.
  • Schema-evolution-safe — new columns added to the model are appended to the table without rebuilds.

Columns

Current event sources

Part A ships with two unioned sources: Additional event sources land in Part B without changing the row shape — on_schema_change='append_new_columns' lets new columns appear without a rebuild.

Querying

mart_axiom_events is a regular Postgres table — query it with any SQL client:
Join to cell_scores to pull current scoring context for any event’s cell:

The aprs_envelope macro

Every mart row carries the APRS envelope via a single macro call, so envelope shape is identical across sources:
Defaults: schema_version='aprs.locus/1.0.0', normalization_version='1.0.0', acl_tier='research'. Override per source when needed.

Local development

Install the dbt adapters into a Python environment:
Then build the project against DuckDB:
The dev target writes to /tmp/axiom_locus.duckdb. Use any DuckDB client to inspect compiled marts locally.

Targets

The project ships three profiles: Both Postgres targets materialize into the public schema by default.

Continuous integration

A GitHub Actions workflow runs dbt deps && dbt build --profiles-dir profiles --target ci on every pull request that touches the dbt project or the workflow file itself. The build executes every staging, intermediate, and mart model plus the not_null / unique schema tests defined in models/marts/schema.yml. PRs that break a model or fail a test are blocked at merge. Schema tests currently enforced on mart_axiom_events:
  • event_idnot_null, unique
  • occurred_atnot_null
  • h3_indexnot_null

Roadmap

Three marts are planned. Part A ships mart_axiom_events; the remaining two land in Part B without changes to the existing mart:
  • mart_cell_score_composite — replaces the in-app composite computation in the scorer service.
  • mart_metro_overview — replaces the hand-rolled metro overview SQL in the web service and retires the legacy refresh_metro_overview pg_cron job.
The full retirement plan for legacy pg_cron event-source jobs runs in stages: land marts, parity-check against the existing pg_cron-derived tables, migrate downstream consumers, then disable the pg_cron jobs.