by Ta-Tech Solutions All documents

03 - Domain Model

CivicLoop by Ta-Tech Solutions Purpose: Define every entity in the system, how they relate, and the lifecycle a service request moves through. This is the contract the database, the API, and every screen are built against.


1. The core idea

CivicLoop has one central object: the Service Request. Everything else either describes it, routes it, works it, or communicates about it. If you understand the Service Request and its lifecycle, you understand the system.

A Service Request is a living record. It is created once, by a resident or an agent, and from that moment it carries its own status, its own owner, its own clock, its own history, and its own conversation - visible to the resident, the assigned agent, and County leadership at the same time. It is the opposite of the "black hole."

2. Entity catalogue

2.1 Tenancy & geography

Entity What it is Key fields
County The top-level tenant. CivicLoop is multi-tenant; Prince George's County is one tenant. The model supports many. id, name, state, timezone, default_language, branding (logo, colors), Open311 endpoint config, autopilot_level (text: off / route / full; controls how much the AI does on its own at intake - Document 07, Component J)
Department An operating unit the County routes work to: DPWT, DOE, WSSC, DPIE, PARKS, ANIMAL, 311 (Office of Community Relations), Health & Human Services, etc. Each department also owns one persistent channel (Section 2.7 below). id, county_id, name, code, contact, business_hours, escalation_contact
Service Area A geographic subdivision used for routing and analytics: council district, planning area, sub-division, or a custom polygon. id, county_id, name, type, boundary (geo polygon), council_district (1-9 for PG County, used by the equity panel and the council view), zip_code
Location A point on the map attached to a request. Captured from device GPS, a dropped pin, or a geocoded address. lat, lng, address, service_area_id, accuracy_m, source (gps / pin / geocoded), council_district, zip_code (both denormalized from the resolved service area so the equity, forecast, and council-view queries do not have to join a geometry on every read)

2.2 The request and its shape

Entity What it is Key fields
Service Category The kind of problem. A two-level tree: a top group (Roads & Sidewalks, Trash & Recycling, Streetlights, Noise, Parks, Housing, Animal Services, Health & Human Services, etc.) and specific types under it (pothole, missed collection, broken streetlight...). Each category carries its default routing and SLA. id, county_id, parent_id, name, default_department_id, default_sla_hours, default_priority, open311_service_code, requires_location, requires_photo
Service Request The central living record. id, county_id, request_number, category_id, department_id, status, priority, location_id, description, channel, language, resident_id, created_by, assigned_to, sla_due_at, sla_predicted_breach, sentiment, duplicate_of_id, opened_at, acknowledged_at, resolved_at, closed_at
Request Attachment A photo, video, voice clip, or document on the request - at intake (resident's evidence) or at resolution (proof of work). id, request_id, type, url, caption, stage (intake / progress / resolution), uploaded_by
Request Event An append-only history entry. Every status change, assignment, comment, notification, and AI action writes one. The request's full timeline. id, request_id, event_type, actor (resident / agent / ai / system), from_value, to_value, note, created_at
Request Comment Two-way message thread between resident and agent on a request. id, request_id, author, body, internal (true = staff-only note), created_at

2.3 People

Entity What it is Key fields
Resident A member of the public. Can exist with just a phone number - no account required to file a request. An optional account adds tracking convenience. id, county_id, name (optional), phone, email (optional), preferred_language, preferred_channel, has_account, created_at
Staff A County employee who works inside CivicLoop: agent, supervisor, department head, 311 director, county admin. id, county_id, department_id, name, email, role, active, mfa_enrolled, last_seen_at

(The full role and permission model is Document 04.)

2.4 Routing, SLA & AI

Entity What it is Key fields
Routing Rule How a category (optionally narrowed by service area or keyword) maps to a department and priority. The AI proposes routing; rules are the deterministic backbone it works within and can be overridden by. id, county_id, category_id, service_area_id (optional), keyword_match (optional), department_id, priority, active
SLA Policy The promised resolution time for a category + priority, in business hours, against a department's business calendar. id, county_id, category_id, priority, response_hours, resolution_hours, business_hours_only
AI Decision A record of every AI action on a request - what it did, what it concluded, its confidence, and the rationale shown to humans. Auditable; the AI is never a black box. id, request_id, decision_type (classify / route / predict_breach / detect_sentiment / detect_duplicate), input_summary, output, confidence, rationale, model_version, created_at

2.5 Communication

Entity What it is Key fields
Notification An outbound message to a resident about their request - received, assigned, in progress, resolved, needs-more-info. Carries language and channel. id, request_id, resident_id, channel (sms / whatsapp / email / push), language, template, body, status (queued / sent / delivered / failed), sent_at
Notification Template The localized message templates, one set per language, for each lifecycle event. id, county_id, event_type, language, subject, body

2.6 Oversight

Entity What it is Key fields
Audit Log Every meaningful action by any staff member or system process, immutable, attributable. The compliance spine (Document 09). id, county_id, actor, action, entity_type, entity_id, before, after, ip, created_at
Saved View A staff member's filtered queue (e.g. "my open high-priority road requests"). id, staff_id, name, filter_json

2.7 Visits, channels, surveys, forecasts (the 2026-05-18 wave)

These five tables landed in web/sql/civicpulse-schema.sql as part of the post-core build wave. Each has row-level security on county_id and each ships with INSERT/DELETE smoke tests in the schema dump (TaTech "trigger smoke test rule").

Entity What it is Key fields
Scheduled Visit (scheduled_visits) A field-crew or follow-up visit an agent puts on a resident's request. Creating one fires SMS + email to the resident with a .ics calendar attachment; crews can set a 24h alarm; cancel + complete actions both write Request Events on the request. id, county_id, request_id, scheduled_at (timestamptz), duration_minutes, alarm_minutes_before, address (snapshot), notes, created_by, status (scheduled / completed / cancelled), completed_at
Channel (channels) A persistent Slack-style channel. One per County department (DPWT, DOE, WSSC, DPIE, PARKS, ANIMAL, 311) plus a county-wide #311-all. Drives /channels and /channels/[slug]. id, county_id, slug, name, department_id (nullable for #311-all), kind (department / county), created_at
Channel Message (channel_messages) One message in a channel. Supports @mention highlighting, full-text substring search, and auto-linked CP-... tracking numbers that deep-link into /console/[requestNumber]. Slash commands (/help, /open, /breaches, /summary CP-...) are processed deterministically server-side, no AI call. @loop mentions trigger the Loop persona (Document 07). id, county_id, channel_id, author_id (nullable when author is loop system), body, mentions[], request_refs[] (parsed CP-... numbers), created_at
Request Survey (request_surveys) Created on every transition to RESOLVED. A one-tap 1-5 link is texted/emailed to the resident; the public survey page lives at /[locale]/survey/[token]. Responses feed the NPS panel on the director dashboard. id, county_id, request_id, token (opaque, single-use), score (1-5, nullable until answered), comment, sent_at, responded_at
Predicted Issue (predicted_issues) One forecast row per (category, council_district, run_id): expected request count for the next 7 days, a confidence score, the run timestamp. Populated by the forecast component (Document 07, Component K); read by the dashboard forecast panel and by the council-district view. id, county_id, category_id, council_district, expected_count, confidence, horizon_days (7), computed_at

3. How the entities relate

County
 |-- Department          (many)
 |-- Service Area        (many)
 |-- Service Category    (many, 2-level tree)
 |-- Staff               (many)
 |-- Resident            (many)
 |-- Routing Rule        (many)
 |-- SLA Policy          (many)
 |-- Notification Template (many)
 |
 +-- Service Request     (many)  <-- the center of everything
      |-- belongs to one Service Category
      |-- routed to one Department
      |-- has one Location -> one Service Area
      |-- filed by / about one Resident
      |-- assigned to one Staff (agent)
      |-- has many Request Attachments
      |-- has many Request Events      (the timeline)
      |-- has many Request Comments    (the conversation)
      |-- has many AI Decisions        (every AI action, auditable)
      |-- has many Notifications       (every message to the resident)
      |-- may reference another Request as duplicate_of

Every box above is tenant-scoped to a County. A second county added to CivicLoop gets its own departments, categories, rules, staff, and requests, fully isolated - the multi-tenant property inherited from the Ta-Tech engine (Document 05).

The 2.7 wave plugs in cleanly:

Service Request   ----  Scheduled Visit       (0..n; ICS + alarm + SMS/email)
                  ----  Request Survey        (0..1; one per RESOLVED transition)

Department        ----  Channel               (1; the dept's slack-style room)
County            ----  Channel               (1 county-wide: #311-all)
Channel           ----  Channel Message       (0..n; including @loop replies)
Channel Message   ----> Service Request       (0..n; auto-parsed CP-... refs)

(Category, Service Area)  ----  Predicted Issue   (0..n; one per forecast run)

Triggers worth knowing about:

4. The lifecycle of a Service Request

This is the state machine. Every transition writes a Request Event and may fire a Notification.

            +-------------+
            |   DRAFT     |   resident is still composing (intake AI
            +-----+-------+   conversation in progress); not yet visible
                  |          to staff
                  v
            +-------------+
            |  SUBMITTED  |   resident confirmed; request_number issued;
            +-----+-------+   AI classifies + routes; resident gets
                  |          "received" notification
                  v
            +-------------+
            |  TRIAGED    |   routed to a Department with a priority and
            +-----+-------+   an SLA clock; AI duplicate-check run; if a
                  |          duplicate, link + merge, notify resident
                  v
            +-------------+
            |  ASSIGNED   |   a specific agent owns it; resident gets
            +-----+-------+   "a crew/agent is assigned" notification
                  |
                  v
            +-------------+
            | IN PROGRESS |   agent is actively working it; progress
            +-----+-------+   updates + photos may be added; SLA-breach
                  |          prediction runs continuously
                  |
       +----------+----------+
       v                     v
+-------------+        +-------------+
|  RESOLVED   |        | NEEDS INFO  |  agent needs something from the
+-----+-------+        +-----+-------+  resident; clock pauses; resident
      |                     |          notified with a question
      |                     +----------> back to IN PROGRESS on reply
      v
+-------------+
|   CLOSED    |   resolution confirmed; proof-of-resolution attachment
+-----+-------+   required; resident gets "resolved" notification with
      |          before/after; resident may reopen within a window
      v
+-------------+
|  REOPENED   |   resident says it is not actually fixed; returns to
+-------------+   IN PROGRESS, flagged, supervisor notified

Side states reachable from most points:

5. Rules the model enforces

These are invariants - the system guarantees them, so no screen or workflow can violate them.

  1. No black holes. A request cannot move to CLOSED without a resolution note and at least one resolution-stage attachment (proof). The state machine blocks it.
  2. Every transition is notified. SUBMITTED, ASSIGNED, IN PROGRESS, NEEDS INFO, RESOLVED, REOPENED each fire a Notification to the resident in their language on their channel - automatically, with no staff action required.
  3. Every AI action is recorded. Classify, route, predict, detect - each writes an AI Decision row with its rationale and confidence. The AI is auditable, never a black box.
  4. The clock is honest. SLA timers run against the department's real business calendar and pause in NEEDS INFO. Predicted breaches are surfaced before the deadline, not after.
  5. A request can be filed without an account. A phone number is enough. Accounts are an optional convenience, never a barrier (closes the documented "mandatory account" failure mode).
  6. Tenant isolation is absolute. Every row is County-scoped; no query crosses the tenant boundary.

6. What this model deliberately leaves for later

To keep v1 focused and demo-ready for May 23, the model does not yet include (but is structured to accept in Phase 2):

The discipline: model the v1 loop completely and correctly, leave clean seams for Phase 2, build nothing speculative.


Next: 04 - User Roles & Permissions.

PreviousCompetitive Landscape & Strategy
CivicLoop - Ta-Tech Solutions - Architecture & Design Documentation