Skip to content

Sense — Overview

Sense is the data-collection product. Field inspectors record video, GPS, and radio scans on a phone; supervisors plan their routes and review the results on a web map; an external ML system enriches the frames with detections.

What ships in Sense

Repo What it is
axion.sense.backend Two .NET 10 binaries — Sense API (REST + gRPC) and Sense Worker (Kafka + Hangfire).
axion.sense.web Planner web app (React 19 + MapLibre + PMTiles).
axion.sense.contracts Protobuf contracts (5 files, code-gen for Kotlin / Swift / C#).

Subsystems

flowchart LR
  subgraph mobile["Sense Mobile"]
    record[Record video]
    slice[Slice frames]
    sensors[GPS / WiFi / cell]
  end

  subgraph planner["Planner Web"]
    plan[Plan territories]
    assign[Assign tasks]
    review[Review coverage]
  end

  subgraph api["Sense API"]
    rest[REST controllers]
    grpc[gRPC services]
  end

  subgraph worker["Sense Worker"]
    consumers[Kafka consumers]
    jobs[Hangfire jobs]
  end

  mobile -- gRPC --> api
  planner -- REST --> api
  api -- Kafka events --> worker
  worker -- FrameRecognitionRequest --> vision[Vision pipeline]
  vision -- detections in ClickHouse --> worker
  vision -- TrackMatchingResult --> worker

Vision is a separate Python service stack (Detections API / Worker / Quality / Clusterization / Matching) that owns the ML round-trip. It writes detection rows into the shared axion_sense.detections table directly — no Kafka roundtrip back to Sense. See Frame upload + ML flow for the full picture.

Responsibilities

Subsystem Owns Doesn't own
Sense Mobile App Capture loop, frame slicing, offline queue, presigned uploads, detour task sync. Detection logic, route planning.
Planner Web App Territory and task UX, coverage map rendering, user/role admin. Mobile workflows, analytics.
Sense API Request handling, authn + authz, transactional state in Postgres, presigned URL issuance, Kafka publish on commit. Long-running work, batch ingestion, ML round-trip.
Sense Worker Kafka consumption, Hangfire scheduling, ClickHouse batch writes, PMTiles generation, Citylens sync, audit batching. Synchronous user requests.

Key architectural choices

  1. Dual-port API. REST (8080) for the web app; gRPC (8081) for the mobile app. See ADR-0002. The dual port avoids HTTP/1 vs HTTP/2 quirks in the same listener and lets us wire different middleware stacks per protocol.
  2. Mobile uploads frames directly to S3. API issues a presigned URL; mobile PUTs the bytes; only metadata flows through the API. This is the single most important throughput decision in Sense.
  3. Async-by-default. Anything that can wait — ML round-trip, audit aggregation, PMTiles generation, coverage refresh — happens in the Worker. The API's job ends when the data is durable.
  4. Two persistence stores by purpose. Postgres for transactional state (users, orgs, tasks, mappings); ClickHouse for high-volume immutable data (tracks, frames, detections, audit). See ADR-0004.
  5. Hangfire on Postgres. Same operational footprint as the API; no extra infra. See ADR-0003.
  6. OpenFGA for authz. Multi-tenant + relationship checks are awkward in plain RBAC. See ADR-0005.

Where to go next