Illustrative scenario: fictional locations and simulated telemetry, not operational records. Decision support only.
All depots, forward locations, routes, vehicles, terrain, weather, consumption and sensor telemetry in this repository are fictional and generated by a seeded script. Nothing here represents real Army data, real bases, real operational routes, troop/unit positions, deployments or real sensors, and this is not production military software. Every recommendation is a decision-support output computed from a simulated scenario; final decisions rest with authorised personnel.
The machine-learning components were trained on ISOMORPH, a simulator-generated research benchmark (CC-BY-4.0) - see
ml/. Benchmark scores are not evidence of accuracy on real operations.
Full-stack upgrade of the original v3 HTML version (not shipped in V7; its calculations are pinned by backend/tests/golden_v3.json) into React + Vite + TypeScript + Tailwind ·
FastAPI + Pydantic + SQLAlchemy · PostgreSQL + PostGIS · Docker Compose, with a simulated ESP32/RFID layer.
Indian Army – Predictive Logistics & Forward Supply Chain. Forward locations in difficult terrain are resupplied over long, weather-exposed routes. Planners need to predict consumption, see shortages before they happen, decide what to send first, and pick routes and vehicles that can actually deliver — and to understand why a location is at risk.
SIH 2026 Context & Historical Evidence: Developed for Smart India Hackathon (SIH) Problem Statement 26251. The original prototype UI demonstrations, dark/light interface captures, and legacy artifacts are preserved for historical continuity:
- Original Hackathon UI Screenshots: docs/screenshots/sih_original/
- Original Prototype Code & Sweeps: legacy/v3/ and legacy/scripts/api_sweep.py
| Need | What the system does |
|---|---|
| Predict demand | Weather-aware statistical forecast (default) or a trained LightGBM model (opt-in / promotion-gated), per location × supply, 7/14/30-day horizons, 80 % interval, backtest MAPE, confidence, per-row model attribution; Random Forest cross-check |
| See shortages early | Days-of-supply, 14-day predicted shortage, stock-out date, GREEN/YELLOW/RED (NORMAL/WARNING/CRITICAL) classification, explainable "Why at risk" |
| Decide what first | Supply priority queue (score 0–100) from shortage, stock deficit, criticality, lead time, route feasibility, transport capacity |
| Decide how | Route optimisation (distance, ETA, vehicle capacity, road, weather, terrain) with alternatives and a stated reason; allocation within real depot stock and vehicle capacity |
| Test resilience | What-If simulation: demand, weather, road, vehicle availability → before/after metrics + explanations |
| Near-real-time inventory | Simulated ESP32/RFID events → FastAPI → PostgreSQL → inventory, alerts, priority, allocation, dashboard |
- Dark / Light theme — Dark is the default; the sidebar switch (keyboard-accessible,
role="switch") persists the choice inlocalStorageand applies it before first paint. Light-theme text colours are darkened so sampled text pairs measure ≥ 4.5:1 (WCAG AA) in both themes, while GREEN / YELLOW(amber) / RED keep their meaning (status is also always written out: CRITICAL / WARNING / NORMAL). - Command Dashboard opens with a Command Briefing: overall risk gauge, critical locations, predicted shortages, transport capacity vs need, active alerts and the top 3 recommended actions, followed by "Why this recommendation?" (stock-out horizon, shortage, lead time, criticality, route risk, vehicle/dispatch, and the weighted score breakdown for any of the top priorities).
- GIS shows source → destination direction arrows, the selected route (thick, labelled) vs alternatives (dotted, labelled), SOURCE / DESTINATION tags, and a Route Selection Explained panel with a per-alternative "why not" (score / risk / ETA / capacity deltas).
- Forecast separates the active model (statistical by default; trained model when promoted or opted in), the trained-model preview / Random Forest cross-check (never feed decisions), the validation metrics and the confidence, and states plainly what is and is not AI.
- What-If adds a concise Recommended Response derived from the backend's simulation result plus the scenario's own
/api/priorityand/api/allocation. - IoT shows the flow Telemetry → Database → Inventory/Risk impact for the latest simulated scan (stock before/after,
iot_eventsid, status, critical-item count and risk index before/after). - The header now reads SIMULATION TIME · 19 JUN 2026 and DATA · ILLUSTRATIVE SCENARIO (date-level only; hover: fictional clock anchored at 2026-06-19, 60×, not real time). The underlying simulation clock is unchanged.
V4 polish pass (presentation only — backend/, engines, forecasting/routing/allocation logic, schema and API contracts untouched)
- Confidence Index — forecast confidence is shown as
Confidence Index: 99/100 — HIGHwith an info tooltip: heuristic confidence index derived from historical backtest error; not a probability of forecast correctness. The backend value/label/formula are unchanged. - System Decision Flow strip at the top of the Command Dashboard: DATA → FORECAST → RISK → PRIORITY → ALLOCATION → ROUTE → DECISION, each step showing an existing backend figure (records, units short, risk index, top priority, allocated/need, top route) and ending in human decision support.
- Why this recommendation? gains a Recommendation · decision support banner naming source depot → destination via route (from the allocation legs) and stating it is advisory, not an autonomous command.
- Why this route? heading plus Total route risk and Alternatives tiles in the GIS route explanation.
- IoT value flow: ESP32 / RFID TELEMETRY → API → PostgreSQL → INVENTORY / RISK → DECISION SUPPORT, stating that all telemetry is simulated and no hardware is required.
- Recommended Response adds a route-risk line (only when the backend's mean route risk rose), a fleet-availability line (only when vehicles < 100% and capacity still suffices) and a note to use APPLY TO ALL SCREENS to rerun allocation; every line is derived from the simulation result.
- The recommendation engine is labelled RULE-BASED — not machine learning, not an LLM wherever it appears. (The legacy card name “AI Logistics Insight” is kept as a substring because the pre-existing E2E suite asserts it; the label always carries the rule-based qualifier. Renaming it is a one-line change plus that assertion.)
Screenshots (final build): docs/screenshots/ — each screen in Dark and Light.
- Training pipeline in
ml/: ISOMORPH benchmark -> chronological split -> baselines (existing EWMA, seasonal naive, last value, trailing mean) vs Random Forest vs LightGBM -> one-shot test evaluation -> versioned, checksum-verified artefacts -> scenario holdout and a pre-registered promotion rule. - Measured (details:
ml/reports/EVALUATION_REPORT.md): on the benchmark test split the trained model reaches 9.32% WAPE vs 11.12% for the best simple baseline and 11.83% for the existing statistical model (run without weather). On this application's own scenario the statistical model is better (4.00% vs 4.34% WAPE, days 1-14), so the trained model is not promoted and the default forecast remains statistical. Opt in withFORECAST_MODEL_MODE=ml. - Backend:
GET /api/ml/models,GET /api/ml/evaluation,POST /api/ml/shortage-risk(benchmark-format scoring);GET /api/forecastnow reports model id, family, generation time, data freshness, interval method and any fallback reason; every forecast row stores the model that produced it. A failed or missing model falls back visibly to the statistical model. - Frontend: Forecast page shows attribution, a labelled trained-model preview and the holdout comparison; new Model Evaluation page with benchmark/scenario/shortage tables, licences and limitations; keyboard-operable rows, skip link, accessible chart labels, page titles.
- Shortage-onset model: trained and evaluated on the benchmark (ROC-AUC 0.988) but deliberately not wired into inventory status: the scenario has no backlog or in-transit ledger and the probabilities are calibrated to ISOMORPH's replenishment policy.
- Not done / not claimed: no route-disruption model (no outcome labels exist), no accuracy claim for real operations,
Dataset_AI_Supply_Optimization.csvnot used for training (no dates/IDs; columns behave like independent uniform draws - seeml/DATA_CARD.md). - Audit:
python scripts/audit_terminology.pyscans user-facing sources for prohibited development terminology (also enforced bybackend/tests/test_terminology.py).
See docs/CHANGES_V7_1.md: Compose now separates the administrative, owner (migrations) and application database roles; a CLI crash under the restricted role, a shared-client-address problem behind load balancers, postgres:// URLs and a map error on fast navigation were fixed; build-tool advisories reduced from 9 to 5 (all braces, build-time only); Docker and Render remain unexecuted (blocked), with a full audit and commands in docs/DEPLOYMENT.md.
Built on the V6 archive (see docs/CHANGES_V7.md): corridor-closure feasibility rule, idempotent inventory events, saved-scenario comparison, least-privilege database role support, concurrency/conservation/integrity tests, a deterministic evaluator walkthrough (scripts/guided_walkthrough.py) and a complete document set: ARCHITECTURE · DEPLOYMENT · SECURITY_REVIEW · ML_EVALUATION_REPORT · DATA_DICTIONARY · LIMITATIONS_AND_ASSUMPTIONS · VERIFICATION_REPORT.
Evaluator walkthrough (needs a running stack and an admin account):
PLSD_URL=http://localhost:8080 PLSD_USER=<admin> PLSD_PASSWORD=<password> python scripts/guided_walkthrough.pyIncremental upgrade of V5 (nothing rebuilt): sign-in with viewer/operator/admin roles, CSRF protection, lock-out and rate limits, security headers and a strict CSP, development/production configuration profiles that refuse weak or missing secrets, non-destructive versioned migrations, atomic multi-step writes, strict input validation, layered health endpoints with model/fallback/data-coverage reporting, structured logs, an inventory-risk explanation (source, horizon, rules, limitations), and a much larger automated test suite. Details: docs/CHANGES_V6.md.
The shortage-onset model is not used for inventory status because the application has no daily on-hand history or in-transit/backlog ledger to feed it; why, and how to scrutinise that decision, is in docs/SHORTAGE_MODEL_DECISION.md.
Browser (React + Leaflet + Recharts) iot-simulator/simulator.py (simulated ESP32/RFID)
│ /api/* │ POST /api/iot/update
▼ ▼
nginx :8080 ───────────────────────────────► FastAPI :8000 (routers → services → repository)
(static SPA + /api proxy) │ pure engines: forecasting · inventory · terrain · routing
│ priority · allocation · simulation · recommendation · iot
▼
PostgreSQL 16 + PostGIS 3.4 :5432 (migrations, seed.sql)
- Business logic lives in the backend. The frontend only renders API responses (scenario sliders are sent as query parameters or to
POST /api/simulation/run). backend/app/services/*are pure functions over plain dataclasses (no DB) so they are unit-testable;repository.pyloads aSnapshotfrom PostgreSQL/PostGIS and persists derived data.
React 18 · Vite 5 · TypeScript · Tailwind CSS 3 · Recharts 2 · Leaflet 1.9 / React-Leaflet 4 (OpenStreetMap tiles) · FastAPI · Pydantic 2 · SQLAlchemy 2 · GeoAlchemy2 · psycopg 3 · scikit-learn (challenger) · PostgreSQL 16 + PostGIS 3.4 · Docker Compose · nginx · pytest · Playwright (e2e).
python scripts/generate_env.py # writes .env with fresh random secrets (3 database passwords, JWT secret, first admin) - never overwrites
docker compose up --build # db -> one-shot migrate -> backend -> frontend ; UI: http://localhost:8080- Sign in with the administrator printed by
generate_env.py, then removeBOOTSTRAP_ADMIN_PASSWORDfrom.env. There are no default credentials:docker compose upstops with a clear message if a secret is missing. - Database roles (V7.1):
plsd_adminonly initialises the volume (roles + PostGIS); the one-shotmigrateservice runs migrations as the non-superuser ownerplsd_owner, grants the data-only role and loads the fictional scenario into an empty database; the API runs asplsd_app(data access only, no owner credentials,AUTO_MIGRATE=0). The database and API are not published on the host. APP_ENVdefaults toproduction(Secure session cookie: works onhttps://and onhttp://localhost); for plain-HTTP use from another machine setAPP_ENV=development.- Not executed in the release environment: the images were not built and Compose was not started (no Docker; image registries unreachable). The Compose file passes the compose-spec schema and hadolint, its role design was executed with
scripts/local_stack.sh, and the exact commands to verify it on a Docker host are indocs/DEPLOYMENT.md. Seedocs/VERIFICATION_REPORT.md.
# 1. database
createdb logistics_dev && psql logistics_dev -c 'CREATE EXTENSION postgis'
export DATABASE_URL=postgresql+psycopg://USER:PASSWORD@localhost:5432/logistics_dev
export BOOTSTRAP_ADMIN_USERNAME=admin BOOTSTRAP_ADMIN_PASSWORD='<choose a long password>'
# 2. backend (APP_ENV defaults to development: random per-process JWT secret, API docs at /docs)
cd backend && python -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt
python -m app.cli migrate && uvicorn app.main:app --reload --port 8000
# 3. frontend (Vite proxies /api -> http://localhost:8000)
cd frontend && npm ci && npm run dev # http://localhost:5173Operational commands: python -m app.cli check-config | migrate | verify-schema | seed-scenario | grant-app-role ROLE | create-user NAME --role admin | set-password NAME | reset-database --confirm DB (see backend/app/cli.py).
pip install -r backend/requirements-dev.txt
# the database tests CREATE tables and users: they refuse any database whose name does not contain test, ci or scratch
createdb logistics_test && psql logistics_test -c 'CREATE EXTENSION postgis'
export DATABASE_URL=postgresql+psycopg://USER:PASSWORD@localhost:5432/logistics_test
cd backend && REQUIRE_DB=1 python -m pytest # release mode: an unreachable database FAILS the run instead of skipping
cd ml && PYTHONPATH=../backend python -m pytest # training/evaluation pipeline tests (needs ml/requirements-ml.txt)
python scripts/audit_terminology.py # user-facing terminology audit (add --dist after `npm run build`)
cd frontend && npm run typecheck && npm run build
# browser suites need a running stack (docker compose, or scripts/local_stack.sh) and an existing admin account:
APP_URL=http://localhost:8080 E2E_USER=<admin> E2E_PASSWORD=<password> AXE_JS=<path to axe.min.js> python tests/e2e/e2e_v6_security_workflows.pypip install playwright && playwright install chromium is needed once for the browser suites (tests/e2e/).
python backend/scripts/generate_seed.py # regenerate db/seed.sql (deterministic, seed 2026)
PLSD_USER=<operator> PLSD_PASSWORD=<password> python iot-simulator/simulator.py --count 10 --interval 0.5 # simulated scannerEnvironment variables: docs/CONFIGURATION.md (every variable, per profile).
├── docker-compose.yml db (PostGIS) · backend · frontend
├── db/ init/10-roles.sh (database roles + PostGIS, first start only) · migrations/ (0001 baseline · 0002 users+audit · 0003 integrity · 0004 event idempotency) · seed.sql (generated)
├── backend/
│ ├── app/{main,config,security,auth,migrate,observability,cli,database,models,schemas,repository,bootstrap,seed_data,serializers}.py
│ ├── app/routers/ gis · inventory · forecast · decision · iot · simulation
│ ├── app/services/ domain · forecasting · challenger · inventory · terrain · routing · priority
│ │ allocation · simulation · recommendation · explain · iot · status · engine
│ ├── scripts/ generate_seed.py · verify_v3_parity.py
│ └── tests/ 434 tests (+ golden_v3.json produced by the original v3 JS)
├── frontend/ React + Vite + TS + Tailwind (src/pages: Dashboard, Forecast, Gis, Inventory, Simulation;
│ src/components: CommandBriefing, WhyRecommendation, RouteSelection, ModelRoles,
│ RecommendedResponse, IotFlow, LogisticsMap…; src/theme.tsx: Dark/Light)
├── iot-simulator/simulator.py stdlib-only simulated ESP32/RFID client
├── tests/e2e/ e2e_v6_security_workflows.py (60) · e2e_v7_workflows.py (5) · e2e_v71_gis_resilience.py (14) · e2e_playwright.py (73) · e2e_ui_enhancements.py (92) · e2e_v4_polish.py (38)
├── docs/ ARCHITECTURE · DEPLOYMENT · SECURITY_REVIEW · ML_EVALUATION_REPORT · DATA_DICTIONARY · LIMITATIONS_AND_ASSUMPTIONS · VERIFICATION_REPORT · CHANGES_V7_1/V7/V6 · CONFIGURATION · SHORTAGE_MODEL_DECISION · TROUBLESHOOTING · DEPENDENCIES
- nginx resolves the backend per request (
resolver 127.0.0.11+ variable upstream), so the frontend container starts and serves the UI even if the backend is down, restarting or not yet created;/api/*returns 502 and the UI shows "Backend unavailable" with Retry. A recreated backend with a new IP is picked up without restarting nginx./healthzreports nginx liveness only. - Compose:
dbhealth uses TCP (pg_isready -h 127.0.0.1) so it cannot report ready during the image's first-run init;backendwaits for a healthydb;frontendonly waits for the backend to start (not to be healthy), so a slow backend never abortsdocker compose up; services userestart: unless-stopped. - Backend: if the first bootstrap fails (database unreachable/not yet seeded), it keeps retrying in the background every 5 s until it succeeds — no manual restart.
/api/healthreturns 503degradedmeanwhile.
Versioned migrations in db/migrations/ (all tables have keys/constraints; PostGIS extension enabled; never destructive; checksum-protected):
supplies · locations (PostGIS Point,4326, GiST index) · depots · vehicles · routes (PostGIS LineString,4326, GiST) · terrain (generated column terrain_risk) ·
weather · consumption (5,500 rows) · inventory (forward + depot stock, safety stock) · forecasts · alerts · priority_scores · allocations · iot_events · simulation_runs, plus view v_consumption_enriched.
The backend genuinely uses the database: forecasts are computed from consumption + weather and stored in forecasts; inventory, routes, terrain and vehicles are read from their tables;
coordinates/polylines are read via ST_X/ST_Y/ST_AsGeoJSON; route polyline length is measured by PostGIS (ST_Length(geography), shown on the route card); baseline alerts, priority scores and allocations are persisted and refreshed on start-up and after every IoT event; simulation runs and IoT events are stored.
Simulated scenario data (seed 2026, deterministic): 3 depots, 5 forward locations, 10 supply types, 8 routes, 8 vehicles (one in maintenance), 140 weather days (110 observed + 30 outlook), 5,500 consumption records (5 × 10 × 110).
Relationships: demand = base × location factor × slow trend × weather effect (per-supply sensitivity) × weekly cycle × noise; stock levels are set relative to forecast demand; lead time derives from route ETA, which derives from distance, road, weather and terrain.
Coordinates are arbitrary points in a made-up grid (Base_01 (fictional) etc.).
Scenario parameters (demand 0–0.5, weather 0–1, road 0–1, vehicles 0.5–1) can be added as query parameters to every scenario-aware GET.
| Method & path | Purpose |
|---|---|
GET /api/health |
API + DB status (503 degraded if the DB is down) |
GET /api/locations, /api/locations/{id} |
Depots/forward locations with risk status; detail incl. items, Why-at-risk, insight, routes |
GET /api/inventory (location,supply,risk) · /api/inventory/projection |
Stock, days of supply, shortage, status · 14-day projection |
GET /api/forecast (location,supply,horizon) · /api/forecast/challenger · POST /api/forecast/recompute |
Forecast, backtest, confidence · RF challenger · recompute |
GET /api/alerts |
Alerts (baseline from the alerts table; scenario derived live) |
GET /api/vehicles · /api/routes · /api/routes/compare?destination= |
Fleet · routes with terrain/ETA/risk · alternatives + reason |
GET /api/priority · /api/priority/weights · /api/allocation · /api/insights · /api/dashboard |
Priority queue · weights · allocation plan · recommendations/Why-at-risk · KPIs/system status/pipeline |
POST /api/simulation/run · GET /api/simulation/runs |
What-If run (stored) · history |
GET /api/iot/events · /api/iot/feed · POST /api/iot/update · /api/iot/simulate · /api/iot/reset |
Simulated IoT |
Interactive docs at /docs.
Statistical time-series model (not a trained ML model), deterministic: history is de-weathered and de-seasonalised, smoothed with an EWMA (α = 0.2), extended with a damped linear trend (0.5 × recent 7-day slope), then re-weathered with the synthetic 30-day weather outlook and re-seasonalised (weekly cycle).
Weather multiplier = 1 + sensitivity × 0.6 × weather_index. Backtest: rolling one-step-ahead over the last 60 days → MAPE, RMSE, coefficient of variation, interval coverage (±1.28 σ). Confidence = 100 − MAPE × (1 + horizon/30) (a stated heuristic).
Random Forest challenger (scikit-learn, 20 trees, depth 6, seeded) is trained on days 1–90 and validated on days 91–110; on this data it scores ≈ 4.8 % hold-out MAPE vs ≈ 3.7 % for the statistical model. It is a cross-check only and does not drive any dashboard number.
Caveat: the scenario history is generated from the same weather/weekly structure the model assumes, so the low error is expected and says nothing about real-world accuracy.
- Days of supply = stock ÷ forecast daily demand (first 7 forecast days, scenario-adjusted).
- Predicted shortage (14 d) = max(0, demand × 14 − stock). Stock-out date = sim-clock + days of supply.
- CRITICAL (red): cover < 3 d or < lead time + 1 d · WARNING (yellow): cover < lead time + 3 d or stock < safety stock · else NORMAL (green).
- Replenishment quantity = safety + demand × (lead + 5 d) − stock, rounded to 50-unit loads.
- Location status: ≥ 2 critical items → CRITICAL; ≥ 1 critical or ≥ 3 warning → WARNING.
- Overall risk index (0–100) =
100 × min(1, 2.2·crit/N + 0.8·warn/N + 0.3·mean route risk + 0.2·capacity-deficit share). - Why at risk: per location, the top items with demand change vs the last 7 days, stock vs safety stock, cover vs lead time, best-route risk and weather index, as a plain-language summary.
- Terrain risk =
0.7 × difficulty + 0.3 × elevation_band/3(fictional bands 0–3, e.g. "High-altitude ridge"). - Effective speed =
nominal × (1 − 0.45·road) × (1 − 0.35·terrain risk) × (1 − 0.3·weather risk)(floor 8 km/h); ETA = distance ÷ effective speed. Weather risk on a route = weather × (0.5 + 0.5·terrain risk), so terrain amplifies weather. - Route risk =
0.25 road + 0.25 weather + 0.30 terrain + 0.20 distance factor. - Route selection: combined score = route risk + ETA/24 h + capacity penalty (≤ 0.2, grows when the depot's available vehicle capacity is below the quantity to move). A route is feasible only if its depot has an available vehicle.
GET /api/routes/comparereturns all routes, scores, SELECTED/ALTERNATIVE/NO VEHICLE and a written reason. - Leaflet + OpenStreetMap map: status-coloured markers (normal / warning / critical), square depot markers, risk-coloured route lines (dashed = poor road), a toggleable terrain band layer, popups with inventory, shortage, ETA, terrain, weather and capacity. If tiles cannot load, a notice appears and the overlay keeps working on a plain background.
- Tile provider (v7.1 fix): The nginx
Referrer-Policyheader is set tostrict-origin-when-cross-origin(the browser default). The previousno-referrersetting suppressed the HTTPRefererheader, causing OSM tile servers to return 403 Access Blocked (the OSM tile usage policy explicitly requires a valid Referer from web pages). The tile URL and attribution are configurable at build time viaVITE_TILE_URL/VITE_TILE_ATTRIBUTION; the default is the OSM standard tile server (https://tile.openstreetmap.org/{z}/{x}/{y}.png). OSM tiles are a best-effort volunteer service with no SLA — for a production deployment use a commercial provider (Stadia Maps, Jawg, Mapbox) and document the provider's API key as a Dockerfile build ARG; see.env.examplefor details.
- Tile provider (v7.1 fix): The nginx
Priority score (0–100) = 100 × (0.30 shortage urgency + 0.15 shortage volume + 0.15 stock deficit + 0.15 supply criticality + 0.10 lead-time pressure + 0.10 route feasibility/risk + 0.05 transport capacity). Weights are fixed, documented prototype assumptions (GET /api/priority/weights).
Allocation walks the queue in priority order; for each item it tries routes in combined-score order, ships at most the depot's actual stock of that supply (depot stock is a finite synthetic quantity, ≈ 8 days of network demand) and loads vehicles within effective vehicle capacity (smallest sufficient vehicle first). Unmet quantity is reported with its binding constraint (VEHICLE CAPACITY, DEPOT STOCK, NO FEASIBLE ROUTE).
The per-item "AI Logistics Insight" is a stand-alone, deterministic rule-based recommendation (no external AI model) assuming full fleet availability; the allocation plan resolves vehicle contention across all items, so the vehicle shown can differ — the UI/API say so.
iot-simulator/simulator.py (or the UI scanner, or POST /api/iot/simulate) emits simulated ESP32/RFID scans: RECEIVE (+), ISSUE (−, never below 0), COUNT (set). Each goes through POST /api/iot/update → validated → stored in iot_events → inventory updated → alerts/priority/allocation refreshed → dashboard recalculated. Every response and UI panel is labelled simulated; the simulated column is constrained to true. No hardware is involved.
Controls: demand +0–50 %, weather severity, road condition, vehicle availability 50–100 %. POST /api/simulation/run recomputes the baseline and the scenario and returns before/after: critical/warning items, shortage-risk locations, expected shortage, transport capacity/need/deficit, mean route risk, mean ETA delay, overall risk index, allocated/unmet units, per-location status and per-route ETA/risk, plus generated explanations (including which route terrain slows most). The run is stored in simulation_runs. "Apply to all screens" sets the scenario for every screen.
- Toggle Light/Dark in the sidebar (Dark is default; the choice persists across reloads).
- Dashboard — the Command Briefing shows baseline risk 62/100 (HIGH), critical locations Base_03 & Base_04, 1,44,595 units predicted short, transport 34,000 vs 41,800 needed, 8 critical / 6 warning alerts and the top 3 actions; pick any priority in Why this recommendation?; then map, priority queue, allocation, insight, Why-at-risk, system status.
- GIS — click Base_04 → route comparison (R03 selected, R08 alternative, reason shown); click route R07 → terrain/elevation/effective speed.
- Forecast — switch 7/14/30 days; filter Base_04 / Medical; read methodology and the RF challenger.
- Inventory — filter by risk; Send scan
RECEIVE Base_04/Medical 2000→ status turns NORMAL, header and dashboard update (critical 8 → 7, risk 62 → 57). Reset inventory restores the seed. - Simulation — demand +30 %, severe weather, poor roads, 60 % fleet → risk 62 → 93, capacity 34,000 → 20,400; read the explanations; Apply to all screens and revisit Dashboard/GIS.
Exact commands and the results of the V6 verification run are in docs/VERIFICATION_REPORT.md, separated into what was executed, what was verified only statically, and what could not be verified.
- Backend: 434 tests, all against FastAPI + PostgreSQL/PostGIS: configuration profiles, security primitives, authentication/RBAC/CSRF/lock-out, migrations (fresh, repeatable, adoption, rollback, integrity), API validation and transactions, ML feature parity and artefact integrity, health and logging, deployment-file validation, terminology.
- ML pipeline: 14 tests (
ml/tests): chronological splits, purging, leakage and metric correctness of the training/evaluation code. - Browser: 282 checks (281 passed, 1 deliberately skipped) in six Playwright suites, run against the production profile behind the real nginx configuration (including a WCAG 2 A/AA axe scan).
- v3 parity: the Python engine is compared with
backend/tests/golden_v3.json, captured from the original v3 JavaScript. The original page is not shipped in V7 (backend/scripts/verify_v3_parity.pyexplains how to regenerate the fixture).
See also docs/SECURITY_REVIEW.md and docs/VERIFICATION_REPORT.md.
- Simulated scenario data only. Findings are properties of the generator, not of any real supply chain. Forecast accuracy in particular is flattering because the data and the model share assumptions.
- All formulas, thresholds, priority weights and the capacity penalty are documented assumptions, not calibrated against real operations.
- The recommendation engine is rules-based; it is not an AI/LLM and does not learn.
- Routes are drawn as simple fictional polylines; "distance" is a seeded number, not computed from road networks. No real elevation or weather data is used.
- IoT is simulated end-to-end; no device authentication, no MQTT, no real RFID/ESP32 firmware.
- Authentication, roles and an audit trail exist (V6) but the application has not had an independent security review or penetration test; there is no encryption at rest, no multi-factor sign-in, no device authentication for the simulated scanner, and rate limits are per process. See
docs/SECURITY_REVIEW.md. - Allocation is a greedy, priority-ordered heuristic, not an optimal solver; vehicles serve only their own depot's routes.
- The Docker images,
docker compose upand the Render blueprint were not built or run in V6, V7 or V7.1 (no Docker, registries unreachable, no Render access); the same application settings, migrations, role design and nginx template were exercised without containers instead (scripts/local_stack.sh). - The simulation clock is anchored at 2026-06-19 06:00 UTC and runs at 60× for display only.
- The bundle is ~760 kB (no code splitting).