System architecture¶
OpenSoil uses one monorepo with separately deployable applications. This keeps shared decisions and infrastructure visible without coupling runtime failures.
Internet
└─ Route 53
└─ Nginx + Let's Encrypt on one EC2 host
├─ opensoil.net → static release + Node app on 127.0.0.1:3000
├─ lab.opensoil.net → independent Node prototype on 127.0.0.1:8080
├─ api.opensoil.net → API landing pages + FastAPI on 127.0.0.1:8100
└─ docs.opensoil.net → versioned MkDocs static release
FastAPI
├─ versioned routes and generated OpenAPI
├─ canonical Pydantic models and registries
├─ provider adapter registry
├─ bounded in-memory cache
└─ USDA SDA adapter → official USDA REST/POST service
Normalization flow¶
- FastAPI validates coordinate, provider, properties, and depth.
- The route resolves an adapter; it contains no provider SQL or parsing.
- The adapter builds a bounded provider request from allowlisted fields.
- A timeout, one limited retry, and cache protect the provider and caller.
- The original JSON enters the cache with a timestamp.
- The adapter maps rows into canonical observations while preserving original fields.
- FastAPI validates the response model and returns JSON with request/cache metadata.
The raw route stops after step 5 and places the original payload in an explicitly labeled wrapper.
API versioning¶
Public contracts live under /v1; health, generated OpenAPI, and the explorer are unversioned service surfaces. The project is experimental, so v1 can change with decision-log updates. Stable releases will add a documented deprecation and compatibility policy.
Database boundary¶
The host already runs loopback-only PostgreSQL/PostGIS for the main application. The first public adapter does not need persistent metadata, spatial joins, or durable caching, so it does not gain database credentials. A future persistent cache or area service will use a separately owned schema and least-privilege runtime role.
Provider availability¶
/health checks OpenSoil only. Provider records expose planned/operational state, while actual upstream failures return provider-specific errors. This avoids making routine health checks consume provider capacity or marking the application dead during a temporary government-service interruption.