How UnitOps works
A short tour of the pieces behind the product. You do not need this to use UnitOps, but it helps when you are planning an integration or debugging an environment.
Components
| Component | Technology | Responsibility |
|---|---|---|
| Web app | Next.js (App Router) with TypeScript, Tailwind, and shadcn/ui | Every page you see, server-rendered |
| API | FastAPI on Python 3.12 with SQLAlchemy 2 async | Business logic, KPI queries, AI orchestration, notification dispatch |
| Metadata database | Postgres with pgvector | Chats, saved views, audit log, embeddings, forecast metadata |
| Warehouse | Snowflake, Databricks, BigQuery, and others | POS, labor, review, and weather source data, see Data stores |
| Scheduler | APScheduler in the API, or an external scheduler | Evaluates notification rules on their cadence |
| AI | A platform-managed provider (OpenAI, Anthropic, Google, or Vertex AI), set by the deployment operator | Chat, summaries, and embeddings |
Request flow
Browser
-> Next.js server components and server actions
-> FastAPI (JWT validated, tenant and location scoped)
-> Postgres metadata (row-level security per organization)
-> Warehouse (read-only, validated SQL)
-> AI provider (grounded with KPI and document context)
Forecasting model
- Historical actuals are read at the finest grain available.
- The selected projection model produces a value for each 15-minute slot across seven days.
- Manual adjustments are stored separately from the projection, so accuracy reporting can distinguish the model from the operator.
- Publishing freezes the week as the official plan.
- The labor matrix maps each period's projected volume to headcount per position, producing hours and cost.
- Actuals are compared to the published plan to produce variance and rolling accuracy (MAPE and bias) that alert rules read.
Notification pipeline
Rule evaluates (schedule or event)
-> org override decides whether the rule runs
-> recipients resolved from role defaults and user subscriptions
-> thresholds, quiet hours, mute, and cooldown filter deliveries
-> channel provider sends (in-app, email, push, Slack, Teams, SMS)
-> permanent failures land in the dead-letter queue for replay
Multi-tenancy
Every record carries an organization identifier, and many carry a project identifier too. Isolation is enforced in three layers: API middleware sets the tenant context from the validated token, repositories require the organization identifier as a parameter, and Postgres row-level security filters rows in the database itself. Generated warehouse SQL is statically checked for the tenant predicate before it executes.