factcircuit
FactCircuit: an auditable claim-and-evidence verification loop with immutable provenance, staged checks, bounded retrieval, and reproducible evaluation.
Caspian Sea oil spill intelligence — satellite detection, AI analysis, and operator response.
Translation pending
DataTypeScript · PythonGitHub @arizazadli20OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
Satellite & AI oil spill intelligence for the Caspian Sea
SeaSentry is a full-stack operational platform for detecting, reviewing, and
responding to potential oil spills. Satellite (SAR) imagery is analyzed for
dark-signature slicks, an AI layer estimates area/risk/probable cause, and a
human specialist always makes the final call — confirm, reject, escalate, or
approve a cleanup response. Everything from detection to PDF report lives in
one workspace.Read the full README ↗Satellite & AI oil spill intelligence for the Caspian Sea
SeaSentry is a full-stack operational platform for detecting, reviewing, and responding to potential oil spills. Satellite (SAR) imagery is analyzed for dark-signature slicks, an AI layer estimates area/risk/probable cause, and a human specialist always makes the final call — confirm, reject, escalate, or approve a cleanup response. Everything from detection to PDF report lives in one workspace.
/docs)main,
synced from https://github.com/Kanan-peoiks/Pelagos, is what Render deploys —
see Deployment for why two repos are involved)Status: this is a working demo with a real backend, real database, and real auth — not a static mockup. The one piece still pending is the ML detection model itself (see Roadmap); everything around it (review workflow, response calculations, admin tooling) is fully built and live.
viewer, operator, or
admin (see Roles & permissions).detectionSource: "Manual report") — useful standalone today, and a
stand-in for the ML pipeline until it's wired up./admin, admin role only)| Layer | Technology |
|---|---|
| Frontend framework | Next.js 16 (App Router, Turbopack disabled — see AGENTS.md), React 19, TypeScript |
| Frontend data/UI | Recharts (charts), Leaflet + react-leaflet (map), jsPDF (report generation), Lucide icons |
| Backend framework | FastAPI (Python), Pydantic v2, SQLAlchemy 2.x |
| Auth | python-jose (JWT), passlib/bcrypt (password hashing) |
| Database | PostgreSQL (Neon serverless) in production, SQLite fallback for local dev |
Gmail SMTP via an app password (stdlib smtplib, no third-party API) | |
| Hosting | Vercel (frontend), Render (backend, via a render.yaml Blueprint) |
| Live external data | Open-Meteo (wind/sea state, no key), Copernicus Data Space Ecosystem / Sentinel Hub (real Sentinel-1 SAR tile fetch, backend-side — see app/satellite.py) |
Browser
│
│ same-origin only — the browser never talks to Render directly
▼
Next.js app (Vercel)
├─ Public pages: / , /login , /register , /forgot-password , /reset-password
├─ proxy.ts — cookie-based route guard (Next.js 16's renamed
│ "middleware"; redirects unauthenticated users to
│ /login, redirects authenticated users away from
│ the auth pages)
├─ Protected AppShell pages: dashboard, incidents, vessels, ai-analysis,
│ response, reports, account, admin
└─ app/api/* — Route Handlers that proxy to the backend,
forwarding the httpOnly JWT cookie as a Bearer
token. This is the only thing that talks to
Render — it sidesteps CORS entirely and keeps
the backend URL and the raw JWT out of the
browser.
│
▼
FastAPI backend (Render)
├─ /auth/* — register, login, demo, forgot/
│ reset password, me
├─ /incidents/* — list, get, create, decision
├─ /reports/* — save/list generated PDF snapshots
├─ /feedback/* — submit, list, resolve, reply
├─ /admin/* — user list, role changes, stats
├─ /detect — ML stub (503 today)
└─ /health
│
▼
Postgres (Neon, serverless)
Why two repos are involved: the frontend (Vercel) and backend (Render)
are configured against two different GitHub remotes —
arizazadli20/Pelagos and the fork Kanan-peoiks/Pelagos — for
account/ownership reasons unrelated to the code itself. Render watches the
fork's main, which is kept caught up via GitHub's "Sync fork" button
after each merge to arizazadli20/Pelagos:main. If backend changes aren't
showing up live, check whether the fork has been synced.
Pelagos/
├── app/
│ ├── page.tsx # Public landing page
│ ├── login/ register/ # Auth pages
│ ├── forgot-password/ reset-password/
│ ├── dashboard/ incidents/ vessels/ ai-analysis/ response/ reports/
│ ├── account/ admin/
│ └── api/ # Route Handlers (proxy to the backend)
│ ├── auth/{login,register,logout,demo,forgot-password,reset-password}/
│ ├── incidents/ incidents/[id]/ incidents/[id]/decision/
│ ├── reports/
│ ├── feedback/ feedback/[id]/ feedback/[id]/reply/
│ ├── admin/{stats,users,users/[id]/role}/
│ └── detect/ # Proxies the "check for new imagery" scan to the backend
├── components/
│ ├── AppShell.tsx # Auth gate + header/sidebar + loading state
│ ├── MapPanel.tsx # Leaflet map (markers, slick polygons, source pin)
│ ├── landing/LandingPage.tsx
│ ├── auth/AuthForm.tsx
│ ├── incidents/ # Detail panel, manual-report form, charts
│ └── ui/ # Shared primitives (DetailPanel, StatCard, …)
├── lib/
│ ├── auth.ts # Client auth helpers (role checks, login/register/…)
│ ├── incident-store.tsx # Shared React context — incidents, decisions, live sim
│ ├── spill-physics.ts # Drift/source-back-calculation, sorbent math
│ ├── useSpillSourceEstimate.ts # Hook wrapping weather + spill-physics
│ ├── weather.ts # Open-Meteo wind/sea-state client
│ ├── mock-data.ts # Still-mock domain data (vessels, risk zones, activity)
│ ├── types.ts # Shared domain types
│ ├── server/backend.ts # Server-only helper: backend URL + auth-header forwarding
│ └── nav.ts
├── proxy.ts # Route guard (formerly middleware.ts)
└── backend/
├── app/
│ ├── main.py # FastAPI app, CORS, router registration
│ ├── config.py # Settings (env vars)
│ ├── database.py # Engine/session, sqlite↔postgres URL normalization
│ ├── models.py # SQLAlchemy models
│ ├── schemas.py # Pydantic request/response shapes (camelCase aliases)
│ ├── security.py # JWT + bcrypt
│ ├── deps.py # get_current_user / require_operator / require_admin
│ ├── rate_limit.py # In-memory login rate limiter
│ ├── email_util.py # SMTP sender + bilingual email templates
│ ├── seed.py # Seeds 2 demo incidents
│ └── routers/
│ ├── auth.py incidents.py reports.py feedback.py admin.py detect.py
├── render.yaml # Render Blueprint
├── ML_INTEGRATION.md # Contract + recommendations for the ML teammate
└── requirements.txt
render.yaml — newer versions may
lack prebuilt wheels for some backend dependencies)cd backend
python -m venv .venv
.venv\Scripts\activate # Windows
# source .venv/bin/activate # macOS/Linux
pip install -r requirements.txt
cp .env.example .env # fill in DATABASE_URL, or leave blank for SQLite
python -m app.seed # optional: adds 2 demo incidents
uvicorn app.main:app --reload
API docs (Swagger UI): http://localhost:8000/docs.
npm install
cp .env.example .env.local
# set BACKEND_URL=http://localhost:8000 in .env.local
npm run dev
Open http://localhost:3000. npm run build / npm start for a production
build; both dev and build use the --webpack flag (Turbopack currently
panics on this project's PostCSS setup).
/ → click Continue as guest to see the dashboard instantly
(viewer-only — no write actions), or Register for a full account./reports to see it in the report history table.ADMIN_EMAILS, visit /admin to manage
users and reply to feedback..env.local)| Variable | Required | Notes |
|---|---|---|
BACKEND_URL | Yes | The FastAPI backend's base URL — http://localhost:8000 locally, the Render URL in production. Never exposed to the browser; only read inside Route Handlers. |
Copernicus/Sentinel Hub credentials now live on the backend instead (see below) — the real SAR tile fetch happens there, not in a frontend Route Handler.
backend/.env)| Variable | Required | Notes |
|---|---|---|
DATABASE_URL | No | Postgres connection string. Omit for a local SQLite file (sqlite:///./seasentry.db). |
JWT_SECRET | Recommended | Signs auth tokens — generate with python -c "import secrets; print(secrets.token_hex(32))". Auto-generated on Render. |
JWT_ALGORITHM | No | Defaults to HS256. |
ACCESS_TOKEN_EXPIRE_MINUTES | No | Defaults to 1440 (24h). |
CORS_ORIGINS | Yes in production | Comma-separated allowed origins. Note: since the frontend proxies through Route Handlers rather than calling the backend from the browser, this mostly matters for direct/manual API access, not the app itself. |
ADMIN_EMAILS | No | Comma-separated emails auto-promoted to admin on register/login — the bootstrap mechanism for the first admin account. |
SMTP_HOST / SMTP_PORT / SMTP_USER / SMTP_PASSWORD | For email features | Gmail SMTP + an app password (requires 2-Step Verification on the account). If unset, emails are logged instead of sent — the flow still works end-to-end for local testing. |
FRONTEND_URL | For email features | Used to build the link inside password-reset emails. |
REQUIRE_ADMIN_2FA | No | Gates the built email-code 2FA challenge on admin login. Defaults false so a broken email sender can never lock admins out. |
SLACK_WEBHOOK_URL | For Slack alerts | Incoming Webhook URL — posts an alert when a new HIGH-risk incident is created. Unset = silently skipped. |
COPERNICUS_CLIENT_ID / COPERNICUS_CLIENT_SECRET | For /detect | Copernicus Data Space Ecosystem OAuth client (dataspace.copernicus.eu → Sentinel Hub → OAuth clients) — used by app/satellite.py to fetch real Sentinel-1 SAR tiles. Unset = returns a clear 502 instead of faking a result. |
| Role | Can view | Can create/decide incidents, generate reports | Can manage users |
|---|---|---|---|
viewer (default for new registrations, and always for the demo account) | ✅ | ❌ | ❌ |
operator | ✅ | ✅ | ❌ |
admin | ✅ | ✅ | ✅ |
New self-registrations start as viewer — an existing admin has to promote
them to operator from /admin. This is deliberate: not every registered
account should be able to act on real incidents, only vetted team members.
The one exception is bootstrapping — any email listed in ADMIN_EMAILS
becomes admin automatically the moment it registers or logs in.
| Table | Purpose |
|---|---|
users | Accounts — role, is_demo, bcrypt password_hash. |
incidents | Mirrors the frontend's Incident type field-for-field (camelCase JSON via a Pydantic alias generator). |
reports | A snapshot of each generated PDF report (materials, cost, who/when). |
feedback | Feedback/suggestion/question submissions, resolved flag, admin_reply/replied_at. |
login_events | One row per successful register/login/demo-login — powers the admin panel's usage stats. |
password_reset_tokens | Single-use, 30-minute-expiry reset tokens. |
There's no migration framework (no Alembic) — schema changes are additive
ALTER TABLE ... ADD COLUMN IF NOT EXISTS statements run once by hand
against the shared database. Base.metadata.create_all() in main.py
handles brand-new tables automatically; it does not add columns to existing
tables.
arizazadli20/Pelagos:main.backend/render.yaml, deploying
Kanan-peoiks/Pelagos:main (see the sync note in
Architecture). Free tier — the service spins down after
~15 minutes idle and takes 30–50s to wake on the next request; hit
/health a minute before a live demo to warm it up.To ship a change: push to feature/kenan-api → open a PR into
arizazadli20/Pelagos:main → merge → Sync fork on
Kanan-peoiks/Pelagos so Render picks it up. Vercel needs no equivalent
step; it deploys straight from the first repo.
lib/mock-data.ts) — no live AIS feed is integrated. Flagged as
out-of-scope for the current backend unless a specific need comes up.ImagePlaceholder in IncidentDetailsPanel.tsx) —
the real tile fetch now exists (app/satellite.py, used by POST /detect) but its output isn't rendered as an on-screen image anywhere
yet, only analyzed.POST /detect's confidence score is a classical CV heuristic, not a
trained model — real Sentinel-1 pixels, real threshold/blob analysis
(app/spill_detect.py), but not machine-learned. See
backend/ML_INTEGRATION.md for swapping in a real model.backend/ML_INTEGRATION.md
documents the exact contract, a recommended detection approach (SAR
dark-spot segmentation, fine-tuning a public dataset rather than training
from scratch), an infrastructure recommendation (keep it light enough for
Render's free tier, or split heavy inference into its own service), and a
suggested trigger model (a manual "check for new imagery" button first,
before an automatic scheduler).arizazadli20/Pelagos directly instead of the synced fork, to remove the
manual "Sync fork" step.DATABASE_URL, JWT_SECRET, SMTP_PASSWORD, etc.) live
only in gitignored .env files locally and in Render/Vercel's dashboard
env-var UI in production — never in git, even in render.yaml (secrets
there are marked sync: false, meaning "set this by hand")./detect