Documents the exact Prisma query per dashboard figure, the one new durable table this feature needs (ErrorCodeLookup — 014's own equivalent metric is process-lifetime, unusable for a historical report), the "no data -> null, never NaN" convention, and why the AI dashboard's confidence distribution deliberately uses the system-default threshold rather than resolving a per-diagnosis policy (AIDiagnosis has no reliable FK back to which policy applied). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
8.8 KiB
Implementation Plan: Reporting and Analytics Dashboards
Branch: 015-reporting-dashboards | Date: 2026-09-09 | Spec: spec.md
Input: Feature specification from specs/015-reporting-dashboards/spec.md
Summary
Wires the pre-scaffolded, unused platform/reports module into four real, admin-gated,
read-only aggregation endpoints (Management, Product, Support, AI) matching
docs/09-testing-observability-cicd.md's own dashboard table — each computed synchronously,
on request, directly from existing durable tables (Ticket, Problem, SLARun, EscalationEvent,
AISupportSession, AIDiagnosis, AIAction, Resolution, Assignment). The one new piece of state is
a small durable ErrorCodeLookup audit table, needed only because no existing record lets "top
errors" be computed historically (014-full-observability's own equivalent is a process-lifetime
Prometheus counter, unusable for a dated report). No presentation layer — see spec.md's
Assumptions for why supporthub-web work is a separate follow-on.
Technical Context
Language/Version: TypeScript 5.4 / Node.js 20+ (unchanged).
Primary Dependencies: None new — Prisma's own groupBy/count/aggregate/findMany, no
raw SQL (research.md §5), reusing decideConfidenceBand (005-ai-support) and the
Resolution.resolvedBy convention (014-full-observability) rather than reimplementing either.
Storage: One new table, ErrorCodeLookup (id, errorCodeId FK, productId FK,
createdAt) — append-only, no update/delete path, indexed (productId, createdAt) for the
Product dashboard's range-scoped ranking query. No change to any existing table.
Testing: Vitest — unit tests for the "no data → null, never NaN" averaging helper and the
confidence-bucketing reuse; integration tests against real Postgres/Redis driving each
dashboard's real underlying data (tickets in various terminal states, SLA runs met/breached,
escalation events, AI sessions/diagnoses/actions, error-code lookups) and asserting every
returned figure against hand-computed expected values — the same rigor and mixed
HTTP-driven/direct-repository setup style as 014's business-metrics.test.ts.
Target Platform: Same Fastify modular monolith. Rewrites platform/reports (service,
new controller, new routes, new schema for the date-range/product-id query params) from its
current one-stub-method state into the real module. Adds one line to
ai-support/knowledge/service/error-codes.service.ts's existing findKnownIssuesByErrorCode
(the same call site 014 already instrumented) to also write the new durable audit row.
Project Type: Backend service — single project.
Performance Goals: Every dashboard query is bounded by the requested date range (default 30 days, config) and, where a full-row fetch is needed for in-application averaging (research.md §5), only the two timestamp columns needed for that specific average — never a full-table scan with no range filter. Acceptable at current data volumes per spec.md's own Assumptions; pre-aggregation is explicitly deferred to if/when load testing (a separate, not-yet-started Phase 11 sub-area) shows it's actually needed.
Constraints: FR-006 — an unknown productId on the Product dashboard is a 404, never an
empty-but-200 response. FR-007 — every rate/average is number | null, null meaning "no
qualifying data," computed by checking the qualifying count before ever dividing. FR-008 — every
route requires requireRole('ADMIN'), the same gate every admin surface uses since
010-identity-auth.
Scale/Scope: Four new GET routes, one new Prisma model + migration, four new service
methods (one per dashboard) replacing the single stub method, one new schema file for query-param
validation, three new env-configured values (Constitution Principle II). No new module — this
extends platform/reports, already the correct architectural home.
Constitution Check
GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.
| Principle / Section | Check | Result |
|---|---|---|
| I. SaaS Is the Sole Identity & Access Authority | Not applicable — no identity/access surface touched; every figure is derived from SupportHub's own domain data (tickets, problems, SLA, escalation, AI sessions), squarely inside SupportHub's own sole-authority domain per this principle's own second sentence. | PASS |
| II. Configuration Over Hardcoding | The default reporting window, the SLA-risk threshold, and the top-N ranking limit are all new env-configured values (REPORTING_DEFAULT_WINDOW_DAYS, REPORTING_SLA_RISK_THRESHOLD_MINUTES, REPORTING_TOP_N_LIMIT), never hardcoded — matches spec.md's own Assumptions and the roadmap's "never hardcode a placeholder value and ship it as final." |
PASS |
| III. Layered Architecture With Enforced Module Boundaries | All new code lives inside platform/reports (already its correct home) following Route → Schema → Controller → Service → Repository → Prisma; cross-module reads (tickets, AI support, orchestration, SLA/escalation, problem resolution) go through each owning module's own public index.ts, the same precedent every prior feature this session established (e.g. tool-executor.ts reading ticketsService from @/modules/ticketing/tickets). |
PASS |
| IV. AI Recommends, Deterministic Policy Decides | Not applicable — no AI tool-execution or decision logic changed; the AI dashboard only reports on outcomes the existing, already-deterministic confidence-band/tool-policy code already produced. | PASS — N/A |
| V. Evidence-Based Verification | Not applicable — no resolution-recording logic changed. | PASS — N/A |
| VI. Durable Audit & History | The one new table (ErrorCodeLookup) is itself an append-only audit record, directly in this principle's spirit — "which error codes came up, when" becomes durably answerable for the first time. |
PASS |
| VII. Concurrency-Safe, Durable Job Handling | Not applicable — read-only aggregation queries, no job handlers, no assignment/SLA state mutated. | PASS — N/A |
| VIII. Problem and Ticket Are Separate, Related Entities | Respected — the Product dashboard's problem-type breakdown queries Problem directly, never conflating it with Ticket. |
PASS |
| Technology & Platform Constraints | No new dependencies; one new Prisma model via the established non-interactive migration workflow (prisma migrate diff → hand-written migration.sql → prisma migrate deploy) this session has used for every prior schema change. |
PASS |
No violations requiring Complexity Tracking justification.
Project Structure
Documentation (this feature)
specs/015-reporting-dashboards/
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
│ └── reports-api-contract.md
└── tasks.md
Source Code (repository root)
supporthub-api/
├── prisma/
│ ├── schema.prisma # MODIFIED — new ErrorCodeLookup model
│ └── migrations/
│ └── <timestamp>_add_error_code_lookup/migration.sql # NEW
├── src/
│ ├── config/
│ │ └── env.ts / reporting.ts (or similar) # MODIFIED — 3 new env-configured values
│ └── modules/
│ ├── platform/
│ │ └── reports/ # REWRITTEN (was a 1-method stub)
│ │ ├── controller/
│ │ ├── mapper/ # date-range parsing/defaulting, averaging helper
│ │ ├── repository/ # the 4 dashboards' Prisma queries
│ │ ├── routes/
│ │ ├── schema/ # query-param validation
│ │ ├── service/
│ │ └── index.ts
│ └── ai-support/
│ └── knowledge/
│ ├── repository/ # MODIFIED — errorCodeLookupRepository
│ └── service/
│ └── error-codes.service.ts # MODIFIED — one new line at the existing
│ lookup call site
└── tests/
├── unit/platform/reports/ # averaging/no-data-null helper, confidence
│ bucketing reuse
└── integration/platform-reports/ # all four dashboards against real data
Structure Decision: Single project, no new module — platform/reports already exists as the
correct architectural home and simply needs its real implementation built out, following the
same Route → Schema → Controller → Service → Repository → Prisma layering every other module
already uses.
Complexity Tracking
No constitution violations — table intentionally omitted.