TenantAtlas/specs/245-customer-health-score/plan.md
ahmido 86505483bf
Some checks failed
Main Confidence / confidence (push) Failing after 52s
feat(customer-health): add decision card to tenant/workspace detail (spec 245) (#283)
Add Customer Health decision card to tenant & workspace detail pages (spec 245).

What I changed:
- Render a decision-first Customer Health card on tenant and workspace detail pages.
- Reuse `WorkspaceHealthSummaryQuery` and preserve `window` query param.
- Update attention widget link text to "Review health details" and include `?window=`.
- Add/adjust tests to cover new behavior and explainability.
- Run Pint formatting.

Compare URL: https://git.cloudarix.de/ahmido/TenantAtlas/compare/dev...245-customer-health-score

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #283
2026-04-27 08:30:01 +00:00

228 lines
18 KiB
Markdown

# Implementation Plan: Customer Health Score
**Branch**: `245-customer-health-score` | **Date**: 2026-04-27 | **Spec**: `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/245-customer-health-score/spec.md`
**Input**: Feature specification from `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/245-customer-health-score/spec.md`
## Summary
- Add one bounded `CustomerHealth` support namespace that derives workspace-health summaries from existing onboarding, provider, telemetry, `OperationRun`, findings, and review-pack truth.
- Reuse the existing `/system` dashboard, existing dashboard window selector, and existing system widget family to show both aggregate health counts and an attention-needed workspace list.
- Reuse the existing system tenant and workspace residual detail pages as decision-first follow-up surfaces by adding one read-only customer-health card above the existing diagnostics.
- Keep the slice derived-only, Livewire v4-compatible, Filament v5-native, and free of migrations, new persistence, new panel providers, global-search changes, destructive actions, or asset changes.
## Technical Context
**Language/Version**: PHP 8.4 (Laravel 12)
**Primary Dependencies**: Laravel 12 + Filament v5 + Livewire v4 + Pest; existing `ProductTelemetrySummaryQuery`, `ProviderConnectionStateProjector`, `StuckRunClassifier`, `BadgeRenderer`, `SystemConsoleWindow`, system directory links, and system operations links
**Storage**: N/A - no new persisted health truth
**Testing**: Pest unit + feature tests only
**Validation Lanes**: fast-feedback, confidence
**Target Platform**: Sail-backed Laravel admin panel under `/system`
**Project Type**: web
**Performance Goals**: derive health summaries inline for the current system dashboard without background jobs, remote calls, or broad per-tenant page reconstruction
**Constraints**: no new score table, no customer-facing surface, no CRM or billing workflow expansion, no direct `/admin` deep links, and no new badge taxonomy
**Scale/Scope**: six fixed first-slice dimensions, one derived summary query, two dashboard widgets, one shared decision-first follow-up card on the existing system detail pages, and focused unit plus feature proof only
## First-Slice Dimension Inventory
The first slice is locked to these six dimensions only:
1. **Onboarding readiness** — point-in-time signal derived from existing onboarding-readiness truth
2. **Provider connection health** — point-in-time signal derived from existing provider consent and verification truth
3. **Operational stability** — windowed signal derived from failed and stuck `OperationRun` truth using the selected `SystemConsoleWindow`
4. **Governance pressure** — point-in-time signal derived from active high-severity, overdue, or exception-warning findings truth
5. **Review-pack readiness** — narrow mixed signal derived from recent review-pack request context plus the latest relevant `ReviewPack` state
6. **Engagement freshness** — windowed signal derived from existing `product_usage_events`
Any change to this dimension inventory requires an explicit spec update before implementation expands or swaps the slice.
## Health-Level Resolution Rules
- Reuse existing `SystemHealth` levels only: `ok`, `warn`, `critical`, `unknown`
- Overall level precedence is fixed for v1: `critical` > `warn` > `unknown` > `ok`
- Windowed dimensions must honor the selected dashboard time window
- Point-in-time dimensions must not pretend to share the same time basis as windowed dimensions
- Missing or stale source truth must remain explicit and must never silently collapse to `ok`
- Archived workspaces are excluded from active counts, and archived tenants do not contribute source truth into active workspace-health derivation
- Attention-needed workspace ordering is fixed for v1: overall severity desc, non-`ok` dimension count desc, workspace name asc, then workspace id asc
## Review-Pack Readiness Rule
`Recent` means the currently selected dashboard window. Request context comes from existing review-pack request telemetry when available, falling back to a `ReviewPack` created for the same workspace inside that same window.
| Condition | Review-pack readiness level | Notes |
|---|---|---|
| No request context and no relevant pack activity in the selected window | `unknown` | The first slice does not infer periodic review obligations from silence |
| Request context exists in the selected window and no relevant pack is usable yet | `warn` | Covers queued, running, or not-yet-materialized review-pack generation |
| Latest relevant pack in the selected window is ready and not expired | `ok` | A usable recent review pack exists |
| Latest relevant pack in the selected window is failed or expired | `critical` | Recent review-pack work ended unusably and needs attention |
## UI / Surface Guardrail Plan
- **Guardrail scope**: changed surfaces
- **Native vs custom classification summary**: native Filament dashboard plus existing custom system widget family and the existing residual system detail pages
- **Shared-family relevance**: dashboard signals/cards, compact attention list, system-safe deep links, existing `SystemHealth` badges
- **State layers in scope**: page, widget, window query, detail-link state, residual detail follow-up state
- **Handling modes by drift class or surface**: review-mandatory
- **Repository-signal treatment**: review-mandatory
- **Special surface test profiles**: standard-native-filament
- **Required tests or manual smoke**: functional-core, state-contract, follow-up-detail smoke
- **Exception path and spread control**: none
- **Active feature PR close-out entry**: Guardrail
- **List-surface review standard**: `/Users/ahmeddarrazi/Documents/projects/wt-plattform/docs/product/standards/list-surface-review-checklist.md` applies to `CustomerHealthTopWorkspaces`; accepted compact-widget exceptions are no persistence trio, no bulk actions, no row click, and no empty-state CTA
## Shared Pattern & System Fit
- **Cross-cutting feature marker**: yes
- **Systems touched**: `App\Filament\System\Pages\Dashboard`, `App\Filament\System\Pages\Directory\ViewTenant`, `App\Filament\System\Pages\Directory\ViewWorkspace`, `App\Filament\System\Widgets\ControlTowerHealthIndicator`, `App\Filament\System\Widgets\ControlTowerTopOffenders`, `App\Support\ProductTelemetry\ProductTelemetrySummaryQuery`, `App\Services\Providers\ProviderConnectionStateProjector`, `App\Support\SystemConsole\StuckRunClassifier`, existing `Finding` truth, existing `ReviewPack` truth, and platform-plane system link helpers
- **Shared abstractions reused**: dashboard widget composition, dashboard time-window semantics, `BadgeRenderer` for `SystemHealth`, system-safe link helpers, existing residual system detail pages, and existing source-truth owners for provider, telemetry, operations, findings, and review packs
- **New abstraction introduced? why?**: one bounded `CustomerHealth` support namespace with a fixed dimension catalog and one derived summary query is justified because the repo already has isolated source truths but no shared workspace-health derivation path
- **Why the existing abstraction was sufficient or insufficient**: existing abstractions are sufficient for rendering and navigation but insufficient for deriving one explainable cross-domain workspace summary
- **Bounded deviation / spread control**: no page-local health arithmetic, no second badge language, no persistence, and no customer-success surface. All health derivation must converge on the single `CustomerHealth` namespace.
- **Platform-safe link contract**: every attention-needed workspace row must expose exactly one platform-safe next link. If a more specific platform-plane route is not appropriate, the row falls back to the existing system tenant-detail context instead of rendering with no link. Dashboard links into residual detail pages must preserve the selected time window so the follow-up card explains the same top drivers as the dashboard.
## OperationRun UX Impact
- **Touches OperationRun start/completion/link UX?**: no
- **Central contract reused**: N/A
- **Delegated UX behaviors**: N/A
- **Surface-owned behavior kept local**: N/A
- **Queued DB-notification policy**: N/A
- **Terminal notification path**: N/A
- **Exception path**: none
## Provider Boundary & Portability Fit
- **Shared provider/platform boundary touched?**: yes
- **Provider-owned seams**: provider consent and verification outcomes on `ProviderConnection`
- **Platform-core seams**: workspace-health dimensions, overall `SystemHealth` level, dominant reason labels, dashboard widget copy, and system-safe next links
- **Neutral platform terms / contracts preserved**: customer health, workspace health, attention needed, operational stability, engagement freshness, review readiness
- **Retained provider-specific semantics and why**: Microsoft verification and consent states remain provider-owned inputs because they are existing current-release truth already modeled on provider connections
- **Bounded extraction or follow-up path**: `document-in-feature` only if one provider-specific dominant reason still needs text-only explanation instead of a platform-safe link in v1
## Constitution Check
*GATE: Must pass before implementation begins. Re-check after design changes.*
- Inventory-first / snapshots-second: PASS - the slice derives health from existing observed truths only
- Read/write separation: PASS - the slice is read-only
- Graph contract path: PASS - no new Graph calls are added
- RBAC-UX / workspace isolation / tenant isolation: PASS - system dashboard access rules remain authoritative and no tenant/admin viewer is introduced
- Shared pattern reuse / `XCUT-001`: PASS - dashboard widget family, badge semantics, and system link helpers are reused explicitly
- Proportionality / `PROP-001` and `ABSTR-001`: PASS - one bounded derived-query path is the narrowest reusable solution
- Persisted truth / `PERSIST-001`: PASS - no new persistence is introduced
- UI semantics / `UI-SEM-001`: PASS - the slice adds decision-support summaries only and does not create a new workflow hub
- Filament-native UI / `UI-FIL-001`: PASS - the operator-facing impact stays on the existing dashboard widget family
- Livewire v4 / Filament v5: PASS - the slice remains fully inside the current Filament v5 + Livewire v4 stack
- Provider registration location: PASS - no provider registration changes are introduced; Laravel 11+ provider registration stays in `bootstrap/providers.php`
- Global search rule: PASS - no resource or global-search changes are introduced
- Destructive actions: PASS - none added
- Asset strategy: PASS - no new assets are required, so deployment behavior for `filament:assets` remains unchanged
- Test governance / `TEST-GOV-001`: PASS - proof remains in focused unit + feature lanes only
## Test Governance Check
- **Test purpose / classification by changed surface**: Unit for dimension rules and summary derivation; Feature for `/system` dashboard rendering, explainability, authorization, and residual detail follow-up rendering
- **Affected validation lanes**: fast-feedback, confidence
- **Why this lane mix is the narrowest sufficient proof**: the slice is server-driven, read-only, and widget-based; browser automation would duplicate what focused unit and feature tests already prove
- **Narrowest proving command(s)**:
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Support/CustomerHealth/CustomerHealthDimensionCatalogTest.php tests/Unit/Support/CustomerHealth/WorkspaceHealthSummaryQueryTest.php`
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/System/CustomerHealth/CustomerHealthDashboardWidgetsTest.php tests/Feature/System/CustomerHealth/CustomerHealthExplainabilityTest.php tests/Feature/System/CustomerHealth/CustomerHealthAuthorizationTest.php`
- **Fixture / helper / factory / seed / context cost risks**: reuse existing workspaces, tenants, provider connections, onboarding sessions, telemetry events, operation runs, findings, and review packs; avoid browser setup and avoid new heavy support fixtures
- **Expensive defaults or shared helper growth introduced?**: no
- **Heavy-family additions, promotions, or visibility changes**: none
- **Surface-class relief / special coverage rule**: standard-native-filament relief
- **Closing validation and reviewer handoff**: reviewers should confirm unknown handling, dominant reason explainability, platform-safe linking, absence of new persistence, and explicit selected-window semantics
- **Disclosure-ladder proof**: reviewers should also confirm that default-visible dashboard content stays operator-first, raw or support-grade detail remains behind linked surfaces, and the two widgets do not duplicate the same visible decision summary
- **Budget / baseline / trend follow-up**: none expected beyond ordinary feature-local upkeep
- **Review-stop questions**: did the implementation add a persisted score model, customer-facing route, or second badge taxonomy; do unknown and recent-review-pack-request cases degrade honestly?
- **Escalation path**: `reject-or-split` if the slice expands into CRM, billing, predictive scoring, or a new portfolio workbench page
- **Active feature PR close-out entry**: Guardrail
- **Test-governance outcome**: keep
## Project Structure
### Documentation (this feature)
```text
specs/245-customer-health-score/
├── checklists/
│ └── requirements.md
├── spec.md
├── plan.md
└── tasks.md
```
### Source Code (repository root)
```text
apps/platform/
├── app/
│ ├── Filament/
│ │ └── System/
│ │ ├── Pages/
│ │ │ ├── Dashboard.php
│ │ │ └── Directory/
│ │ │ ├── ViewTenant.php
│ │ │ └── ViewWorkspace.php
│ │ └── Widgets/
│ │ ├── CustomerHealthKpis.php
│ │ └── CustomerHealthTopWorkspaces.php
│ ├── Services/
│ │ └── Providers/ProviderConnectionStateProjector.php
│ └── Support/
│ ├── CustomerHealth/
│ │ ├── CustomerHealthDimensionCatalog.php
│ │ └── WorkspaceHealthSummaryQuery.php
│ ├── ProductTelemetry/ProductTelemetrySummaryQuery.php
│ └── SystemConsole/
├── resources/views/filament/system/pages/directory/
│ ├── view-tenant.blade.php
│ └── view-workspace.blade.php
├── resources/views/filament/system/widgets/customer-health-top-workspaces.blade.php
└── tests/
├── Unit/Support/CustomerHealth/
│ ├── CustomerHealthDimensionCatalogTest.php
│ └── WorkspaceHealthSummaryQueryTest.php
└── Feature/System/CustomerHealth/
├── CustomerHealthDetailDecisionTest.php
├── CustomerHealthAuthorizationTest.php
├── CustomerHealthDashboardWidgetsTest.php
└── CustomerHealthExplainabilityTest.php
```
**Structure Decision**: Single Laravel web application. The implementation adds one bounded support namespace and two dashboard widgets only.
## Complexity Tracking
No constitution violations are required. The slice adds one derived summary path only and introduces no new persistence, no new state family, and no new workflow surface.
## Rollout & Risk Controls
- Start on the existing `/system` dashboard only; do not introduce a new portfolio or customer-health page in v1.
- Keep the first-slice dimension inventory fixed at six. Any added dimension requires a spec update.
- Keep review-pack readiness narrow and operational, not programmatic or compliance-heavy.
- Use explicit copy or visual grouping to distinguish point-in-time signals from windowed signals.
- Guarantee one platform-safe next link per attention-needed workspace row by falling back to the existing system tenant-detail context when a more specific platform-plane route is not appropriate.
## Implementation Outline
- Add `App\Support\CustomerHealth\CustomerHealthDimensionCatalog` as the single source for first-slice dimension labels, order, and level precedence rules.
- Add `App\Support\CustomerHealth\WorkspaceHealthSummaryQuery` to derive per-workspace summaries from existing onboarding, provider, telemetry, operations, findings, and review-pack truth.
- Add `App\Filament\System\Widgets\CustomerHealthKpis` for aggregate health counts.
- Add `App\Filament\System\Widgets\CustomerHealthTopWorkspaces` plus a small Blade view for the attention-needed list.
- Add one read-only customer-health decision card to the existing system tenant and workspace residual detail pages using the existing summary query and the same dominant drivers as the dashboard.
- Register both widgets on the existing `App\Filament\System\Pages\Dashboard` and reuse existing system-safe link helpers only.
## Implementation Phases
1. **Foundation**: fixed dimension catalog + derived workspace summary query with core unknown-handling and review-pack-readiness rules + unit proof
2. **Dashboard summary**: aggregate KPI widget + system dashboard registration + feature proof
3. **Attention list**: compact unhealthy-workspace widget + system-safe links + explainability proof
4. **Detail follow-up**: residual system tenant/workspace detail card + preserved window context + follow-up rendering proof
5. **Safety hardening**: archived workspace plus tenant exclusions, deterministic ordering hardening, and authorization proof
## Constitution Check (Post-Design)
Re-check result: PASS. The plan stays derived-only, reuses existing system dashboard and health presentation semantics, keeps Filament v5 + Livewire v4 intact, leaves provider registration in `bootstrap/providers.php` untouched, introduces no global-search or asset changes, adds no destructive actions, and keeps proof in narrow unit + feature lanes only.