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>
6.0 KiB
6.0 KiB
Specification Quality Checklist: Reporting and Analytics Dashboards
Purpose: Validate specification completeness and quality before proceeding to planning Created: 2026-09-09 Feature: spec.md
Content Quality
- No implementation details (languages, frameworks, APIs)
- Focused on user value and business needs
- Written for non-technical stakeholders
- All mandatory sections completed
Requirement Completeness
- No [NEEDS CLARIFICATION] markers remain
- Requirements are testable and unambiguous
- Success criteria are measurable
- Success criteria are technology-agnostic (no implementation details)
- All acceptance scenarios are defined
- Edge cases are identified
- Scope is clearly bounded
- Dependencies and assumptions identified
Feature Readiness
- All functional requirements have clear acceptance criteria
- User scenarios cover primary flows
- Feature meets measurable outcomes defined in Success Criteria
- No implementation details leak into specification
Notes
- This is
docs/10-implementation-roadmap.md's own Phase 11, third sub-area, per explicit user direction (013 was the security pass, 014 was full observability). Backend-first scope (Assumptions) follows the same pattern already established three times this session (010-identity-auth, 011-agent-ticket-queue, and 014-full-observability's own frontend-free scope) — asupporthub-webdashboard UI is a natural, separate follow-on, not re-litigated here via a fresh question. - The pre-scaffolded-but-inert
platform/reportsmodule (ReportsService.generateSummaryReportcurrently returns{}) and theANALYTICSqueue stub (src/jobs/analytics, logs only) were both confirmed via direct code inspection before writing this spec — the same "provisioned before this session's rebuild but never wired up" pattern found repeatedly this session. This feature wires up the former; the Assumptions section explicitly keeps the latter out of scope (synchronous queries, no pre-aggregation job, for this first cut). - All items pass; no revision iterations were needed. No [NEEDS CLARIFICATION] markers were required — every open question (default date window, SLA-risk threshold, top-N limit) had a reasonable, documented, CONFIGURABLE default (see Assumptions), matching the roadmap's own "never hardcode a placeholder value and ship it as final" instruction.
Implementation Notes (post-build)
- Named the Product dashboard's own repository class
ProductReportRepository(notProductRepository) once it became clear resolvingexternalProductId -> Productshould reusecatalog/products' own already-publicproductsRepository.findByExternalProductIdrather than duplicating that lookup — avoids a name collision and keeps "one authority per concern" (Constitution Principle I's spirit) for product resolution. ManagementRepositoryandSupportRepositoryboth needed byte-identical first-response-duration and resolution-duration queries. Extracted into a sharedSharedReportRepositoryboth compose, rather than duplicating the Prisma query (or the averaging helper alone) twice — discovered while writing the second repository and seeing the copy-paste, not planned upfront in research.md.- "Top errors"/"most common errors" resolution-back-to-
codelogic moved intoErrorCodesService.getTopErrorCodesForProduct(a new method on the module that already ownsErrorCode), rather than the reports module reaching intoerrorCodesRepository/errorCodeLookupRepositorydirectly — cleaner module-boundary ownership than research.md's original per-repository sketch implied. - The AI dashboard's "failed troubleshooting then escalated" figure (spec.md User Story 4) has
no single stored flag anywhere in this codebase —
classifyStepOutcome's per-step verdicts are never persisted as their own durable record. Implemented as a documented proxy instead: an escalated session withtoolCallCount > 0attempted troubleshooting before giving up, one with zero attempts escalated immediately. Documented directly inai.repository.ts's own code comment, the same "honest, documented simplification" precedent research.md §7 already set for the confidence-distribution bucketing. - Three of this module's public exports needed adding to their owning modules' top-level
index.ts(not previously exposed):decideConfidenceBand/ConfidenceBandandknowledgeReferenceRepositoryfromai-support/sessions, matching the "extend an existing module's public surface for a later feature" precedent already used repeatedly this session (004'sproductsRepository, 009'sproblemsRepository). - Found a real regression during T028's full regression pass:
known-issues.test.ts(004- product-knowledge, pre-existing) callsfindKnownIssuesByErrorCodeand its ownafterAlldeletedErrorCoderows before this feature's newErrorCodeLookupFK (RESTRICT) existed — once T004 started writing a lookup row on every call, that cleanup order started failing with an FK violation. Fixed by deletingErrorCodeLookuprows first in that test's ownafterAll. This feature's own new test files never deleteErrorCoderows at all, so they weren't affected the same way (leftover rows there are the same accepted throwaway-data tradeoff already established elsewhere this session). - Confirmed (not caused by this feature — the exact pre-existing issue 014-full-observability's
own checklist already documented and root-caused via
git checkoutcomparison) that this feature's own new integration test files, which also name their test productsTEST_*, occasionally hit the same sharedderiveProductCode"TEST" prefix collision under vitest's concurrent file execution when run alongside otherTEST_*-prefixed files. Every dashboard test passes reliably run individually or in small groups; the intermittent 500 in a full combined run is the same known, out-of-scope, 003-ticketing concern.