Wires the pre-scaffolded, unused platform/reports module (ReportsService
.generateSummaryReport previously returned {}) into four real, admin-
gated dashboards matching docs/09-testing-observability-cicd.md's own
table:
- GET /admin/reports/management: total cases, AI-resolved, human-
escalated, resolved/open, SLA compliance/breaches, escalation count,
average response/resolution time.
- GET /admin/reports/product/:externalProductId: support volume,
problem-category breakdown, recurring problems, AI-resolution/human-
escalation rate, top error codes.
- GET /admin/reports/support: current per-agent workload, SLA at-risk/
breached counts, escalation count, response/resolution performance.
- GET /admin/reports/ai: AI resolution/human-handoff rate, failed-
troubleshooting-then-escalated rate, knowledge-match rate, confidence
distribution (reusing 005-ai-support's own decideConfidenceBand),
tool invocation success/failure.
Every rate/average is number|null -- null means no qualifying data in
range, never a computed NaN or a misleading 0. Adds one new durable
table, ErrorCodeLookup, since 014-full-observability's own equivalent
metric is a process-lifetime Prometheus counter unusable for a
historical "top errors" report.
Verified end-to-end against real Postgres/Redis: every figure checked
against hand-computed expected values, including a no-activity range
(all-zero counts, all-null rates) and cross-product isolation.
Also fixes a real regression the new ErrorCodeLookup FK caused in the
pre-existing known-issues.test.ts (its afterAll deleted ErrorCode rows
before the now-referencing lookup rows).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
9.4 KiB
description
| description |
|---|
| Task list for 015-reporting-dashboards |
Tasks: Reporting and Analytics Dashboards
Input: Design documents from specs/015-reporting-dashboards/
Prerequisites: plan.md, spec.md, research.md, data-model.md, contracts/reports-api-contract.md, quickstart.md
Organization: Tasks are grouped by user story (US1 = P1 Management, US2 = P1 Product, US3 = P2 Support, US4 = P2 AI). All four share the Foundational phase (schema, config, shared helpers, module scaffolding) but are otherwise independent of each other.
Format: [ID] [P?] [Story] Description
All file paths are relative to supporthub-api/ (repo root).
Phase 1: Foundational (Blocking Prerequisites)
- T001 Add
REPORTING_DEFAULT_WINDOW_DAYS(default30),REPORTING_SLA_RISK_THRESHOLD_MINUTES(default60), andREPORTING_TOP_N_LIMIT(default10) tosrc/config/env.ts, exposed via a newreportingConfiginsrc/config/reporting.ts(or added to an existing config file, matching this codebase's own per-feature config-file convention) - T002 Add the
ErrorCodeLookupmodel toprisma/schema.prismaper data-model.md, generate the migration viaprisma migrate diff --from-url <db-url> --to-schema-datamodel ./prisma/schema.prisma --script, hand-write it intoprisma/migrations/<timestamp>_add_error_code_lookup/migration.sql, apply viaprisma migrate deployagainst the throwaway test database (depends on T001 only in that both are Foundational — no code dependency) - T003 Add
ai-support/knowledge/repository/error-code-lookup.repository.ts—create(errorCodeId, productId), exported from the knowledge module's repository index (depends on T002) - T004 [P] Call the new repository's
create(...)fromai-support/knowledge/service/error-codes.service.ts's existingfindKnownIssuesByErrorCode, alongside (not replacing) 014's ownknownErrorLookupsCounter.inc(...)call at that same call site (depends on T003) - T005 [P] Add
platform/reports/mapper/date-range.ts— parses/validatesfrom/toquery params, defaulting via T001'sreportingConfig.defaultWindowDays, throwingValidationErrorwhenfrom > to(depends on T001) - T006 [P] Add
platform/reports/mapper/rate.ts— a sharedcomputeRate(numerator, denominator): number | nullandcomputeAverageSeconds(durations: number[]): number | nullpair, both returningnull(neverNaN/0) when there's no qualifying data (research.md §3) — no dependency, pure functions - T007 Scaffold
platform/reports/schema/(query-param zod schema using T005's date-range parsing),platform/reports/controller/reports.controller.ts(empty methods to be filled in per user story below),platform/reports/routes/reports.routes.tsregistering all four routes behindrequireRole('ADMIN'), and updateplatform/reports/index.tsto export the new public surface, replacinggenerateSummaryReport's stub entirely (depends on T005, T006)
Checkpoint: Config, schema, shared helpers, and module scaffolding in place. Each dashboard can now be built independently.
Phase 2: User Story 1 - Management sees organization-wide support health (Priority: P1)
Goal: GET /admin/reports/management returns real figures per data-model.md's
ManagementDashboard shape.
Independent Test: Quickstart Scenario 1.
Tests for User Story 1
- T008 [P] [US1] Unit tests for T006's
computeRate/computeAverageSeconds(empty input ->null; a real mix -> the correct value) intests/unit/platform/reports/rate-helpers.test.ts
Implementation for User Story 1
- T009 [US1] Add
platform/reports/repository/management.repository.ts— one method per research.md §2's Management table row (ticket counts by status, SLA-run outcome counts, response/resolution duration row-fetches for T006 to average) (depends on T007) - T010 [US1] Add
ReportsService.getManagementDashboard(range)composing T009's repository calls into theManagementDashboardshape, reusing theResolution.resolvedByconvention (research.md §4) for the AI-vs-human split (depends on T009) - T011 [US1] Wire
GET /admin/reports/managementto the controller/service (depends on T010) - T012 [US1] Integration test covering Quickstart Scenario 1 (real tickets in various
terminal states, a met and a breached SLA run, verified figure-by-figure; a no-activity
range returns all-zero counts and all-null rates) in
tests/integration/platform-reports/management-dashboard.test.ts(depends on T011)
Checkpoint: Quickstart Scenario 1 passes.
Phase 3: User Story 2 - See support broken down by product (Priority: P1)
Goal: GET /admin/reports/product/:externalProductId returns real figures per
ProductDashboard.
Independent Test: Quickstart Scenario 2.
Implementation for User Story 2
- T013 [P] [US2] Add
platform/reports/repository/product.repository.ts— ticket/problem queries scoped byproductId, plus a query against T003'sErrorCodeLookuptable for the top-N ranking (reportingConfig.topNLimit) (depends on T007) - T014 [US2] Add
ReportsService.getProductDashboard(externalProductId, range), 404-ing viaNotFoundErrorwhen the product doesn't resolve (FR-006) before running any aggregation query (depends on T013) - T015 [US2] Wire
GET /admin/reports/product/:externalProductId(depends on T014) - T016 [US2] Integration test covering Quickstart Scenario 2 (two products' data never
cross-contaminating each other's figures; an unknown product 404s) in
tests/integration/platform-reports/product-dashboard.test.ts(depends on T015)
Checkpoint: Quickstart Scenario 2 passes.
Phase 4: User Story 3 - Support sees team workload and performance (Priority: P2)
Goal: GET /admin/reports/support returns real figures per SupportDashboard.
Independent Test: Quickstart Scenario 3.
Implementation for User Story 3
- T017 [P] [US3] Add
platform/reports/repository/support.repository.ts— currentAssignmentworkload-by-agent query,SLARunat-risk/breached queries (resolutionDueAtwithinreportingConfig.slaRiskThresholdMinutesof now, per research.md §2) (depends on T007) - T018 [US3] Add
ReportsService.getSupportDashboard(range)(depends on T017) - T019 [US3] Wire
GET /admin/reports/support(depends on T018) - T020 [US3] Integration test covering Quickstart Scenario 3 (real per-agent assignment
counts; a near-due-but-not-breached run counted as at-risk, distinct from breached) in
tests/integration/platform-reports/support-dashboard.test.ts(depends on T019)
Checkpoint: Quickstart Scenario 3 passes.
Phase 5: User Story 4 - See how well the AI is performing (Priority: P2)
Goal: GET /admin/reports/ai returns real figures per AiDashboard.
Independent Test: Quickstart Scenario 4.
Tests for User Story 4
- T021 [P] [US4] Unit test: the confidence-distribution bucketing reuses
decideConfidenceBand(005-ai-support) againstaiConfigdefaults, not a reimplemented threshold check, intests/unit/platform/reports/confidence-distribution.test.ts
Implementation for User Story 4
- T022 [US4] Add
platform/reports/repository/ai.repository.ts—AISupportSessionoutcome counts,AIDiagnosisconfidence fetch,AIKnowledgeReferencepresence-per-session query,AIAction/AIActionResultoutcome counts (depends on T007) - T023 [US4] Add
ReportsService.getAiDashboard(range), bucketing confidence viadecideConfidenceBand+aiConfig.defaultHighConfidence/defaultLowConfidence(research.md §7) (depends on T022, T021) - T024 [US4] Wire
GET /admin/reports/ai(depends on T023) - T025 [US4] Integration test covering Quickstart Scenario 4 (real AI sessions to mixed
outcomes, mixed tool results, a spread of diagnosis confidence values) in
tests/integration/platform-reports/ai-dashboard.test.ts(depends on T024)
Checkpoint: Quickstart Scenario 4 passes. All four dashboards work independently and together — this feature's full scope.
Phase 6: Polish & Cross-Cutting Concerns
- T026 [P] Update
specs/015-reporting-dashboards/checklists/requirements.mdNotes with any implementation-time findings - T027 Run
npx tsx scripts/check-architecture.tsandnpm run lint/npm run typecheck - T028 Full regression:
npm run test:unitthen the full integration suite against real Docker-provisioned Postgres/Redis, confirming nothing outside this feature regressed (particularlyerror-codes.service.ts's own existing tests, now touched by T004)
Dependencies & Execution Order
- Foundational (Phase 1): No dependencies — BLOCKS all four user stories
- User Story 1 (Phase 2): Depends on Foundational — independent of US2/US3/US4
- User Story 2 (Phase 3): Depends on Foundational — independent of US1/US3/US4
- User Story 3 (Phase 4): Depends on Foundational — independent of US1/US2/US4
- User Story 4 (Phase 5): Depends on Foundational — independent of US1/US2/US3
- Polish (Phase 6): Depends on all four user stories