Files
support_backend/specs/015-reporting-dashboards/plan.md
T
saqib mirandClaude Sonnet 5 4a159725c2 docs(015-reporting-dashboards): plan, research, data model, contract, quickstart
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>
2026-09-09 11:14:29 +05:30

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.sqlprisma 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.