尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證製作者的介紹僅有上面的一句話。 更多詳情請閱讀儲存庫 README。 儲存庫 README ↗
正在載入完整 README。
檢視完整 README ↗One platform for many oil & gas and maritime clients. Something happens at a site, the right person finds out straight away, and a clean record is kept.
drone / camera / sensor / WhatsApp / phone / person
│ events and messages
▼
┌───────────────────────────────────────────────────────────┐
│ CORE clients · sites · assets · contacts · event types │
│ rules ("if this happens, do that") · alerts │
│ work orders · notifications · tamper-evident audit │
└───────────────────────────────────────────────────────────┘
│ │
alerts by SMS, WhatsApp, email OPS ASSISTANT module
work orders, evidence trail answers drivers and truckers in
EN/ES/PT from approved answers,
hands everything else to a person
It came out of the pain-point research (Oil & Gas and Maritime, US vs LATAM). All six pains share one shape: event → right person told → record kept. So this is one engine that each client configures, not six apps.
| Research pain | Covered by |
|---|---|
| O&G 1: safety and compliance at remote sites | Event types, rules, alerts, notifications, audit trail |
| O&G 2: visibility over remote assets | Sites and assets, CSV import, measurement events |
| O&G 3: workforce gap, after-hours coordination | Ops Assistant (WhatsApp/SMS, EN/ES/PT) |
| Maritime 1: congestion, status questions on disruption days | Ops Assistant plus gate and queue events; repeated events merge into one alert |
| Maritime 2: yard safety | Near-miss events, rules, work orders |
| Maritime 3: compliance, cyber, evidence | Hash-chained audit log with a verify endpoint |
Built and tested: the backend API, database, background worker and Ops Assistant module. All automated tests pass (the PostgreSQL-only ones are skipped on SQLite); lint is clean.
Loops the worker closes (run python -m app.worker next to the API):
notification.failed.Demo screens (frontend/): a Next.js app with one live scenario per
research pain, run against this API, plus an operations console per demo
client (alerts, work orders, conversations, audit trail, setup). See
frontend/README.md.
Not built yet:
/docs.Checked against PostgreSQL 17: the whole suite passes, including the row-level security tests, with the API connecting as a role that does not own the tables and, separately, as a single non-superuser owner role (the way Neon works on Vercel). See Testing.
| Setting | Example |
|---|---|
| Sites and assets (form or CSV upload) | PAD-07, GATE-A, tank batteries, cranes |
| Event types and the fields they carry | gas_detection with gas (choice) and ppm (number, min 0) |
| Rules | if attributes.ppm >= 10 and asset.criticality >= 4, then critical, SMS + email the safety_lead, open an urgent work order due in 2 h |
| Contacts and roles | who is safety_lead, on_call_coordinator, yard_supervisor |
| Language and units | EN/ES/PT per person; imperial or metric per client |
| Modules | Ops Assistant on or off |
| Assistant answers | approved text per language, optionally per site |
New clients start from an industry starter pack (event types and rules for oil & gas or maritime) that they can edit or switch off. Packs never create assistant answers: an invented gate time sent to a real driver is worse than "a person will reply".
Needs Python 3.12+.
cd backend
python -m venv .venv
.venv/Scripts/python -m pip install -e ".[dev]" # macOS/Linux: .venv/bin/python
.venv/Scripts/python -m app.cli migrate
.venv/Scripts/python -m app.cli seed-demo # four invented demo clients
.venv/Scripts/python -m uvicorn app.main:app --reload
Open http://127.0.0.1:8000/docs. seed-demo prints the demo logins and API keys:
All four have the assistant on and use the password demo-password-1234.
Try it:
# A gas sensor reports 18 ppm H2S on a tank battery
curl -X POST localhost:8000/api/v1/events -H "X-API-Key: <Andes key>" \
-H "Content-Type: application/json" \
-d '{"event_type":"gas_detection","asset_code":"TB-07-1","attributes":{"gas":"h2s","ppm":18}}'
# A driver asks on WhatsApp, in Spanish
curl -X POST localhost:8000/api/v1/assistant/inbound -H "X-API-Key: <Andes key>" \
-H "Content-Type: application/json" \
-d '{"channel":"whatsapp","sender":"+5492994441111","body":"Hola, cual es el horario de ingreso al pad 7?"}'
The worker delivers retries and closes idle conversations:
python -m app.worker (or --once from a scheduler).
docker compose up --build
docker compose run --rm api python -m app.cli create-platform-admin --email [email protected] --name "You"
Change every password in docker-compose.yml and deploy/init-roles.sql first.
In the application. Every query on client data filters on org_id
(app/services/scoping.py). An id that belongs to another client returns
404 not found, never 403, so ids cannot be used to probe.
In PostgreSQL. Migration 0002 turns on row-level security for every
client table. Each transaction pins app.current_org (app/db.py), so even
a query that forgets its filter only sees the current client's rows.
This only works if the API connects as an ordinary role, because superusers
and table owners bypass it. That's why there are two URLs:
OPSCORE_DATABASE_URL (app role) and OPSCORE_MIGRATION_DATABASE_URL
(owner).
app/services/events.py
Idempotency-Key again returns the original event, so device retries never
duplicate.app/assistant/
| Message | What happens |
|---|---|
| Emergency words (fire, herido, vazamento...) | Critical escalation and a safety reply. This still fires even if a person already owns the chat. |
| "talk to a person" / "hablar con alguien" | Hand-off reply; conversation escalated |
| Person already handling it | Bot stays quiet |
| Covered by an approved answer | Reply in the sender's language |
| Anything else | "A person will reply"; escalated |
Escalations are ordinary assistant.escalation events, so they use the same
rules, alerts and notifications as everything else.
There are two answerers:
OPSCORE_ASSISTANT_LLM_ENABLED=true, model claude-opus-5-5):
understands paraphrases. It may only answer from the approved entries and
must cite them; uncited answers are discarded. Sender messages are treated
as untrusted input. Any outage, refusal or rate limit falls back to keyword
matching. Needs OPSCORE_ANTHROPIC_API_KEY (or ANTHROPIC_API_KEY); the app
refuses to start with Claude switched on and no key.Thanks, greetings and "ok" get a short courtesy reply and are never escalated.
Escalated conversations go back to the assistant after
OPSCORE_ASSISTANT_ESCALATION_HANDBACK_HOURS (default 8) with no reply from the team.
A site-specific answer is only sent when the sender names that site, matched
the way people type ("pad 7" = PAD-07; "pad 17" does not match).
Every configuration change and every human action is recorded with who, what,
when and the request id. Entries are hash-chained per client.
GET /api/v1/audit/verify recomputes the chain and reports the first entry
that was edited or removed.
| Role | Can |
|---|---|
| viewer | read sites, assets, events, alerts, work orders, notifications, conversations |
| member | + report events, update work order status |
| supervisor | + acknowledge/resolve alerts, manage work orders, reply in assistant chats |
| admin | + configure sites, rules, contacts, API keys, members below admin, read audit |
| owner | + manage admins and owners (there is always at least one owner) |
API keys get narrow scopes instead of roles: events:write, events:read,
messages:write.
; delimiter and decimal commas
(-38,5476), blank trailing rows, duplicate codes, unknown sites. All or
nothing, with every error reported by row number.cd backend
.venv/Scripts/python -m pytest # 376 tests (5 PostgreSQL-only are skipped), in-memory SQLite
.venv/Scripts/ruff check . && .venv/Scripts/ruff format --check .
Against PostgreSQL, including the row-level security tests (needs a
non-superuser app role, see deploy/init-roles.sql):
OPSCORE_TEST_DATABASE_URL=postgresql+psycopg://opscore_app:PW@localhost:5432/opscore_test \
OPSCORE_TEST_MIGRATION_DATABASE_URL=postgresql+psycopg://opscore:PW@localhost:5432/opscore_test \
.venv/Scripts/python -m pytest
vercel.json at the repository root deploys everything as a single Vercel project:
| Part | Folder | On Vercel |
|---|---|---|
| Screens (Next.js) | frontend/ | Service web, public at / |
| OpsCore API (FastAPI) | backend/ | Service api, reached by the web service through a binding that sets OPSCORE_API_URL |
No database service is needed. During the build Vercel runs backend/build.py
(declared in backend/pyproject.toml), which creates backend/data/opscore.db with
the tables and the four sample clients, and the deployment ships that file. The
deployed code is read-only, so each server instance copies it to its temporary
folder when it starts (backend/app/bundled_db.py). Notifications are delivered
during the request, so no separate worker is needed.
vercel.json.How it behaves:
OPSCORE_ENVIRONMENT=production for this setup: production refuses SQLite
and the sample clients.For a real client, with data that must survive and several people working at once,
connect a database: Storage → Create Database → Neon (Postgres). This sets
DATABASE_URL and DATABASE_URL_UNPOOLED, which OpsCore reads, and the bundled file
is then not used. Also set OPSCORE_ENVIRONMENT=production and OPSCORE_JWT_SECRET
(from python -c "import secrets; print(secrets.token_urlsafe(48))"), run
python -m app.cli migrate against DATABASE_URL_UNPOOLED on every deploy, and run
python -m app.worker somewhere (for example a Vercel daemon built from
backend/Dockerfile) for reminders, overdue work orders and hand-backs.
OPSCORE_ENVIRONMENT=production. The app refuses to start with SQLite,
the development JWT secret, or bcrypt cost below 12, and hides /docs.
The development JWT secret is also refused with any non-SQLite database, and
docker compose will not start until OPSCORE_JWT_SECRET is set.python -m app.cli migrate on every deploy.python -m app.worker.backend/
app/
main.py FastAPI app, middleware, health checks
config.py settings (OPSCORE_*), production guard
db.py engine, sessions, UTC timestamps, tenant hook for RLS
models/ tables
schemas/ request/response bodies (unknown fields rejected)
services/ business logic; flush, never commit
events.py intake -> rules -> alerts -> work orders -> notifications
rules.py rule validation and evaluation (pure functions)
audit.py hash-chained audit log
imports.py CSV import
assistant/ Ops Assistant: language, answerers, conversations
notify/ providers (log, SMTP, Twilio) and the retrying dispatcher
packs/ starter packs per industry
api/ routes and auth dependencies
cli.py worker.py bundled_db.py (the SQLite file shipped to Vercel)
alembic/versions/ 0001 schema, 0002 row-level security
tests/ 371 tests (+5 PostgreSQL-only)
deploy/init-roles.sql
docker-compose.yml