SafeEdge Intelligence is an industrial safety platform that fuses IoT/SCADA telemetry, digital work permits, and maintenance records into a single compound risk score per plant zone. The problem it targets: heavy industrial accidents in India (DGFASLI recorded 6,500+ fatal workplace accidents in FY2023) repeatedly follow the same pattern — a sub-threshold sensor reading that becomes genuinely dangerous only in combination with something else happening at the same time (an active hot-work permit, ongoing maintenance), which a single-sensor threshold alarm has no way to see. SafeEdge's pitch is contextual evaluation instead of more sensors: multiply a gas reading's risk by the active permit's hazard multiplier and add a maintenance floor risk, and a legacy-invisible reading becomes a CRITICAL alert — before a threshold-only system would ever respond.
See ARCHITECTURE.md for full system diagrams; this manual covers day-to-day use.
- Postgres (with the
safeedgedatabase created) - Python 3.14 + the venv in
backend_fastapi/.venv - Node.js (for the frontend)
- A Gmail account with an App Password, if you want CRITICAL alert emails (optional)
# Backend — http://localhost:8000
cd backend_fastapi
source .venv/bin/activate
uvicorn app.main:app --reload --port 8000
# Frontend — http://localhost:5173 (separate terminal)
cd frontend_dashboard
npm run devInteractive API docs: http://localhost:8000/docs
This is the single most important thing to understand before using or extending anything here.
| System 1: Weighted Contextual Scoring Model | System 2: ai_engine |
|
|---|---|---|
| What it is | The platform's actual pitch — a deterministic formula | A separately-authored 0–100 point-weighted rule engine + trained classifier |
| Formula | combined_score = min(1, a_score × b_multiplier + c_floor) |
Additive points per triggered condition, capped at 100 |
| Inputs | LEL gas %, active permit hazard multiplier, maintenance overlap | Gas/temp/pressure levels, fire/smoke/PPE flags, alarms, maintenance |
| Decision bands | ≥0.80 CRITICAL · ≥0.50 WARNING · else MONITOR | ≥80 CRITICAL · ≥60 HIGH · ≥30 MEDIUM · else LOW |
| Where you see it | GET /api/v1/risk-events, the resource dashboard |
POST /api/v1/ai/predict/{zone_id}, the AI Risk Prediction page |
| Is it live? | No — computed once by a generator script, static history | Yes — recomputed on every request from current DB state |
Known gap: System 2 only recognizes the literal vocabulary metric="GAS",
event_type="FIRE"/"SMOKE"/"PPE_VIOLATION". The original seed dataset uses
metric="LEL_PCT" and event_type="FIRE_SMOKE" — so realistic seed telemetry
never moves System 2's score. If you want System 2 to react, insert data
using its exact vocabulary (see data_simulation/simulate_live_feed.py and
the POST /api/v1/ingest/{zone_id} endpoint, which both do this correctly).
Every one of the 17 backend tables has a browsable page: sidebar → Core / Extended / SCADA sections. Each page supports:
- Filtering (per-resource fields, e.g.
zone_id,decision,since/until) - Pagination
- Create / Edit / Delete — only on
zones,iot-sensors, andworkers(the rest are read-only by design; seeARCHITECTURE.md§4 for why)
No manual zone selection — it auto-fetches every zone on load and every 5 seconds, rendering one card per zone:
- Risk score + colored risk-level pill (LOW/MEDIUM/HIGH/CRITICAL)
- 🚨 Alarm: true (pulsing red) when risk level is HIGH or CRITICAL, else a quiet "Alarm: false"
- Mail status — shown only when CRITICAL: "✓ sent" or an explanation if it wasn't
- Reasons list (why the score is what it is)
- Last-updated timestamp
data_simulation/simulate_live_feed.py acts as an HTTP client simulating a
real edge gateway — it generates realistic telemetry (mostly quiet, with
occasional randomized spikes) and POSTs it to POST /api/v1/ingest/{zone_id}
once per second by default, which stores the reading and immediately
re-runs the AI engine on it.
# Basic run — all 6 zones, default 1s interval, ~8% chance of a spike per tick
python3 data_simulation/simulate_live_feed.py
# Faster spikes, just two zones, stop after 2 minutes
python3 data_simulation/simulate_live_feed.py --zones ZONE_C,ZONE_F --spike-probability 0.25 --duration 120
# Reproducible run
python3 data_simulation/simulate_live_feed.py --seed 42Leave the /ai-prediction frontend page open in a browser while this runs —
cards will update within 5 seconds of a spike landing.
Manual single-shot alternative (no script, just curl):
curl -X POST http://localhost:8000/api/v1/ingest/ZONE_C \
-H "Content-Type: application/json" \
-d '{"gas_level": 85, "fire_detected": true, "alarm": true}'When POST /api/v1/ai/predict/{zone_id} (or /ingest/{zone_id}, which calls
it internally) returns risk_level: "CRITICAL", it emails the configured
recipient with zone, score, reasons, and recommendations — at most once per
5 minutes per zone (a cooldown prevents alert spam from repeated polling).
Setup (backend_fastapi/.env):
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USERNAME=your-gmail-address@gmail.com
SMTP_PASSWORD=your-16-char-app-password # NOT your regular Gmail password
ALERT_EMAIL_TO=recipient@example.com
Requires a Gmail App Password (Google Account → Security → enable 2-Step Verification → myaccount.google.com/apppasswords). Leave these blank to disable alerting entirely — the system just logs and skips, no crash.
A weekly cron job (crontab -l to see it) runs
backend_fastapi/app/scripts/cleanup_ai_predictions.py every Sunday 2 AM,
deleting compound_risk_event rows that are (a) System-2-generated
(c_floor = 0.0, a value System 1 never produces) and (b) older than 7
days. It never touches System 1's historical dataset. Run it manually any
time with:
cd backend_fastapi && source .venv/bin/activate
python -m app.scripts.cleanup_ai_predictionsBase URL: http://localhost:8000/api/v1
| Purpose | Endpoint |
|---|---|
| Browse any of 17 tables | GET /{resource} (e.g. /zones, /risk-events, /sensor-readings) — supports ?skip=&limit= + per-resource filters |
| Everything in one call | GET /dataset/all?zone_id= |
| Live AI prediction | POST /ai/predict/{zone_id} |
| Ingest live telemetry | POST /ingest/{zone_id} |
| Manage zones/sensors/workers | POST / PATCH / DELETE on /zones, /iot-sensors, /workers |
| Health | GET /health, GET /health/db |
Full endpoint table with filters: backend_fastapi/README.md.
| Symptom | Cause | Fix |
|---|---|---|
Address already in use on uvicorn startup |
Backend already running | It's fine — check http://localhost:8000/health, don't restart unless you need --reload |
| A zone never leaves LOW/MEDIUM no matter what seed data exists | Vocabulary gap (§4) | Use /ingest/{zone_id} or the simulator, which use the correct vocabulary |
| Can't delete a zone/sensor/worker | 409 Conflict — other rows still reference it via foreign key |
Delete the dependent rows first (or it's working as intended — don't force it) |
| No CRITICAL email arrives | SMTP not configured, or the 5-minute cooldown already fired for that zone | Check the backend log for SENT/SKIPPED/FAILED; see §7 |
SQLAlchemy/asyncpg TypeError on startup |
Wrong SQLAlchemy version for Python 3.14 | pip install -U "sqlalchemy[asyncio]" (requirements.txt is already pinned to a working version — this only recurs if something reinstalls an older one) |