Tenant Dashboard — Full Spec
Section

Climacert‑X — Tenant Dashboard (V1)

Version 1.0 • Date: 2025‑08‑23 • Authors: Product Owner (Yazan) / Technical Specs by ChatGPT

1) Goal & Preconditions

Goal

Provide tenants a single, actionable dashboard summarizing facility certification health, device status, alerts and subscriptions using only data the system stores given current retention rules (Device lastReading, Device×Bucket for current month, MonthlyRollup for historical data).

Preconditions

  • Tenant is authenticated and authorized to view tenant resources.
  • Tenant has at least one Facility with geolocation (lat/lon recommended, fallback: address string).
  • System retention guarantees:
    • Device×Bucket rows are retained for current month + purge buffer (month_close + 7d). Do not rely on Device×Bucket for historical trends.
    • MonthlyRollup is the canonical, long-term denormalized store for historical, month-on-month aggregator data.
    • Alerts store exists and keeps at least 3 months of records for dashboard feeds (recommended).
  • Facility objects include: ID, name, type, timezone, lat, lon, currentCertLevel (recommended denorm cache), certExpiryDate, subscriptionStatus.

2) High‑level User Flow (Mermaid)

Mermaid Diagram

Provide/attach the Mermaid diagram here when available.

%% mermaid placeholder
flowchart LR
  A[Open Dashboard] --> B[KPI Summary]
  B --> C[Trends]
  B --> D[Devices vs Alerts]
  C --> E[Top Failures]
  B --> F[Map]
  B --> G[Mini Grid]
  B --> H[Alerts Feed]

3) Widgets — Full Details

Widget A — Dashboard Summary KPIs

Purpose / UX

Show top-level tenant KPIs as tiles at the top of the dashboard. Tiles: Total Devices, Devices with Alerts (30d), Active Certifications, Total Facilities, Total Assets, Active Subscriptions.

Data Fields & Source
  • totalDevices — source: devices table/API. COUNT where tenant_id.
  • devicesWithAlerts30d — source: alerts store. COUNT(DISTINCT device_id) WHERE created_at >= now() - 30d.
  • activeCertifications — source: monthly_rollup or denorm facility.currentCertLevel (count facilities classified as Active).
  • totalFacilities — source: facilities table.
  • totalAssets — source: facility hierarchy (assets/locations/spaces). Sum by tenant.
  • activeSubscriptions — source: subscriptions table (facility subscription statuses).
Backend logic
  • Use cached counts where heavy (summary endpoint caches for 30s–1m).
  • For certifications, read denorm facility.currentCertLevel if available; otherwise compute from latest monthly_rollup month.
API Stub
GET /v1/dashboard/summary?tenantId=TEN_1
Response:
{
  "totalDevices":124,
  "devicesWithAlerts30d":12,
  "activeCertifications":5,
  "totalFacilities":12,
  "totalAssets":34,
  "activeSubscriptions":5
}
Acceptance Criteria

Values reflect tenant data at request time. Tiles link to devices list, alerts view, certifications list, facilities list, subscriptions list.

Edge Cases

Tenant with zero facilities: show zeros & CTA to onboard facility.

Widget B — Devices vs Alerts (last 30 days)

Purpose / UX

Bar chart + sparkline: totalDevices vs devicesWithAlerts (30d) with a 30‑day alerts timeseries sparkline. Clicks filter device table to devices with alerts.

Data Fields & Source
  • devices: devices table (device.id, device.name, device.status, device.lastReading).
  • alertsTimeseries: alerts table (created_at, severity, device_id).
Backend logic
  • totalDevices = COUNT(devices)
  • devicesWithAlerts = COUNT(DISTINCT device_id WHERE created_at >= now()-30d)
  • Timeseries by grouping alerts by DATE(created_at)
API Stub
GET /v1/dashboard/devices-overview?tenantId=TEN_1&days=30
Response:
{
  "totalDevices":124,
  "devicesWithAlerts":12,
  "alertsTimeseries":[{"date":"2025-07-25","count":3}, ...]
}
Acceptance Criteria

Clicking devicesWithAlerts opens a devices table filtered to those that had alerts; include lastReading.

Edge Cases

Toggle to count unique device-days vs raw alerts.

Filters
  • Facility (all / specific)
  • Severity (info / warn / critical)
  • Parameter (CO2, Temp, Humidity, etc.)
  • Date range (default 30 days)

Widget C — Certifications Summary & KPIs

Purpose / UX

Show counts by certification state and a small expiring list with CTA to renew.

Data Fields & Source

facility.currentCertLevel (preferred cached field) OR compute from monthly_rollup for latest month. Fields: facilityId, name, certLevel, certExpiryDate, certStartDate, subscriptionStatus.

Backend logic

If facility.currentCertLevel exists, use it; otherwise derive from latest monthly_rollup per facility using certification gates. Count by state.

API Stub
GET /v1/dashboard/certifications?tenantId=TEN_1
Response:
{
  "totals": { "platinum":2, "gold":5, "silver":3, "pending":2, "grace":1, "suspended":0 },
  "expiring": [{"facilityId":"F3","name":"Shop C","daysRemaining":25}, ...]
}
Acceptance Criteria

Counts reflect latest computed levels. Expiring list ordered by daysRemaining ascending.

Filters

Level (platinum/gold/silver) • Expiring (≤90/≤30/≤7 days)

Widget D — Certification Trend (line chart)

Purpose / UX

Show month-by-month counts per certification level for N months (default 12). Toggle to show/hide levels.

Why use MonthlyRollup

Historical trends MUST use monthly_rollup as Device×Bucket is ephemeral.

Data & Backend

For each month M in window: for each facility fetch monthly_rollup for M, aggregate within_pct across required parameter families, apply gates to classify level, tally counts per level.

API Stub
GET /v1/dashboard/cert-trend?tenantId=TEN_1&months=12
Response:
{
  "months": ["2025-08","2025-07", ...],
  "series": {
    "platinum": [2,3,2,...],
    "gold": [5,4,5,...],
    "silver": [10,9,11,...],
    "paused": [1,1,0,...]
  }
}
Acceptance Criteria

For each month, classification equals facility-level gate application from monthly_rollup.

Filters

Months window (3/6/12/24) • Facility type (Retail/Office/School)

Widget E — Top 5 Facilities failing thresholds

Purpose / UX

Table listing worst-performing 5 facilities (lowest within_pct or worst primary failing parameter) for current month.

Data Fields & Source

monthly_rollup (scope='facility', month=currentMonth): param, within_pct, expected, actual, flags.

Backend logic

Compute failureScore (configurable), sort ascending, return top 5. Include primaryFailParam = param with lowest within_pct.

API Stub
GET /v1/dashboard/top-failures?tenantId=TEN_1&limit=5
Response:
[
  { "facilityId":"F1","name":"Shop A","primaryFailParam":"CO2","withinPct":58.4,"expected":720,"actual":432 },
  ...
]
Acceptance Criteria

Top 5 reflect current month monthly_rollup. Missing monthly_rollup → mark as insufficient data.

Filters

Facility type • Parameter family • Only expected ≥ X

Widget F — Device lastReading mini‑grid

Purpose / UX

Compact card grid showing N primary devices. Each tile: Device name, primary param & value, timestamp (facility local TZ), online/offline badge.

Data Fields & Source

device.lastReading on devices: { param, value, timestamp } and device.lastSeen / device.status.

API Stub
GET /v1/dashboard/device-mini-grid?tenantId=TEN_1&limit=12
Response:
{ "items": [ { "deviceId":"D1","name":"CO2 Sensor 1","param":"CO2","value":412,"timestamp":"2025-08-22T10:00:00Z","status":"online","assetId":"A1" }, ... ] }
Acceptance Criteria

Tile displays lastReading.value and timestamp in facility timezone.

Filters

Facility • Parameter • Status (online/offline)

Widget G — Facility Map (pins color‑coded by certification)

Purpose / UX

Map with facility pins. Color indicates certification status. Hover: facility name, cert level, days remaining, link to facility page.

Data Fields & Source

facilities: facilityId, name, lat, lon, timezone, facility.currentCertLevel (preferred), certExpiryDate, subscriptionStatus. Fallback: compute from latest monthly_rollup.

Backend logic

Return geo points with certLevel and daysRemaining. Pin color rules applied in UI.

API Stub
GET /v1/dashboard/facility-map?tenantId=TEN_1
Response:
[ {"facilityId":"F1","name":"Shop A","lat":25.123,"lon":55.321,"certLevel":"Gold","expiry":"2026-06-01","daysRemaining":285}, ... ]
Filters

Level (All / Platinum / Gold / Silver / Payment Due / Suspended) • Expiring (≤90/30/7 days) • Facility type

Edge Cases

Missing lat/lon → facility appears in side list with CTA to add coordinates.

Widget H — Alerts feed (recent)

Purpose / UX

Rolling feed of recent alerts for tenant with severity badges and quick links.

Data Fields & Source

alerts: alert_id, created_at, device_id, facility_id, param, value, severity, message.

API Stub
GET /v1/facilities/{id}/alerts?tenantId&since=2025-07-01
Response: [ { alert objects... } ]
Filters

Severity • Parameter • Facility • Date range

4) Filters & UI Controls (global)

  • Date Range: 7d, 30d, 90d, 12 months or custom.
  • Facility: All / specific (multi-select).
  • Level: Platinum / Gold / Silver / PaymentDue / Suspended.
  • Facility Type: Retail / Office / School / Warehouse / Others.
  • Parameter: CO2 / Temperature / Humidity / VOC / PM2.5 / Water Temp / Cold Chain.
  • Severity: info / warn / critical.
  • Device status: online / offline / unknown.
  • Export: CSV/PNG for charts & tables.

UX notes: Prefer client-side filter application; heavy filters on MonthlyRollup should call server endpoints. Persist filter state in URL.

5) Data & Validation Cheatsheet

Core tables / stores (canonical)

facilities
FieldTypeNotes
idstring
namestring
tenant_idstring
typestring
timezonestring (IANA)e.g., Asia/Dubai
latfloat
lonfloat
currentCertLevelenumplatinum/gold/silver/pending/grace/suspended (denorm)
certExpiryDatedate
created_at, updated_attimestamp
devices
FieldTypeNotes
id, externalId, name, asset_id, facility_id, tenant_idvarious
lastReadingjson{ param, value, unit, timestamp }
lastSeentimestamp
statusenumonline/offline/unknown
alerts
FieldTypeNotes
id, tenant_id, facility_id, device_idstring
param, value, severity, messagevarious
created_attimestamp
device_x_bucket (ephemeral — current month only)
FieldTypeNotes
device_id, paramstring
bucket_start_utctimestamp
value_last, withinfloat/boolPurge at month_close + 7d
monthly_rollup (denormalized, kept forever)
FieldTypeNotes
scopeenumfacility/asset
scope_idstringfacilityId / assetId
monthdateYYYY‑MM‑01
paramstring
expectedintexpected buckets/readings
actualintactual buckets/readings
within_countint
within_pctfloat
flagsjson{ gate: 'silver', notes: '...' }
subscriptions
FieldTypeNotes
idstring
facility_idstring
statusenumactive/pending_activation/paused/cancelled
nextBillingDatedate
graceEndsAttimestamp

6) API Surface — Full Stubs (JSON shapes)

All endpoints expect authenticated requests with tenant-scoped tokens. Pagination via limit/offset or cursor.

1) GET /v1/dashboard/summary?tenantId=
Response:
{ totalDevices:int, devicesWithAlerts30d:int, activeCertifications:int, totalFacilities:int, totalAssets:int, activeSubscriptions:int }

2) GET /v1/dashboard/devices-overview?tenantId=&days=30&facilityId=
Response:
{ totalDevices:int, devicesWithAlerts:int, alertsTimeseries:[{date:string,count:int}], devices:[{deviceId,name,lastReading,timestamp,status}] }

3) GET /v1/dashboard/certifications?tenantId=&filter=
Response:
{ totals: { platinum:int, gold:int, silver:int, pending:int, grace:int, suspended:int }, expiring:[{facilityId,name,daysRemaining,expiry}] }

4) GET /v1/dashboard/cert-trend?tenantId=&months=12 — uses MonthlyRollup
Response:
{ months:["YYYY-MM"], series:{ platinum:[int,...], gold:[...], silver:[...], paused:[...] } }

5) GET /v1/dashboard/top-failures?tenantId=&limit=5 — uses MonthlyRollup current month
Response:
[ { facilityId, name, primaryFailParam, withinPct, expected, actual, notes } ]

6) GET /v1/dashboard/device-mini-grid?tenantId=&limit=12&facilityId=¶m=
Response:
{ items:[{ deviceId, name, assetId, param, value, unit, timestamp, status }] }

7) GET /v1/dashboard/facility-map?tenantId=&filters=
Response:
[ { facilityId, name, lat, lon, certLevel, expiry, daysRemaining, type } ]

8) GET /v1/facilities/{id}/alerts?since=&severity=&limit=
Response: [{alert objects...}]

7) Non‑Functional Requirements

Performance

  • Dashboard summary API p95 ≤ 300ms (tenants ≤ 100 facilities) when cached (30s).
  • Map endpoint (≤500 pins) p95 ≤ 500ms; leverage facility.currentCertLevel cache.

Scalability

  • MonthlyRollup indexed by (tenant_id, scope, month). Use precomputed aggregates for trends.

Availability & Reliability

  • Alerts feed eventually consistent; show Last updated timestamp on widgets.

Security

  • Tenant-scoped tokens limit data exposure; superadmin portfolio endpoints require elevated privileges.

Retention

  • Device×Bucket purge: month close + 7d. MonthlyRollup persistent.

8) UX Copy & Errors (EN / AR samples)

KPI tiles

EN: Active Certifications: {n} — Click to view facilities.

AR: الشهادات النشطة: {n} — اضغط لعرض المرافق.

Map tooltip

EN: {FacilityName} — {CertLevel} (expires in {daysRemaining} days)

AR: {FacilityName} — {CertLevel} (تنتهي بعد {daysRemaining} يوم)

Errors

  • 500: “Something went wrong — please try again later.” / 500: «حدث خطأ — يرجى المحاولة لاحقًا».
  • 403 (tenant not authorized): “You don’t have permission to see this dashboard.” / «ليس لديك إذن لعرض لوحة التحكم هذه».
  • Map missing coords: “This facility has no coordinates. Add location to appear on map.” / «هذه المنشأة لا تحتوي على إحداثيات. أضف الموقع للظهور على الخريطة.»

9) Edge Cases & Handling

  • Incomplete monthly_rollup: indicate insufficient data; exclude from some aggregates or mark low-confidence.
  • Ties in Top‑5: tie-breaker by actual/expected completeness then by severity of primaryFailParam.
  • Devices with no lastReading: show “No readings” and lastSeen if available.
  • Missing geolocation: show facility in side list with CTA.
  • Large tenants: pagination & server-side filtering for device lists; map clustering for >500 pins.
  • Timezones: render timestamps in facility’s timezone.

10) Acceptance Criteria (Given / When / Then)

  1. A. Dashboard loads summary KPIs — Given tenant has facilities; When user opens Dashboard; Then show KPI tiles with counts and each tile links to relevant lists.
  2. B. Certification Trend correctness — Given monthly_rollup contains months M1..Mn; When user selects 12 months; Then series counts equal facility-level classification derived from monthly_rollup for each month.
  3. C. Top 5 failures computed from monthly_rollup — Given monthly_rollup for current month is present; When user opens Top‑5 widget; Then show top 5 by lowest within_pct and primaryFailParam.
  4. D. Map pins color‑coded — Given each facility has cert status cached or computed; When map loads; Then pins colors reflect cert level and hover shows daysRemaining.
  5. E. Device mini‑grid accuracy — Given device.lastReading exists; When mini‑grid loads; Then tiles display lastReading value and timestamp in facility timezone.

11) Analytics Events (recommended)

EventProperties
dashboard_viewedtenantId, userId, timestamp
widget_openedwidgetId, tenantId, userId
filter_appliedtenantId, userId, filterType, filterValue
map_pin_clickedfacilityId, tenantId, userId
device_tile_clickeddeviceId, tenantId, userId
export_csvwidgetId, tenantId, userId

Include these events in telemetry with low‑sensitivity payloads.

12) Implementation Notes & Recommendations

  • Cache facility.currentCertLevel: compute nightly from monthly_rollup; store on facilities table to speed map & KPI reads.
  • Precompute trend aggregates: maintain cert_trend table per tenant‑month to avoid heavy on‑the‑fly queries.
  • Map clustering for large footprints; client‑side pin color mapping.
  • API versioning: prefix with /v1/dashboard; follow semantic versioning for incompatible changes.
  • Testing: unit tests for classification gates, e2e tests for widget drilldowns, and data‑driven tests for Top‑5 ranking.

13) Implementation Checklist (Dev ticket)

  • Build API endpoints (see section 6)
  • Add caching for summary endpoint (30s)
  • Implement map endpoint + clustering
  • Implement device mini‑grid endpoint
  • Implement certification trend precompute job (cron/nightly)
  • Add analytics instrumentation
  • Add unit & integration tests

14) Appendix — Certification gates (default rules)

  • IAQ/Odor: Silver ≥ 70% within; Gold ≥ 80%; Platinum ≥ 90%
  • Cold/Water: require ≥ 99% within for active certifications (higher priority)
  • Expiry warnings: 90 days / 30 days / 7 days thresholds
  • Offline thresholds: IAQ/Odor device offline if lastSeen > 2h; CO2 if > 30m