FEAT_HR_DASBOARD_MISSED_KPI_ADDED
This commit is contained in:
parent
0c5e46736d
commit
0d6d8f6555
@ -46,6 +46,9 @@ class ClaimsCollectionV2DashboardModel extends Model
|
||||
229 => 'cataract_avg_exceeded_amount',
|
||||
230 => 'hospital_city_wise_si_limit_cataract',
|
||||
231 => 'total_incurred_by_cliam_status',
|
||||
232 => 'inception_emp_lives',
|
||||
233 => 'current_emp_lives',
|
||||
234 => 'claim_value_by_month',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -83,6 +86,9 @@ class ClaimsCollectionV2DashboardModel extends Model
|
||||
'cataract_avg_exceeded_amount' => 'Cataract avg exceeded amount',
|
||||
'hospital_city_wise_si_limit_cataract' => 'Hospital city wise SI Limit - Cataract',
|
||||
'total_incurred_by_cliam_status' => 'Total Incurred by Cliam Status ',
|
||||
'inception_emp_lives' => 'Inception Employees & Lives',
|
||||
'current_emp_lives' => 'Current Employees & Lives',
|
||||
'claim_value_by_month' => 'Claim Value by Month',
|
||||
];
|
||||
|
||||
protected function runKpiQuery(string $sql, int $policyId): array
|
||||
@ -2083,6 +2089,92 @@ WHERE tm.client_policy_id = :policy_id:
|
||||
AND tm.is_active = 1
|
||||
GROUP BY tcs.display_name, totals.total_count, totals.total_value
|
||||
-- ORDER BY claim_count DESC;
|
||||
SQL;
|
||||
return $this->runKpiQuery($sql, $policyId);
|
||||
}
|
||||
|
||||
/** Metabase #232: Inception Employees & Lives */
|
||||
public function inception_emp_lives(int $policyId): array
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT
|
||||
COUNT(DISTINCT CASE
|
||||
WHEN e.relationship = 'Self' THEN e.id
|
||||
END) AS inception_emp,
|
||||
|
||||
COUNT(DISTINCT e.id) AS inception_lives
|
||||
|
||||
FROM employee_polices ep
|
||||
JOIN employees e ON e.id = ep.employee_id
|
||||
WHERE ep.client_policy_id = :policy_id:
|
||||
AND LOWER(e.change_event) LIKE '%inception%'
|
||||
AND ep.is_active = 1
|
||||
AND e.is_active = 1;
|
||||
SQL;
|
||||
return $this->runKpiQuery($sql, $policyId);
|
||||
}
|
||||
|
||||
/** Metabase #233: Current Employees & Lives */
|
||||
public function current_emp_lives(int $policyId): array
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT
|
||||
COUNT(DISTINCT CASE
|
||||
WHEN e.relationship = 'Self' THEN e.id
|
||||
END) AS current_emp,
|
||||
|
||||
COUNT(DISTINCT e.id) AS current_lives,
|
||||
|
||||
ROUND(
|
||||
COUNT(DISTINCT e.id)
|
||||
/ NULLIF(COUNT(DISTINCT CASE
|
||||
WHEN e.relationship = 'Self' THEN e.id
|
||||
END), 0)
|
||||
, 2) AS avg_family_size
|
||||
|
||||
FROM employee_polices ep
|
||||
JOIN employees e ON e.id = ep.employee_id
|
||||
WHERE ep.client_policy_id = :policy_id:
|
||||
AND ep.status = 'active'
|
||||
AND ep.is_active = 1
|
||||
AND e.emp_status = 'active'
|
||||
AND e.is_active = 1;
|
||||
SQL;
|
||||
return $this->runKpiQuery($sql, $policyId);
|
||||
}
|
||||
|
||||
/** Metabase #234: Claim Value by Month */
|
||||
public function claim_value_by_month(int $policyId): array
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT
|
||||
DATE_FORMAT(tm.created_at, '%b %Y') AS claim_month,
|
||||
DATE_FORMAT(tm.created_at, '%Y-%m') AS month_sort,
|
||||
-- COUNT(tm.id) AS claim_count,
|
||||
-- CONCAT(ROUND(COUNT(tm.id) / totals.total_count * 100, 2), '%') AS count_pct,
|
||||
|
||||
COALESCE(SUM(CAST(NULLIF(tm.claim_amount, '') AS DECIMAL(15,2))), 0)
|
||||
AS claim_value
|
||||
-- CONCAT(ROUND(
|
||||
-- COALESCE(SUM(CAST(NULLIF(tm.claim_amount, '') AS DECIMAL(15,2))), 0)
|
||||
-- / NULLIF(totals.total_value, 0) * 100
|
||||
-- , 2), '%') AS value_pct
|
||||
FROM ticket_master tm
|
||||
JOIN (
|
||||
SELECT
|
||||
COUNT(id) AS total_count,
|
||||
COALESCE(SUM(CAST(NULLIF(claim_amount, '') AS DECIMAL(15,2))), 0) AS total_value
|
||||
FROM ticket_master
|
||||
WHERE client_policy_id = :policy_id: AND is_active = 1
|
||||
) totals ON 1=1
|
||||
WHERE tm.client_policy_id = :policy_id:
|
||||
AND tm.is_active = 1
|
||||
GROUP BY
|
||||
DATE_FORMAT(tm.created_at, '%b %Y'),
|
||||
DATE_FORMAT(tm.created_at, '%Y-%m'),
|
||||
totals.total_count,
|
||||
totals.total_value
|
||||
ORDER BY month_sort;
|
||||
SQL;
|
||||
return $this->runKpiQuery($sql, $policyId);
|
||||
}
|
||||
|
||||
205
hr-dashboard.md
205
hr-dashboard.md
@ -1,22 +1,25 @@
|
||||
# Claims Collection V2 — HR Dashboard API
|
||||
|
||||
**Created:** 2026-06-02
|
||||
**Updated:** 2026-06-03
|
||||
**Controller:** `App\Controllers\ClaimsCollectionV2DashboardController`
|
||||
**Model:** `App\Models\ClaimsCollectionV2DashboardModel`
|
||||
**Source queries:** `metabase_raw_queries.csv` → collection `Claims Collection V2`
|
||||
**Base app URL (local):** `https://localhost/PHP828APPS/ruc/nhance/index.php`
|
||||
**Source queries:** `metabase_raw_queries.csv` → collection `Claims Collection V2` (31 original) + 3 HR exposure/trend KPIs added in-model
|
||||
**Base app URL (local):** `https://localhost/PHP828APPS/ruc/nhance/index.php`
|
||||
**Total KPIs:** 34
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Each Metabase question in the CSV is a separate PHP method on the model.
|
||||
Each KPI is a separate PHP method on the model, registered in `KPI_MAP` (Metabase question id → slug) and `KPI_LABELS` (slug → display label).
|
||||
|
||||
The API exposes them in three ways:
|
||||
|
||||
| What | URL fragment | Use case |
|
||||
|------|-------------|----------|
|
||||
| **Single KPI** | `kpi/{slug or Metabase id}` | FE loads one card at a time |
|
||||
| **All KPIs** | `all` | FE loads entire dashboard in one call |
|
||||
| **All KPIs** | `all` | FE loads entire dashboard in one call (34 KPIs) |
|
||||
| **Debug / preview** | `debug` / `preview` | Admin checks raw output in browser |
|
||||
|
||||
All endpoints require `client_policy` or `client_policy_id` as a query param
|
||||
@ -36,7 +39,7 @@ Prefix: `util/claims-collection-v2`
|
||||
| GET | `util/claims-collection-v2/preview/{policy_id}` | `::preview` | Same, policy in URL |
|
||||
| GET | `util/claims-collection-v2/debug` | `::debug` | Raw JSON dump (all KPIs) |
|
||||
| GET | `util/claims-collection-v2/debug/{policy_id}` | `::debug` | Same, policy in URL |
|
||||
| GET | `util/claims-collection-v2/all` | `::all` | JSON — all 31 KPIs |
|
||||
| GET | `util/claims-collection-v2/all` | `::all` | JSON — all 34 KPIs |
|
||||
| GET | `util/claims-collection-v2/kpi/{slug\|id}` | `::kpi` | JSON — single KPI |
|
||||
|
||||
**Filters applied:** `authMVC`, `AclFilter`, `HttpRequestLog`, `Cors`, `SecurityInputFilter`
|
||||
@ -49,7 +52,7 @@ Prefix: `employeeRest/claims-collection-v2`
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| GET | `employeeRest/claims-collection-v2/all` | `::all` | JSON — all 31 KPIs |
|
||||
| GET | `employeeRest/claims-collection-v2/all` | `::all` | JSON — all 34 KPIs |
|
||||
| GET | `employeeRest/claims-collection-v2/kpi/{slug\|id}` | `::kpi` | JSON — single KPI |
|
||||
| GET | `employeeRest/claims-collection-v2/preview` | `::preview` | UI grid (FE debug) |
|
||||
| GET | `employeeRest/claims-collection-v2/preview/{policy_id}` | `::preview` | Same, policy in URL |
|
||||
@ -85,7 +88,7 @@ GET /index.php/util/claims-collection-v2/preview?client_policy=4687
|
||||
# Preview with policy in URL
|
||||
GET /index.php/util/claims-collection-v2/preview/4687
|
||||
|
||||
# Raw JSON — all 31 KPIs
|
||||
# Raw JSON — all 34 KPIs
|
||||
GET /index.php/util/claims-collection-v2/debug?client_policy=4687
|
||||
|
||||
# Raw JSON — single KPI by slug
|
||||
@ -93,6 +96,11 @@ GET /index.php/util/claims-collection-v2/kpi/incurred_ratio?client_policy=4687
|
||||
|
||||
# Raw JSON — single KPI by Metabase question id
|
||||
GET /index.php/util/claims-collection-v2/kpi/207?client_policy=4687
|
||||
|
||||
# Exposure KPIs (added 2026-06-03)
|
||||
GET /index.php/util/claims-collection-v2/kpi/inception_emp_lives?client_policy=4687
|
||||
GET /index.php/util/claims-collection-v2/kpi/current_emp_lives?client_policy=4687
|
||||
GET /index.php/util/claims-collection-v2/kpi/claim_value_by_month?client_policy=4687
|
||||
```
|
||||
|
||||
---
|
||||
@ -113,7 +121,13 @@ X-App-Signature: <app_signature>
|
||||
```
|
||||
|
||||
```http
|
||||
GET /index.php/employeeRest/claims-collection-v2/kpi/207?client_policy=4687
|
||||
GET /index.php/employeeRest/claims-collection-v2/kpi/inception_emp_lives?client_policy=4687
|
||||
Authorization: Bearer <jwt_token>
|
||||
X-App-Signature: <app_signature>
|
||||
```
|
||||
|
||||
```http
|
||||
GET /index.php/employeeRest/claims-collection-v2/kpi/234?client_policy=4687
|
||||
Authorization: Bearer <jwt_token>
|
||||
X-App-Signature: <app_signature>
|
||||
```
|
||||
@ -122,7 +136,7 @@ X-App-Signature: <app_signature>
|
||||
|
||||
## Sample responses
|
||||
|
||||
### `kpi/{slug}` or `kpi/{id}`
|
||||
### `kpi/{slug}` — single-row KPI
|
||||
|
||||
```json
|
||||
{
|
||||
@ -137,6 +151,54 @@ X-App-Signature: <app_signature>
|
||||
}
|
||||
```
|
||||
|
||||
### `kpi/inception_emp_lives` — exposure (single row)
|
||||
|
||||
```json
|
||||
{
|
||||
"status": true,
|
||||
"policy_id": 4687,
|
||||
"kpi_id": 232,
|
||||
"kpi": "inception_emp_lives",
|
||||
"label": "Inception Employees & Lives",
|
||||
"rows": [
|
||||
{ "inception_emp": "150", "inception_lives": "420" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### `kpi/current_emp_lives` — exposure (single row)
|
||||
|
||||
```json
|
||||
{
|
||||
"status": true,
|
||||
"policy_id": 4687,
|
||||
"kpi_id": 233,
|
||||
"kpi": "current_emp_lives",
|
||||
"label": "Current Employees & Lives",
|
||||
"rows": [
|
||||
{ "current_emp": "145", "current_lives": "410", "avg_family_size": "2.83" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### `kpi/claim_value_by_month` — time series (multi-row)
|
||||
|
||||
```json
|
||||
{
|
||||
"status": true,
|
||||
"policy_id": 4687,
|
||||
"kpi_id": 234,
|
||||
"kpi": "claim_value_by_month",
|
||||
"label": "Claim Value by Month",
|
||||
"rows": [
|
||||
{ "claim_month": "Jan 2024", "month_sort": "2024-01", "claim_value": "125000.00" },
|
||||
{ "claim_month": "Feb 2024", "month_sort": "2024-02", "claim_value": "98000.50" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> Sort charts by `month_sort` (ISO `YYYY-MM`), not `claim_month` (display label).
|
||||
|
||||
### `all`
|
||||
|
||||
```json
|
||||
@ -160,6 +222,23 @@ X-App-Signature: <app_signature>
|
||||
"id": 207,
|
||||
"label": "Incurred Ratio",
|
||||
"rows": [{ "incurred_ratio": "72.34%" }]
|
||||
},
|
||||
"inception_emp_lives": {
|
||||
"id": 232,
|
||||
"label": "Inception Employees & Lives",
|
||||
"rows": [{ "inception_emp": "150", "inception_lives": "420" }]
|
||||
},
|
||||
"current_emp_lives": {
|
||||
"id": 233,
|
||||
"label": "Current Employees & Lives",
|
||||
"rows": [{ "current_emp": "145", "current_lives": "410", "avg_family_size": "2.83" }]
|
||||
},
|
||||
"claim_value_by_month": {
|
||||
"id": 234,
|
||||
"label": "Claim Value by Month",
|
||||
"rows": [
|
||||
{ "claim_month": "Jan 2024", "month_sort": "2024-01", "claim_value": "125000.00" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -183,80 +262,102 @@ X-App-Signature: <app_signature>
|
||||
"message": "Unknown KPI. Pass Metabase id or method slug.",
|
||||
"allowed": {
|
||||
"181": "policy_exposure_summary",
|
||||
"207": "incurred_ratio"
|
||||
"207": "incurred_ratio",
|
||||
"232": "inception_emp_lives",
|
||||
"233": "current_emp_lives",
|
||||
"234": "claim_value_by_month"
|
||||
}
|
||||
}
|
||||
```
|
||||
**HTTP 404**
|
||||
**HTTP 404** — `allowed` lists the full `KPI_MAP` (34 entries).
|
||||
|
||||
---
|
||||
|
||||
## All 31 KPIs
|
||||
## All 34 KPIs
|
||||
|
||||
| Metabase ID | Method slug | Label |
|
||||
|-------------|-------------|-------|
|
||||
| 181 | `policy_exposure_summary` | POLICY & EXPOSURE SUMMARY |
|
||||
| 185 | `premium_as_on_date` | PREMIUM AS ON DATE |
|
||||
| 186 | `claims_experience_summary` | CLAIMS EXPERIENCE SUMMARY |
|
||||
| 190 | `claim_amount_by_gender` | Claim Amount by Gender |
|
||||
| 191 | `age_band` | Age Band |
|
||||
| 194 | `top_5_hospitals_by_incurred_amount` | Top 5 Hospitals by Incurred amount |
|
||||
| 197 | `claims_incidence_rate` | Claims Incidence Rate |
|
||||
| 198 | `policy_start_date` | Policy Start Date |
|
||||
| 199 | `policy_end_date` | Policy End Date |
|
||||
| 200 | `insurer` | Insurer |
|
||||
| 201 | `tpa` | TPA |
|
||||
| 203 | `earned_premium` | Earned Premium |
|
||||
| 204 | `total_claims` | Total Claims |
|
||||
| 206 | `incurred_amount` | Incurred Amount |
|
||||
| 207 | `incurred_ratio` | Incurred Ratio |
|
||||
| 208 | `projected_claims` | Projected Claims |
|
||||
| 209 | `projected_ratio` | Projected Ratio |
|
||||
| 213 | `total_reimbursement_amount` | Total Reimbursement Amount |
|
||||
| 214 | `total_reimbursement_amt_pct` | Total Reimbursement Amt % |
|
||||
| 215 | `cashless_claim_amt` | Cashless Claim Amt |
|
||||
| 216 | `cashless_claim_amt_pct` | Cashless Claim Amt % |
|
||||
| 217 | `total_incurred_by_city` | Total Incurred by city |
|
||||
| 219 | `claim_amount_by_claim_status` | Claim Amount by Claim Status |
|
||||
| 220 | `hospitals_in_detail` | Hospitals in detail |
|
||||
| 221 | `hospital_city_wise_si_limit_pregnancy` | Hospital city wise SI Limit - Pregnancy |
|
||||
| 224 | `s_pregnancy_normal_delivery_exceeded_amt` | S-PREGNANCY - NORMAL DELIVERY Exceeded Amt |
|
||||
| 225 | `s_pregnancy_c_sec_avg_exceeded_amt` | S-Pregnancy C-Sec avg exceeded amt |
|
||||
| 228 | `cataract_exceeded_claim_amount` | Cataract exceeded claim amount |
|
||||
| 229 | `cataract_avg_exceeded_amount` | Cataract avg exceeded amount |
|
||||
| 230 | `hospital_city_wise_si_limit_cataract` | Hospital city wise SI Limit - Cataract |
|
||||
| 231 | `total_incurred_by_cliam_status` | Total Incurred by Cliam Status |
|
||||
| Metabase ID | Method slug | Label | Rows |
|
||||
|-------------|-------------|-------|------|
|
||||
| 181 | `policy_exposure_summary` | POLICY & EXPOSURE SUMMARY | single |
|
||||
| 185 | `premium_as_on_date` | PREMIUM AS ON DATE | single |
|
||||
| 186 | `claims_experience_summary` | CLAIMS EXPERIENCE SUMMARY | single |
|
||||
| 190 | `claim_amount_by_gender` | Claim Amount by Gender | multi |
|
||||
| 191 | `age_band` | Age Band | multi |
|
||||
| 194 | `top_5_hospitals_by_incurred_amount` | Top 5 Hospitals by Incurred amount | multi |
|
||||
| 197 | `claims_incidence_rate` | Claims Incidence Rate | single |
|
||||
| 198 | `policy_start_date` | Policy Start Date | single |
|
||||
| 199 | `policy_end_date` | Policy End Date | single |
|
||||
| 200 | `insurer` | Insurer | single |
|
||||
| 201 | `tpa` | TPA | single |
|
||||
| 203 | `earned_premium` | Earned Premium | single |
|
||||
| 204 | `total_claims` | Total Claims | single |
|
||||
| 206 | `incurred_amount` | Incurred Amount | single |
|
||||
| 207 | `incurred_ratio` | Incurred Ratio | single |
|
||||
| 208 | `projected_claims` | Projected Claims | single |
|
||||
| 209 | `projected_ratio` | Projected Ratio | single |
|
||||
| 213 | `total_reimbursement_amount` | Total Reimbursement Amount | single |
|
||||
| 214 | `total_reimbursement_amt_pct` | Total Reimbursement Amt % | single |
|
||||
| 215 | `cashless_claim_amt` | Cashless Claim Amt | single |
|
||||
| 216 | `cashless_claim_amt_pct` | Cashless Claim Amt % | single |
|
||||
| 217 | `total_incurred_by_city` | Total Incurred by city | multi |
|
||||
| 219 | `claim_amount_by_claim_status` | Claim Amount by Claim Status | multi |
|
||||
| 220 | `hospitals_in_detail` | Hospitals in detail | multi |
|
||||
| 221 | `hospital_city_wise_si_limit_pregnancy` | Hospital city wise SI Limit - Pregnancy | multi |
|
||||
| 224 | `s_pregnancy_normal_delivery_exceeded_amt` | S-PREGNANCY - NORMAL DELIVERY Exceeded Amt | multi |
|
||||
| 225 | `s_pregnancy_c_sec_avg_exceeded_amt` | S-Pregnancy C-Sec avg exceeded amt | multi |
|
||||
| 228 | `cataract_exceeded_claim_amount` | Cataract exceeded claim amount | multi |
|
||||
| 229 | `cataract_avg_exceeded_amount` | Cataract avg exceeded amount | multi |
|
||||
| 230 | `hospital_city_wise_si_limit_cataract` | Hospital city wise SI Limit - Cataract | multi |
|
||||
| 231 | `total_incurred_by_cliam_status` | Total Incurred by Cliam Status | multi |
|
||||
| 232 | `inception_emp_lives` | Inception Employees & Lives | single |
|
||||
| 233 | `current_emp_lives` | Current Employees & Lives | single |
|
||||
| 234 | `claim_value_by_month` | Claim Value by Month | multi |
|
||||
|
||||
> **Note:** KPIs 214 and 216 were renamed from the auto-generated slug to avoid collision with 213 and 215.
|
||||
|
||||
> **Note:** IDs **232–234** were assigned in-app for KPIs added outside the original Metabase CSV export. Confirm real Metabase question IDs and update `KPI_MAP` if they differ.
|
||||
|
||||
---
|
||||
|
||||
## Output columns — KPIs 232–234
|
||||
|
||||
| Slug | Row fields | Description |
|
||||
|------|------------|-------------|
|
||||
| `inception_emp_lives` | `inception_emp`, `inception_lives` | Distinct Self employees and all lives at inception (`change_event` contains inception) |
|
||||
| `current_emp_lives` | `current_emp`, `current_lives`, `avg_family_size` | Active employees/lives; `avg_family_size` = lives ÷ employees (2 dp) |
|
||||
| `claim_value_by_month` | `claim_month`, `month_sort`, `claim_value` | Monthly sum of `claim_amount`; ordered by `month_sort` |
|
||||
|
||||
---
|
||||
|
||||
## Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `app/Models/ClaimsCollectionV2DashboardModel.php` | 31 KPI query methods, `KPI_MAP`, `KPI_LABELS`, `getAllKpis()`, `getKpi()` |
|
||||
| `app/Models/ClaimsCollectionV2DashboardModel.php` | 34 KPI query methods, `KPI_MAP`, `KPI_LABELS`, `getAllKpis()`, `getKpi()` |
|
||||
| `app/Controllers/ClaimsCollectionV2DashboardController.php` | `kpi()`, `all()`, `preview()`, `debug()` |
|
||||
| `app/Views/claims_collection_v2_dashboard.php` | Admin/FE debug preview UI (KPI card grid) |
|
||||
| `app/Config/Routes.php` | Both route groups (search `claims-collection-v2`) |
|
||||
| `tests/smoke_claims_collection_v2.php` | CLI smoke test — run: `php tests/smoke_claims_collection_v2.php 4687` |
|
||||
| `metabase_raw_queries.csv` | Source of truth for all SQL queries |
|
||||
| `metabase_raw_queries.csv` | Source of truth for original Metabase SQL queries |
|
||||
| `hr-dashboard.md` | This document |
|
||||
|
||||
---
|
||||
|
||||
## FE integration notes
|
||||
|
||||
- Call `all` once on dashboard mount; render each `data[method].rows` into its card.
|
||||
- Call `all` once on dashboard mount; render each `data[method].rows` into its card (34 keys under `data`).
|
||||
- Call `kpi/{slug}` for lazy/on-demand loading of individual cards.
|
||||
- `policy_id` should come from the HR session / selected policy context — never hardcoded.
|
||||
- All rows are raw arrays; formatting (currency, %, dates) is already applied inside the SQL (`FORMAT()`, `CONCAT()`).
|
||||
- `rows` may be empty `[]` if no claims exist for that policy — handle gracefully in UI.
|
||||
- All rows are raw arrays; formatting (currency, %, dates) is already applied inside the SQL where applicable (`FORMAT()`, `CONCAT()`, `DATE_FORMAT()`).
|
||||
- `rows` may be empty `[]` if no data exists for that policy — handle gracefully in UI.
|
||||
- **Single-row KPIs** (e.g. `incurred_ratio`, `inception_emp_lives`): use `rows[0]`.
|
||||
- **Multi-row KPIs** (e.g. `claim_value_by_month`, `age_band`): iterate `rows`; for time series use `month_sort` for sort order.
|
||||
- **Exposure block:** `inception_emp_lives` + `current_emp_lives` pair for inception vs current headcount.
|
||||
|
||||
---
|
||||
|
||||
## BE notes
|
||||
|
||||
- To add a new KPI: add an entry to `KPI_MAP` + `KPI_LABELS` in the model and write the corresponding method `public function my_kpi(int $policyId): array`.
|
||||
- To add a new KPI: add an entry to `KPI_MAP` + `KPI_LABELS` in the model, write `public function my_kpi(int $policyId): array`, update this doc and bump the smoke test KPI count.
|
||||
- All queries use named binding `:policy_id:` (CodeIgniter style, replaces Metabase `{{policy_id}}`).
|
||||
- Literal `\t` / `\n` in CSV SQL is normalized in `runKpiQuery()` — safe to re-generate from CSV.
|
||||
- Run `php tests/smoke_claims_collection_v2.php {policy_id}` after any model change.
|
||||
- Run `php tests/smoke_claims_collection_v2.php {policy_id}` after any model change (expects `KPI_MAP` count === 34).
|
||||
|
||||
@ -51,10 +51,10 @@ function ok(string $label, bool $cond, string $detail = ''): void
|
||||
$model = new ClaimsCollectionV2DashboardModel();
|
||||
$kpiMap = ClaimsCollectionV2DashboardModel::KPI_MAP;
|
||||
|
||||
ok('KPI_MAP count', count($kpiMap) === 31, (string) count($kpiMap));
|
||||
ok('KPI_MAP count', count($kpiMap) === 34, (string) count($kpiMap));
|
||||
|
||||
$uniqueMethods = array_unique(array_values($kpiMap));
|
||||
ok('unique KPI method names', count($uniqueMethods) === 31, count($uniqueMethods) . ' methods');
|
||||
ok('unique KPI method names', count($uniqueMethods) === 34, count($uniqueMethods) . ' methods');
|
||||
|
||||
foreach (['policy_exposure_summary', 'incurred_ratio'] as $slug) {
|
||||
ok("slug map contains {$slug}", in_array($slug, $kpiMap, true));
|
||||
@ -93,7 +93,7 @@ $badResp = json_decode($controller->kpi('not_a_kpi')->getJSON(), true);
|
||||
ok('controller unknown kpi 404', ($badResp['status'] ?? true) === false);
|
||||
|
||||
$allResp = json_decode($controller->all()->getJSON(), true);
|
||||
ok('controller all KPIs', ($allResp['status'] ?? false) === true && count($allResp['data'] ?? []) === 31);
|
||||
ok('controller all KPIs', ($allResp['status'] ?? false) === true && count($allResp['data'] ?? []) === 34);
|
||||
|
||||
$debugOut = $controller->debug($policyId);
|
||||
$debugBody = is_string($debugOut) ? $debugOut : $debugOut->getBody();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user