6.6 KiB
Denya OneCare — Agent memory
Denya OneCare is a centralized maintenance/issue tracking system for Pavilion Accra (FastAPI + SQLite + Alpine.js + Twilio).
Quick start
# Start the app
docker-compose up
# Or directly
uvicorn app.main:app --reload
Project structure
app/
├── core/ # config, database, security (JWT + bcrypt + RBAC)
├── models/ # SQLAlchemy ORM models
├── schemas/ # Pydantic request/response schemas
├── services/ # Business logic (auth, seed, ticket, sla)
└── routers/ # FastAPI route handlers
alembic/ # Database migrations
tests/ # pytest suite; conftest.py swaps DATABASE_URL to a temp SQLite
uploads/ # Photo uploads (created at runtime)
Commands
alembic upgrade head— apply migrationsalembic revision --autogenerate -m "msg"— new migrationpytest— run the API test suite (tests/; pagination contract anchored in tests/test_tickets_pagination.py)
Seed data
Users, units, and categories are auto-seeded on first startup via lifespan hook:
- 17 users covering all roles (Admin/Jerome, Admin/Wahab, CS Rep, CS Manager, FM Dispatcher, Tech, CEO, Director)
- 120 apartment units (East/West, 10 floors × 6 apts per wing)
- Default password for all seed users:
denya123 - Units load from
apartment_mapping.jsonif present, else built-in fallback - Categories: 23 top-level (13 Maintenance, 6 CS, 4 Emergency) with sub-categories, seeded from
app/services/seed.py::SEED_CATEGORIES_DATA
Key API endpoints
Auth & Health
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /health |
No | Health check |
| POST | /api/auth/register |
No | Create user |
| POST | /api/auth/login |
No | Get JWT tokens |
| POST | /api/auth/refresh |
Token | Refresh tokens |
| GET | /api/auth/me |
Bearer | Current user |
| GET | /api/auth/admin-only |
Admin/Jerome, Admin/Wahab | RBAC demo endpoint |
| POST | /api/whatsapp/mock |
No | Mock WhatsApp |
| GET | /api/whatsapp/mock-log |
No | Recent mock submissions |
Pages (Sprint 3) — Jinja2 templates at app/templates/
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /login |
No | Login page |
| GET | /dashboard/cs |
Client | CS dashboard |
| GET | /dashboard/fm |
Client | FM dashboard |
| GET | /dashboard/ceo |
Client | CEO dashboard |
| GET | /tickets |
Client | All Issues filterable table |
| GET | /tickets/new |
Client | Create Issue form |
| GET | /tickets/{id} |
Client | Issue detail with timeline |
Frontend: Alpine.js (CDN) + Tailwind CSS (CDN). Auth state in localStorage. Role-based nav routing in base.html.
Tickets (Sprint 2)
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/tickets |
Bearer | Create ticket (auto-number PAV-YYYY-NNNNN) |
| GET | /api/tickets |
No | List tickets (filter: status, priority, property, building, unit_id, category_id, assigned_to, date_from, date_to) |
| GET | /api/tickets/{id} |
No | Get ticket detail with timeline, photos, SLA status |
| PATCH | /api/tickets/{id} |
Bearer | Update ticket (validates status transitions) |
| POST | /api/tickets/{id}/status |
Bearer | Change status with note |
| GET | /api/tickets/{id}/sla |
No | Check SLA breach status |
| POST | /api/tickets/{id}/photos |
Bearer | Upload photos (multipart, is_before param) |
| GET | /api/tickets/{id}/photos |
No | List photos |
| GET | /api/tickets/categories |
No | Category tree (filters: type; alert-only hidden unless include_hidden=true) |
| GET | /api/tickets/categories/flat |
No | Flat category list (same type/include_hidden filters) |
Auth
- JWT access (30min) + refresh (7d) tokens
- Roles: CS Rep, CS Manager, FM Dispatcher, Admin/Jerome, Admin/Wahab, Tech, CEO, Director
- Use
require_roles("Admin/Jerome", "Admin/Wahab")dependency for RBAC subclaim holds string user ID
Ticket System (Sprint 2)
Status Lifecycle (15 statuses)
New → Logged → Triage → Assigned → Accepted → Travelling → On Site → In Progress → Waiting Parts → Escalated → Completed → On-Field Verification → Wahab Review → Closed → Reopened
Valid transitions defined in app/services/ticket.py::VALID_TRANSITIONS. Invalid transitions return 400.
SLA Engine
Defined in app/services/sla.py. Priority-based targets:
- Urgent: respond 15min, resolve 4h
- High: respond 30min, resolve 24h
- Medium: respond 4h, resolve 72h (3d)
- Low: respond 24h, resolve 168h (7d)
sla_deadline auto-calculated on ticket creation. SLA status check at GET /api/tickets/{id}/sla.
Ticket Number Format
PAV-YYYY-NNNNN — sequential per year (e.g., PAV-2026-00001).
Photo Uploads
Stored under uploads/ with UUID filenames. Static-files mounted at /uploads/. Multipart POST with is_before query param.
Database
SQLite via aiosqlite with async SQLAlchemy 2.0. Alembic for migrations. Tables: users, units, categories, tickets, ticket_timeline, ticket_photos, escalations, whatsapp_log
Categories & location (Sprint A)
Category.show_in_form(default True) marks alert-only categories:Gas Leakis hidden from the issue picker but keepssla_urgency="urgent"for SLA/alert/reporting. Pickers (/api/tickets/categories[/flat]) exclude them unlessinclude_hidden=true(used by the emergency quick path ontickets/new.html). Seed taxonomy lives inapp/services/seed.py(SEED_CATEGORIES_DATA); the Lost Property → Missing Item rename is a data migration (alembicb2f4a6c8e0d2), with a startup self-heal (app/main.py::ensure_legacy_schema) applying the same column/rename fix to legacy create_all databases. Seeds are idempotent and syncshow_in_formon existing rows.- Location hierarchy is Property → Building → Apartment (uses
Unit.building).GET /api/tickets/units/groupedreturns{property: {building: [units]}};/api/ticketsaccepts additivebuilding/unit_idfilters. Unit data is deterministic from the committedapartment_mapping.json(built-in fallback inseed.py). - Priority grouping ("Group by priority") is client-side via
app().groupByPriority()inapp/templates/base.html; used bytickets/list.htmlanddashboard/fm.html.
Maintaining this file
Keep this file for knowledge useful to almost every future agent session in this project. Do not repeat what the codebase already shows; point to the authoritative file or command instead. Prefer rewriting or pruning existing entries over appending new ones. When updating this file, preserve this bar for all agents and keep entries concise.