A web-based interface for querying the SAWGraph knowledge graph — a PFAS contamination dataset linking water samples, industrial facilities, and hydrological features across the United States.
The app lets you ask spatial analysis questions in plain English:
"What water samples are downstream of [22 - Utilities] facilities in Ohio?"
It translates those questions into multi-step SPARQL pipelines, executes them against SAWGraph's knowledge graph endpoints, and renders the results on an interactive map.
- React 19 + TypeScript + Vite 7
- Zustand — application state
- React Query — filter dropdown data with
staleTime: Infinity - react-leaflet — map rendering
- S2 Level 13 cells — spatial bucketing for all geo queries
cd sawgraph-query-editor
npm install --legacy-peer-deps # react-leaflet has a peer dep mismatch with React 19
npm run devOther commands:
npm run build # TypeScript check + Vite build
npm run lint # ESLint
npm run preview # Preview production buildDeployed on Railway, two environments, each with a frontend and an API
service. Build config lives in the Railway dashboard, not in this repo — there
is no railway.json or nixpacks.toml here.
| Environment | Branch | Frontend | API |
|---|---|---|---|
| Production | main |
https://sawgraph-explorer.up.railway.app | https://sawgraph-explorer-api.up.railway.app |
| Development | development |
https://sawgraph-explorer-development.up.railway.app | https://sawgraph-explorer-api-development.up.railway.app |
The frontend reaches the API through VITE_API_BASE_URL, baked in at build
time, so each environment's frontend points at its own API. The API allows the
frontend's origin via FRONTEND_ORIGIN. Both services share a Postgres
instance per environment.
An Analysis Question has three parts:
[Block A — target entity] [Relationship] [Block C — anchor entity]
water samples downstream facilities in Ohio
When you click Apply, the query engine:
- Plans the question (
engine/planner.ts) → array ofPipelineStep - Executes steps sequentially (
engine/executor.ts), threading S2 cell sets between steps - Renders results as map layers (
resultTransformer.ts→MapFeature[])
Supported entity types: samples, facilities, water bodies, wells, streams Supported relationships: near (~1–2 km), downstream, upstream
Downstream/upstream traces accept an optional cumulative flowpath cutoff
(Within N km of flow). Unset, the trace is the full transitive closure.
node scripts/flow-distance-check.mjs verifies the bounded trace against the
live endpoints.
All hosted at apps.okn.us:
| Endpoint | Used for |
|---|---|
sawgraph |
Sample data, substances |
fiokg |
Facility industry codes |
federation |
Facility spatial queries (kwg-ont:sfContains) |
spatialkg |
S2 cell lookups, region/county boundaries |
hydrologykg |
Upstream/downstream tracing |
| Dropdown | Data source |
|---|---|
| Industry (NAICS) | Live SPARQL → fiokg, fallback to hardcoded list |
| Substance | Live SPARQL → sawgraph, fallback to hardcoded list |
| Material type | Live SPARQL → sawgraph, fallback to hardcoded list |
| State | Static — all 50 states; 13 with SAWGraph data are selectable |
| County | Live SPARQL → spatialkg, fetched on state selection |
Inside docs/:
| File | Contents |
|---|---|
ARCHITECTURE.md |
System design, module boundaries, data flow |
SCHEMA.md |
Predicate inventories, class counts, endpoint roles |
QUERY-MATRIX.md |
Every query shape, measured — plus the error catalogue (Part 4) |
health/STATUS.md |
Weekly dashboard health: working, timings, row-count drift |
query-matrix/ |
One CSV per sweep, dated, with a generated index |
DEBUGGING.md |
Documented bugs with root causes and fixes |
CONVENTIONS.md |
Coding standards, endpoint selection rules |
wiki/ |
One page per filter dropdown; source of truth for the GitHub Wiki |
queries/ |
Write-ups of individual real questions |
changelog/ |
Weekly changelogs (YYYY-Www.md) |
plans/ |
Feature planning: drafts/ → active/ → done/ |