Merge branch 'dev' of bitbucket.org:jubilian/nhance into dev
This commit is contained in:
commit
9f3bd4162f
@ -49,6 +49,8 @@ class ClaimsCollectionV2DashboardModel extends Model
|
||||
232 => 'inception_emp_lives',
|
||||
233 => 'current_emp_lives',
|
||||
234 => 'claim_value_by_month',
|
||||
235 => 'claim_amount_by_relationship',
|
||||
236 => 'top_10_ailments_by_claim_count',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -89,6 +91,8 @@ class ClaimsCollectionV2DashboardModel extends Model
|
||||
'inception_emp_lives' => 'Inception Employees & Lives',
|
||||
'current_emp_lives' => 'Current Employees & Lives',
|
||||
'claim_value_by_month' => 'Claim Value by Month',
|
||||
'claim_amount_by_relationship' => 'Claim Amount by Relationship',
|
||||
'top_10_ailments_by_claim_count' => 'Top 10 Ailments by Claim Count',
|
||||
];
|
||||
|
||||
protected function runKpiQuery(string $sql, int $policyId): array
|
||||
@ -2175,6 +2179,113 @@ GROUP BY
|
||||
totals.total_count,
|
||||
totals.total_value
|
||||
ORDER BY month_sort;
|
||||
SQL;
|
||||
return $this->runKpiQuery($sql, $policyId);
|
||||
}
|
||||
|
||||
/** Metabase #235: Claim Amount by Relationship */
|
||||
public function claim_amount_by_relationship(int $policyId): array
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT
|
||||
COALESCE(tm.relationship, 'Unknown') AS relationship,
|
||||
COUNT(tm.id) AS claim_count,
|
||||
ROUND(COUNT(tm.id) / NULLIF(totals.total_count, 0) * 100, 2)
|
||||
AS count_pct,
|
||||
COALESCE(SUM(CAST(NULLIF(tm.claim_amount, '') AS DECIMAL(15,2))), 0) AS claim_value,
|
||||
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 tm.relationship
|
||||
|
||||
ORDER BY claim_count DESC;
|
||||
|
||||
-- SELECT
|
||||
-- COALESCE(
|
||||
-- CONCAT(UPPER(SUBSTRING(tm.relationship, 1, 1)), LOWER(SUBSTRING(tm.relationship, 2))),
|
||||
-- 'Unknown'
|
||||
-- ) AS relationship,
|
||||
-- COUNT(tm.id) AS claim_count,
|
||||
-- ROUND(COUNT(tm.id) / NULLIF(totals.total_count, 0) * 100, 2) AS count_pct,
|
||||
-- COALESCE(SUM(CAST(NULLIF(tm.claim_amount, '') AS DECIMAL(15,2))), 0) AS claim_value,
|
||||
-- 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 tm.relationship
|
||||
-- ORDER BY
|
||||
-- CASE LOWER(tm.relationship)
|
||||
-- WHEN 'self' THEN 1
|
||||
-- WHEN 'spouse' THEN 2
|
||||
-- WHEN 'son' THEN 3
|
||||
-- WHEN 'daughter' THEN 4
|
||||
-- WHEN 'father' THEN 5
|
||||
-- WHEN 'mother' THEN 6
|
||||
-- WHEN 'father-in-law' THEN 7
|
||||
-- WHEN 'mother-in-law' THEN 8
|
||||
-- WHEN 'unknown' THEN 9
|
||||
-- ELSE 10
|
||||
-- END;
|
||||
SQL;
|
||||
return $this->runKpiQuery($sql, $policyId);
|
||||
}
|
||||
|
||||
/** Metabase #236: Top 10 Ailments by Claim Count */
|
||||
public function top_10_ailments_by_claim_count(int $policyId): array
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT
|
||||
COALESCE(tm.tpa_ailments, 'Not Specified') AS ailment,
|
||||
COUNT(tm.id) AS claim_count,
|
||||
CONCAT(ROUND(COUNT(tm.id) / totals.total_count * 100, 2), '%')
|
||||
AS count_pct,
|
||||
FORMAT(
|
||||
COALESCE(SUM(CAST(NULLIF(tm.claim_amount, '') AS DECIMAL(15,2))), 0)
|
||||
, 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
|
||||
AND tm.tpa_ailments IS NOT NULL
|
||||
GROUP BY tm.tpa_ailments, totals.total_count, totals.total_value
|
||||
ORDER BY claim_count DESC
|
||||
LIMIT 10;
|
||||
SQL;
|
||||
return $this->runKpiQuery($sql, $policyId);
|
||||
}
|
||||
|
||||
@ -4,9 +4,9 @@
|
||||
**Updated:** 2026-06-03
|
||||
**Controller:** `App\Controllers\ClaimsCollectionV2DashboardController`
|
||||
**Model:** `App\Models\ClaimsCollectionV2DashboardModel`
|
||||
**Source queries:** `metabase_raw_queries.csv` → collection `Claims Collection V2` (31 original) + 3 HR exposure/trend KPIs added in-model
|
||||
**Source queries:** `metabase_raw_queries.csv` → collection `Claims Collection V2` (31 original) + 5 KPIs added in-model (232–236)
|
||||
**Base app URL (local):** `https://localhost/PHP828APPS/ruc/nhance/index.php`
|
||||
**Total KPIs:** 34
|
||||
**Total KPIs:** 36
|
||||
|
||||
---
|
||||
|
||||
@ -19,7 +19,7 @@ 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 (34 KPIs) |
|
||||
| **All KPIs** | `all` | FE loads entire dashboard in one call (36 KPIs) |
|
||||
| **Debug / preview** | `debug` / `preview` | Admin checks raw output in browser |
|
||||
|
||||
All endpoints require `client_policy` or `client_policy_id` as a query param
|
||||
@ -39,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 34 KPIs |
|
||||
| GET | `util/claims-collection-v2/all` | `::all` | JSON — all 36 KPIs |
|
||||
| GET | `util/claims-collection-v2/kpi/{slug\|id}` | `::kpi` | JSON — single KPI |
|
||||
|
||||
**Filters applied:** `authMVC`, `AclFilter`, `HttpRequestLog`, `Cors`, `SecurityInputFilter`
|
||||
@ -52,7 +52,7 @@ Prefix: `employeeRest/claims-collection-v2`
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| GET | `employeeRest/claims-collection-v2/all` | `::all` | JSON — all 34 KPIs |
|
||||
| GET | `employeeRest/claims-collection-v2/all` | `::all` | JSON — all 36 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 |
|
||||
@ -88,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 34 KPIs
|
||||
# Raw JSON — all 36 KPIs
|
||||
GET /index.php/util/claims-collection-v2/debug?client_policy=4687
|
||||
|
||||
# Raw JSON — single KPI by slug
|
||||
@ -101,6 +101,8 @@ GET /index.php/util/claims-collection-v2/kpi/207?client_policy=4687
|
||||
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
|
||||
GET /index.php/util/claims-collection-v2/kpi/claim_amount_by_relationship?client_policy=4687
|
||||
GET /index.php/util/claims-collection-v2/kpi/top_10_ailments_by_claim_count?client_policy=4687
|
||||
```
|
||||
|
||||
---
|
||||
@ -127,7 +129,13 @@ X-App-Signature: <app_signature>
|
||||
```
|
||||
|
||||
```http
|
||||
GET /index.php/employeeRest/claims-collection-v2/kpi/234?client_policy=4687
|
||||
GET /index.php/employeeRest/claims-collection-v2/kpi/claim_amount_by_relationship?client_policy=4687
|
||||
Authorization: Bearer <jwt_token>
|
||||
X-App-Signature: <app_signature>
|
||||
```
|
||||
|
||||
```http
|
||||
GET /index.php/employeeRest/claims-collection-v2/kpi/235?client_policy=4687
|
||||
Authorization: Bearer <jwt_token>
|
||||
X-App-Signature: <app_signature>
|
||||
```
|
||||
@ -265,15 +273,17 @@ X-App-Signature: <app_signature>
|
||||
"207": "incurred_ratio",
|
||||
"232": "inception_emp_lives",
|
||||
"233": "current_emp_lives",
|
||||
"234": "claim_value_by_month"
|
||||
"234": "claim_value_by_month",
|
||||
"235": "claim_amount_by_relationship",
|
||||
"236": "top_10_ailments_by_claim_count"
|
||||
}
|
||||
}
|
||||
```
|
||||
**HTTP 404** — `allowed` lists the full `KPI_MAP` (34 entries).
|
||||
**HTTP 404** — `allowed` lists the full `KPI_MAP` (36 entries).
|
||||
|
||||
---
|
||||
|
||||
## All 34 KPIs
|
||||
## All 36 KPIs
|
||||
|
||||
| Metabase ID | Method slug | Label | Rows |
|
||||
|-------------|-------------|-------|------|
|
||||
@ -311,20 +321,56 @@ X-App-Signature: <app_signature>
|
||||
| 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 |
|
||||
| 235 | `claim_amount_by_relationship` | Claim Amount by Relationship | multi |
|
||||
| 236 | `top_10_ailments_by_claim_count` | Top 10 Ailments by Claim Count | 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.
|
||||
> **Note:** IDs **232–236** 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
|
||||
## Output columns — KPIs 232–236
|
||||
|
||||
| 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` |
|
||||
| `claim_amount_by_relationship` | `relationship`, `claim_count`, `count_pct`, `claim_value`, `value_pct` | Claims by `ticket_master.relationship`; `count_pct` / `value_pct` are numeric (not `%` strings) |
|
||||
| `top_10_ailments_by_claim_count` | `ailment`, `claim_count`, `count_pct`, `claim_value`, `value_pct` | Top 10 `tpa_ailments` (non-null); `claim_value` formatted via `FORMAT()`; pct fields as `%` strings |
|
||||
|
||||
---
|
||||
|
||||
### `kpi/claim_amount_by_relationship` — breakdown (multi-row)
|
||||
|
||||
```json
|
||||
{
|
||||
"status": true,
|
||||
"policy_id": 4687,
|
||||
"kpi_id": 235,
|
||||
"kpi": "claim_amount_by_relationship",
|
||||
"label": "Claim Amount by Relationship",
|
||||
"rows": [
|
||||
{
|
||||
"relationship": "Self",
|
||||
"claim_count": "42",
|
||||
"count_pct": "35.00",
|
||||
"claim_value": "850000.00",
|
||||
"value_pct": "40.50"
|
||||
},
|
||||
{
|
||||
"relationship": "Spouse",
|
||||
"claim_count": "28",
|
||||
"count_pct": "23.33",
|
||||
"claim_value": "520000.00",
|
||||
"value_pct": "24.80"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> Not the same as `claim_amount_by_gender` (#190), which groups by employee `gender` via join — not `ticket_master.relationship`.
|
||||
|
||||
---
|
||||
|
||||
@ -332,7 +378,7 @@ X-App-Signature: <app_signature>
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `app/Models/ClaimsCollectionV2DashboardModel.php` | 34 KPI query methods, `KPI_MAP`, `KPI_LABELS`, `getAllKpis()`, `getKpi()` |
|
||||
| `app/Models/ClaimsCollectionV2DashboardModel.php` | 36 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`) |
|
||||
@ -344,7 +390,7 @@ X-App-Signature: <app_signature>
|
||||
|
||||
## FE integration notes
|
||||
|
||||
- Call `all` once on dashboard mount; render each `data[method].rows` into its card (34 keys under `data`).
|
||||
- Call `all` once on dashboard mount; render each `data[method].rows` into its card (36 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 where applicable (`FORMAT()`, `CONCAT()`, `DATE_FORMAT()`).
|
||||
@ -360,4 +406,4 @@ X-App-Signature: <app_signature>
|
||||
- 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 (expects `KPI_MAP` count === 34).
|
||||
- Run `php tests/smoke_claims_collection_v2.php {policy_id}` after any model change (expects `KPI_MAP` count === 36).
|
||||
|
||||
@ -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) === 34, (string) count($kpiMap));
|
||||
ok('KPI_MAP count', count($kpiMap) === 36, (string) count($kpiMap));
|
||||
|
||||
$uniqueMethods = array_unique(array_values($kpiMap));
|
||||
ok('unique KPI method names', count($uniqueMethods) === 34, count($uniqueMethods) . ' methods');
|
||||
ok('unique KPI method names', count($uniqueMethods) === 36, 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'] ?? []) === 34);
|
||||
ok('controller all KPIs', ($allResp['status'] ?? false) === true && count($allResp['data'] ?? []) === 36);
|
||||
|
||||
$debugOut = $controller->debug($policyId);
|
||||
$debugBody = is_string($debugOut) ? $debugOut : $debugOut->getBody();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user