FEAT_DASHBOARD_QURIES&MAIL_CHANGE

This commit is contained in:
velz 2026-06-02 14:26:20 +05:30
parent f3ac9c6833
commit 6e6b0f9426
10 changed files with 1051612 additions and 4 deletions

View File

@ -35,6 +35,7 @@ $routes->get('/fedeploy', 'DeployController::fedeploy_view', ['filter' => 'authM
$routes->post('/fedeploy', 'DeployController::fedeploy', ['filter' => 'authMVC']);
$routes->get('/visitOffBoardCheck', 'EmployeeController::visitOffBoardCheck');
$routes->get('/metaDashboardDemo', 'TestingController::metaDashboardDemo');
$routes->get('/apacheSuperSetDemo', 'TestingController::apacheSuperSetDemo');
$routes->get('/metaTpaDashboardDemo', 'TestingController::metaTpaDashboardDemo');
// Reminder Mail Notification
@ -459,6 +460,15 @@ $routes->group("/util", ["filter" => "authMVC"], function ($routes) {
$routes->get('insertSampleTpaApiData/(:any)', 'TestingController::insertSampleTpaApiData/$1');
$routes->get('listEmployeeCountByClientPolicy', 'TestingController::listEmployeeCountByClientPolicy');
$routes->get('testMediAssistWellness','TestingController::testMediAssistWellness');
$routes->group('claims-collection-v2', static function ($routes) {
$routes->get('preview', 'ClaimsCollectionV2DashboardController::preview');
$routes->get('preview/(:num)', 'ClaimsCollectionV2DashboardController::preview/$1');
$routes->get('kpi/(:segment)', 'ClaimsCollectionV2DashboardController::kpi/$1');
$routes->get('all', 'ClaimsCollectionV2DashboardController::all');
$routes->get('debug', 'ClaimsCollectionV2DashboardController::debug');
$routes->get('debug/(:num)', 'ClaimsCollectionV2DashboardController::debug/$1');
});
});
$routes->post("policy_tranction/sendInstallmentRemainderMail","PolicyTransactionController::sendInstallmentRemainderMail");
@ -766,6 +776,15 @@ $routes->group("employeeRest", ["filter" => ['GlobalPostFileUploadGuard', 'ratel
$routes->get("downloadPolicyFiles", "EmployeeRestController::downloadPolicyFiles");
$routes->post("bulkEcardDownloadAsZip", "EmployeeRestController::bulkEcardDownloadAsZip");
$routes->get("downloadSampleExcel/(:any)", "EmployeeController::downloadSampleExcelFile/$1");
$routes->group('claims-collection-v2', static function ($routes) {
$routes->get('preview', 'ClaimsCollectionV2DashboardController::preview');
$routes->get('preview/(:num)', 'ClaimsCollectionV2DashboardController::preview/$1');
$routes->get('kpi/(:segment)', 'ClaimsCollectionV2DashboardController::kpi/$1');
$routes->get('all', 'ClaimsCollectionV2DashboardController::all');
$routes->get('debug', 'ClaimsCollectionV2DashboardController::debug');
$routes->get('debug/(:num)', 'ClaimsCollectionV2DashboardController::debug/$1');
});
});
$routes->post("bulkEcardDownloadAsZip", "EmployeeRestController::bulkEcardDownloadAsZip");

View File

@ -0,0 +1,166 @@
<?php
namespace App\Controllers;
use App\Models\ClaimsCollectionV2DashboardModel;
use CodeIgniter\API\ResponseTrait;
/**
* Claims Collection V2 KPI API (Metabase SQL as model methods).
*/
class ClaimsCollectionV2DashboardController extends BaseController
{
use ResponseTrait;
/**
* Resolve policy id from request; optional $fallback (e.g. route default for debug only).
*/
protected function resolvePolicyId(?int $fallback = null): int
{
$id = (int) (
$this->request->getGet('client_policy')
?? $this->request->getGet('client_policy_id')
?? $this->request->getPost('client_policy')
?? $this->request->getPost('client_policy_id')
?? 0
);
if ($id > 0) {
return $id;
}
if ($fallback !== null && $fallback > 0) {
return $fallback;
}
return 0;
}
/**
* Resolve KPI by Metabase numeric id or method slug.
*/
protected function resolveKpiMethod(string $kpiKey): ?string
{
$kpiKey = trim($kpiKey);
if ($kpiKey === '') {
return null;
}
if (in_array($kpiKey, ClaimsCollectionV2DashboardModel::KPI_MAP, true)) {
return $kpiKey;
}
if (ctype_digit($kpiKey)) {
$id = (int) $kpiKey;
return ClaimsCollectionV2DashboardModel::KPI_MAP[$id] ?? null;
}
return null;
}
/**
* JSON: single KPI by slug (e.g. incurred_ratio) or Metabase id (e.g. 207).
* Requires client_policy or client_policy_id.
*/
public function kpi(string $kpiMethod = '')
{
$policyId = $this->resolvePolicyId();
if ($policyId <= 0) {
return $this->respond([
'status' => false,
'message' => 'client_policy or client_policy_id is required.',
], 422);
}
$kpiMethod = $this->resolveKpiMethod($kpiMethod);
if ($kpiMethod === null) {
return $this->respond([
'status' => false,
'message' => 'Unknown KPI. Pass Metabase id or method slug.',
'allowed' => ClaimsCollectionV2DashboardModel::KPI_MAP,
], 404);
}
$model = new ClaimsCollectionV2DashboardModel();
$metabaseId = array_search($kpiMethod, ClaimsCollectionV2DashboardModel::KPI_MAP, true);
return $this->respond([
'status' => true,
'policy_id' => $policyId,
'kpi_id' => $metabaseId !== false ? (int) $metabaseId : null,
'kpi' => $kpiMethod,
'label' => ClaimsCollectionV2DashboardModel::KPI_LABELS[$kpiMethod] ?? $kpiMethod,
'rows' => $model->getKpi($kpiMethod, $policyId),
]);
}
/**
* JSON: all KPIs. Requires client_policy or client_policy_id.
*/
public function all()
{
$policyId = $this->resolvePolicyId();
if ($policyId <= 0) {
return $this->respond([
'status' => false,
'message' => 'client_policy or client_policy_id is required.',
], 422);
}
$model = new ClaimsCollectionV2DashboardModel();
return $this->respond([
'status' => true,
'policy_id' => $policyId,
'data' => $model->getAllKpis($policyId),
]);
}
/**
* Admin preview UI (authMVC only) KPI grid for manual testing.
* Default policy id only in method signature; override via query or /preview/{id}.
*/
public function preview(int $policyId = 4687)
{
$policyId = $this->resolvePolicyId($policyId);
$path = $this->request->getUri()->getPath();
$isJwt = stripos($path, 'employeeRest') !== false;
$prefix = $isJwt ? 'employeeRest/claims-collection-v2' : 'util/claims-collection-v2';
return view('claims_collection_v2_dashboard', [
'policy_id' => $policyId,
'kpi_map' => ClaimsCollectionV2DashboardModel::KPI_MAP,
'kpi_labels' => ClaimsCollectionV2DashboardModel::KPI_LABELS,
'api_all_url' => base_url($prefix . '/all'),
'api_kpi_url' => base_url($prefix . '/kpi'),
]);
}
/**
* Admin check only: raw JSON on screen (no dashboard UI).
* Default policy id only here; override via ?client_policy= or /debug/{id}.
*/
public function debug(int $policyId = 4687)
{
$policyId = $this->resolvePolicyId($policyId);
if ($policyId <= 0) {
return $this->response
->setStatusCode(422)
->setBody('client_policy or client_policy_id is required.');
}
$model = new ClaimsCollectionV2DashboardModel();
$body = json_encode([
'status' => true,
'policy_id' => $policyId,
'data' => $model->getAllKpis($policyId),
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
return $this->response
->setHeader('Content-Type', 'application/json; charset=UTF-8')
->setBody($body);
}
}

View File

@ -1143,11 +1143,54 @@ class TestingController extends BaseController
'metabaseUrl' => 'https://nsights.nhanceindia.in',
]);
}
public function apacheSuperSetDemo()
{
public function testingquerys1(){
// echo 'Dta';die;
// 🔐 Move this to .env in real projects
$METABASE_SECRET_KEY = getenv('METABASE_SECRET_KEY');
$database_id = (int)$this->request->getGet('database_id') ?? 2;
$policy_id = $this->request->getGet('client_policy') ?? null;
$tpa_url = 'https://nsights.nhanceindia.in/public/dashboard/4babf324-6c1e-4c5a-adbb-1c80a0f545b1';
$policy_id = $policy_id ? $policy_id : 4687;
$payload = [
'resource' => [
// 'dashboard' => 1
'dashboard' => 2
],
'exp' => time() + (10 * 60), // 10 minutes
];
if(!empty($policy_id)){
$payload['params'] = (object)['client_policy' => $policy_id]; // MUST be object for Metabase
}else{
$payload['params'] = (object)[]; // MUST be object for Metabase
}
// dd($payload);
$token = JWT::encode($payload, $METABASE_SECRET_KEY, 'HS256');
// // You can either return token only
// return $this->response->setJSON([
// 'token' => $token,
// 'iframe_url' => "https://your-metabase-domain/embed/dashboard/{$token}#bordered=true&titled=true"
// ]);
if($this->request->getGet('api') == 1)
{
return $this->respond([
'status' => 'success',
'message' => 'Form data received successfully!',
'data' => [
'metabaseToken' => $token,
'metabaseUrl' => 'https://nsights.nhanceindia.in']
]);
}
return view('apache_dashboard_demo_one', [
'metabaseToken' => $token,
'metabaseUrl' => 'https://nsights.nhanceindia.in',
]);
}
public function testingquerys()
{
$calendar = new \App\Libraries\GoogleCalendarService();

View File

@ -183,12 +183,24 @@ use App\Models\JobModel;
class MailHelper
{
private const SYSTEM_EMAIL_FOOTER = '<div style="
text-align:center;
font-size:9px;
color:#999999;
border-top:1px solid #eeeeee;
margin-top:2px;
padding-top:1px;
line-height:1;
">
This is a system-generated email. Please do not reply.
</div>';
public static function send_email_smtp($params)
{
$myLogger = \Config\Services::mylogger();
$emaill = $params['mail'];
$subject = $params['subject'];
$message = $params['message'];
$message .= self::SYSTEM_EMAIL_FOOTER;
if (isset($params['bcc'])) {
$bcc = $params['bcc'];
} else {
@ -206,7 +218,8 @@ class MailHelper
$curl = curl_init();
$postData = [
'from' => [
'address' => $from_address
'address' => $from_address,
'name' => 'Nhance India Insurance'
],
'to' => [
[
@ -315,6 +328,9 @@ class MailHelper
$subject = $params['subject'];
$message = $params['message'];
$message .= self::SYSTEM_EMAIL_FOOTER;
// echo $message;die;
$attachments = isset($params['attachments']) ? $params['attachments'] : [];
$common = isset($params['common']) ? $params['common'] : '';
$bcc = isset($params['bcc']) ? $params['bcc'] : '';
@ -328,7 +344,8 @@ class MailHelper
$postData = [
'from' => [
'address' => $from_address
'address' => $from_address,
'name' => "Nhance India Insurance"
],
'to' => $emails,
'subject' => $subject,

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,113 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Claims Overview</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: sans-serif; background: #f5f5f5; }
#dashboard-container {
width: 100%;
height: 100vh;
}
#dashboard-container iframe {
width: 100%;
height: 100%;
border: none;
}
#error-msg {
display: none;
padding: 2rem;
color: #c0392b;
font-size: 14px;
text-align: center;
}
</style>
</head>
<body>
<div id="dashboard-container"></div>
<iframe src="https://venbait.in/chartBoard/public/share/8e130e4faa821740183feaa805f3ba0c7935841a64c5461ba6ed255f5b419f93?embed=1" width="1200" height="800" style="border:0;" loading="lazy" title="Chart-Board"></iframe>
<div id="error-msg"></div>
<script src="https://unpkg.com/@superset-ui/embedded-sdk"></script>
<script>
const SUPERSET_DOMAIN = "https://demo.venbait.in";
const DASHBOARD_ID = "aaff42c8-1d7a-4e28-8beb-f6a7b9e08661";
const USERNAME = "admin";
const PASSWORD = "$tr0n9pa55w0rd ";
async function getGuestToken() {
// Step 1: Login to get access token
const loginRes = await fetch(SUPERSET_DOMAIN + "/api/v1/security/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
username: USERNAME,
password: PASSWORD,
provider: "db",
refresh: true
})
});
if (!loginRes.ok) throw new Error("Login failed: " + loginRes.status);
const { access_token } = await loginRes.json();
// Step 2: Get CSRF token
const csrfRes = await fetch(SUPERSET_DOMAIN + "/api/v1/security/csrf_token/", {
method: "GET",
headers: { "Authorization": "Bearer " + access_token }
});
if (!csrfRes.ok) throw new Error("CSRF fetch failed: " + csrfRes.status);
const { result: csrf_token } = await csrfRes.json();
// Step 3: Get guest token
const guestRes = await fetch(SUPERSET_DOMAIN + "/api/v1/security/guest_token/", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + access_token,
"X-CSRFToken": csrf_token
},
body: JSON.stringify({
resources: [{ type: "dashboard", id: DASHBOARD_ID }],
rls: [],
user: { username: "guest", first_name: "Guest", last_name: "User" }
})
});
if (!guestRes.ok) throw new Error("Guest token failed: " + guestRes.status);
const { token } = await guestRes.json();
return token;
}
async function init() {
alert();
return true;
try {
await supersetEmbeddedSdk.embedDashboard({
id: DASHBOARD_ID,
supersetDomain: SUPERSET_DOMAIN,
mountPoint: document.getElementById("dashboard-container"),
fetchGuestToken: () => getGuestToken(),
dashboardUiConfig: {
hideTitle: true,
filters: { expanded: true }
}
});
} catch (err) {
const el = document.getElementById("error-msg");
el.style.display = "block";
el.textContent = "Error loading dashboard: " + err.message;
console.error(err);
}
}
init();
</script>
</body>
</html>

View File

@ -0,0 +1,103 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Claims Collection V2 Dashboard</title>
<style>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; padding: 1.5rem; background: #f0f2f5; color: #1a1a1a; }
h1 { font-size: 1.35rem; margin: 0 0 1rem; }
.toolbar { display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: center; margin-bottom: 1.25rem; }
.toolbar input { padding: 0.45rem 0.6rem; border: 1px solid #ccc; border-radius: 6px; width: 140px; }
.toolbar button { padding: 0.5rem 1rem; border: none; border-radius: 6px; background: #2563eb; color: #fff; cursor: pointer; }
.toolbar button.secondary { background: #64748b; }
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 1rem; }
.card { background: #fff; border-radius: 8px; padding: 1rem; box-shadow: 0 1px 3px rgba(0,0,0,.08); }
.card h2 { font-size: 0.85rem; margin: 0 0 0.5rem; color: #475569; font-weight: 600; }
.card pre { font-size: 0.72rem; margin: 0; max-height: 200px; overflow: auto; background: #f8fafc; padding: 0.5rem; border-radius: 4px; white-space: pre-wrap; word-break: break-word; }
.card .status { font-size: 0.75rem; color: #94a3b8; margin-bottom: 0.35rem; }
.card.loading pre { color: #94a3b8; }
.card.error pre { color: #b91c1c; }
</style>
</head>
<body>
<h1>Claims Collection V2</h1>
<div class="toolbar">
<label>Policy ID <input type="number" id="policy-id" value="<?= (int) $policy_id ?>" min="1"></label>
<button type="button" id="btn-load-all">Load all KPIs</button>
<button type="button" class="secondary" id="btn-clear">Clear</button>
</div>
<div class="grid" id="kpi-grid">
<?php foreach ($kpi_map as $metabaseId => $method): ?>
<div class="card" id="card-<?= esc($method) ?>" data-kpi="<?= esc($method) ?>">
<div class="status">#<?= (int) $metabaseId ?> · <?= esc($kpi_labels[$method] ?? $method) ?></div>
<h2><?= esc($method) ?></h2>
<pre>Click “Load all KPIs” or open single KPI API.</pre>
</div>
<?php endforeach; ?>
</div>
<script>
(function () {
const baseUrl = <?= json_encode(rtrim(base_url(), '/')) ?>;
const policyInput = document.getElementById('policy-id');
const grid = document.getElementById('kpi-grid');
function setCardState(method, state, text) {
const card = document.getElementById('card-' + method);
if (!card) return;
card.classList.remove('loading', 'error');
if (state) card.classList.add(state);
card.querySelector('pre').textContent = text;
}
async function loadAll() {
const policyId = policyInput.value;
if (!policyId) {
alert('Enter a policy ID');
return;
}
grid.querySelectorAll('.card').forEach(function (c) {
c.classList.add('loading');
c.querySelector('pre').textContent = 'Loading…';
});
try {
const apiAllUrl = <?= json_encode($api_all_url ?? base_url('util/claims-collection-v2/all')) ?>;
const res = await fetch(apiAllUrl + '?client_policy=' + encodeURIComponent(policyId));
const json = await res.json();
if (!json.status) {
throw new Error(json.message || 'Request failed');
}
Object.keys(json.data || {}).forEach(function (method) {
const block = json.data[method];
setCardState(method, '', JSON.stringify(block.rows, null, 2));
});
} catch (e) {
grid.querySelectorAll('.card').forEach(function (c) {
c.classList.remove('loading');
c.classList.add('error');
c.querySelector('pre').textContent = e.message;
});
} finally {
grid.querySelectorAll('.card.loading').forEach(function (c) {
c.classList.remove('loading');
});
}
}
document.getElementById('btn-load-all').addEventListener('click', loadAll);
document.getElementById('btn-clear').addEventListener('click', function () {
grid.querySelectorAll('.card').forEach(function (c) {
c.classList.remove('error');
c.querySelector('pre').textContent = '—';
});
});
})();
</script>
</body>
</html>

262
hr-dashboard.md Normal file
View File

@ -0,0 +1,262 @@
# Claims Collection V2 — HR Dashboard API
**Created:** 2026-06-02
**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`
---
## Overview
Each Metabase question in the CSV is a separate PHP method on the model.
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 |
| **Debug / preview** | `debug` / `preview` | Admin checks raw output in browser |
All endpoints require `client_policy` or `client_policy_id` as a query param
(or route segment for `debug/{id}` / `preview/{id}`).
---
## Route stacks
### 1. Admin — `authMVC` (session login required)
Prefix: `util/claims-collection-v2`
| Method | Path | Handler | Purpose |
|--------|------|---------|---------|
| GET | `util/claims-collection-v2/preview` | `::preview` | UI debug grid in browser |
| 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/kpi/{slug\|id}` | `::kpi` | JSON — single KPI |
**Filters applied:** `authMVC`, `AclFilter`, `HttpRequestLog`, `Cors`, `SecurityInputFilter`
---
### 2. Frontend / HR App — `authJWT` (Bearer token required)
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/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 |
| GET | `employeeRest/claims-collection-v2/debug` | `::debug` | Raw JSON (FE debug) |
| GET | `employeeRest/claims-collection-v2/debug/{policy_id}` | `::debug` | Same, policy in URL |
**Filters applied:** `GlobalPostFileUploadGuard`, `ratelimit`, `appSignature`, `authJWT`
---
## Parameters
| Param | Location | Type | Required | Notes |
|-------|----------|------|----------|-------|
| `client_policy` | query string | int | **Yes** (API) | Policy ID |
| `client_policy_id` | query string | int | **Yes** (API) | Alias for above |
| `{policy_id}` | URL segment | int | No | Only on `preview/{id}` and `debug/{id}`; falls back to 4687 if omitted |
| `{slug\|id}` | URL segment | string/int | Yes (kpi only) | KPI method slug OR Metabase question id |
> **Default policy ID (4687)** is only the method-signature default for `preview` and `debug`.
> Change it by passing the query param or URL segment — never hardcoded elsewhere.
---
## How to call — Admin (browser / Postman, session cookie)
Open in browser while logged in to admin:
```
# UI preview page (loads grid, click "Load all KPIs")
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
GET /index.php/util/claims-collection-v2/debug?client_policy=4687
# Raw JSON — single KPI by slug
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
```
---
## How to call — Frontend / HR App (JWT)
```http
GET /index.php/employeeRest/claims-collection-v2/all?client_policy=4687
Authorization: Bearer <jwt_token>
X-App-Signature: <app_signature>
Content-Type: application/json
```
```http
GET /index.php/employeeRest/claims-collection-v2/kpi/incurred_ratio?client_policy=4687
Authorization: Bearer <jwt_token>
X-App-Signature: <app_signature>
```
```http
GET /index.php/employeeRest/claims-collection-v2/kpi/207?client_policy=4687
Authorization: Bearer <jwt_token>
X-App-Signature: <app_signature>
```
---
## Sample responses
### `kpi/{slug}` or `kpi/{id}`
```json
{
"status": true,
"policy_id": 4687,
"kpi_id": 207,
"kpi": "incurred_ratio",
"label": "Incurred Ratio",
"rows": [
{ "incurred_ratio": "72.34%" }
]
}
```
### `all`
```json
{
"status": true,
"policy_id": 4687,
"data": {
"policy_exposure_summary": {
"id": 181,
"label": "POLICY & EXPOSURE SUMMARY",
"rows": [
{
"policy_start_date": "2024-01-01",
"policy_end_date": "2024-12-31",
"insurer_name": "HDFC Ergo",
"tpa_name": "Medi Assist"
}
]
},
"incurred_ratio": {
"id": 207,
"label": "Incurred Ratio",
"rows": [{ "incurred_ratio": "72.34%" }]
}
}
}
```
### Error — missing policy id
```json
{
"status": false,
"message": "client_policy or client_policy_id is required."
}
```
**HTTP 422**
### Error — unknown KPI
```json
{
"status": false,
"message": "Unknown KPI. Pass Metabase id or method slug.",
"allowed": {
"181": "policy_exposure_summary",
"207": "incurred_ratio"
}
}
```
**HTTP 404**
---
## All 31 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 |
> **Note:** KPIs 214 and 216 were renamed from the auto-generated slug to avoid collision with 213 and 215.
---
## Files
| File | Purpose |
|------|---------|
| `app/Models/ClaimsCollectionV2DashboardModel.php` | 31 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 |
---
## FE integration notes
- Call `all` once on dashboard mount; render each `data[method].rows` into its card.
- 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.
---
## 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`.
- 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.

1048639
metabase_raw_queries.csv Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,156 @@
<?php
/**
* One-off smoke test: Claims Collection V2 dashboard.
* Run: php tests/smoke_claims_collection_v2.php [policy_id]
*/
declare(strict_types=1);
ob_start();
define('FCPATH', __DIR__ . '/../public/');
chdir(FCPATH);
require FCPATH . '../app/Config/Paths.php';
$paths = new Config\Paths();
require rtrim($paths->systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'bootstrap.php';
require_once SYSTEMPATH . 'Config/DotEnv.php';
(new CodeIgniter\Config\DotEnv(ROOTPATH))->load();
defined('ENVIRONMENT') || define('ENVIRONMENT', env('CI_ENVIRONMENT', 'development'));
$boot = APPPATH . 'Config/Boot/' . ENVIRONMENT . '.php';
if (is_file($boot)) {
require_once $boot;
}
// Load autoloaded services without full HTTP stack.
helper('url');
use App\Controllers\ClaimsCollectionV2DashboardController;
use App\Models\ClaimsCollectionV2DashboardModel;
use Config\Services;
$policyId = isset($argv[1]) ? (int) $argv[1] : 4687;
$pass = 0;
$fail = 0;
$results = [];
function ok(string $label, bool $cond, string $detail = ''): void
{
global $pass, $fail, $results;
if ($cond) {
$pass++;
$results[] = '[PASS] ' . $label . ($detail ? "{$detail}" : '');
} else {
$fail++;
$results[] = '[FAIL] ' . $label . ($detail ? "{$detail}" : '');
}
}
$model = new ClaimsCollectionV2DashboardModel();
$kpiMap = ClaimsCollectionV2DashboardModel::KPI_MAP;
ok('KPI_MAP count', count($kpiMap) === 31, (string) count($kpiMap));
$uniqueMethods = array_unique(array_values($kpiMap));
ok('unique KPI method names', count($uniqueMethods) === 31, count($uniqueMethods) . ' methods');
foreach (['policy_exposure_summary', 'incurred_ratio'] as $slug) {
ok("slug map contains {$slug}", in_array($slug, $kpiMap, true));
}
ok('id 207 maps to incurred_ratio', ($kpiMap[207] ?? '') === 'incurred_ratio');
ok('id 181 maps to policy_exposure_summary', ($kpiMap[181] ?? '') === 'policy_exposure_summary');
try {
$rows = $model->policy_exposure_summary($policyId);
ok('model policy_exposure_summary', is_array($rows), 'rows=' . count($rows));
} catch (Throwable $e) {
ok('model policy_exposure_summary', false, $e->getMessage());
}
try {
$rows = $model->getKpi('incurred_ratio', $policyId);
ok('model getKpi(incurred_ratio)', is_array($rows), 'rows=' . count($rows));
} catch (Throwable $e) {
ok('model getKpi(incurred_ratio)', false, $e->getMessage());
}
$request = Services::request(null, false);
$response = Services::response();
$request->setGlobal('get', ['client_policy' => (string) $policyId]);
$controller = new ClaimsCollectionV2DashboardController();
$controller->initController($request, $response, service('logger'));
$slugResp = json_decode($controller->kpi('incurred_ratio')->getJSON(), true);
ok('controller kpi by slug', ($slugResp['status'] ?? false) === true && ($slugResp['kpi'] ?? '') === 'incurred_ratio');
$idResp = json_decode($controller->kpi('207')->getJSON(), true);
ok('controller kpi by id 207', ($idResp['status'] ?? false) === true && ($idResp['kpi_id'] ?? 0) === 207);
$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);
$debugOut = $controller->debug($policyId);
$debugBody = is_string($debugOut) ? $debugOut : $debugOut->getBody();
$debugJson = json_decode($debugBody, true);
ok('controller debug JSON', ($debugJson['status'] ?? false) === true && isset($debugJson['data']));
$previewOut = $controller->preview($policyId);
$previewHtml = is_string($previewOut) ? $previewOut : $previewOut->getBody();
ok('controller preview HTML', str_contains($previewHtml, 'Claims Collection V2') && str_contains($previewHtml, 'kpi-grid'));
$routeCollection = Services::routes(true);
$mvcRoutes = [];
$jwtRoutes = [];
foreach ($routeCollection->getRoutes('get') as $pattern => $handler) {
if (! str_contains($pattern, 'claims-collection-v2')) {
continue;
}
if (str_starts_with($pattern, 'util/')) {
$mvcRoutes[] = $pattern;
}
if (str_starts_with($pattern, 'employeeRest/')) {
$jwtRoutes[] = $pattern;
}
}
ok('MVC claims-collection-v2 routes registered', count($mvcRoutes) >= 5, implode(', ', $mvcRoutes) ?: 'none');
ok('JWT claims-collection-v2 routes registered', count($jwtRoutes) >= 5, implode(', ', $jwtRoutes) ?: 'none');
$baseUrl = rtrim((string) env('app.baseURL', ''), '/');
if ($baseUrl !== '') {
$results[] = '';
$results[] = '=== HTTP auth gate checks (no session/token) ===';
$urls = [
'MVC preview' => $baseUrl . '/util/claims-collection-v2/preview?client_policy=' . $policyId,
'MVC kpi slug' => $baseUrl . '/util/claims-collection-v2/kpi/incurred_ratio?client_policy=' . $policyId,
'MVC kpi id' => $baseUrl . '/util/claims-collection-v2/kpi/207?client_policy=' . $policyId,
'MVC debug' => $baseUrl . '/util/claims-collection-v2/debug?client_policy=' . $policyId,
'JWT kpi slug' => $baseUrl . '/employeeRest/claims-collection-v2/kpi/incurred_ratio?client_policy=' . $policyId,
'JWT debug' => $baseUrl . '/employeeRest/claims-collection-v2/debug?client_policy=' . $policyId,
];
foreach ($urls as $label => $url) {
$ctx = stream_context_create(['http' => ['ignore_errors' => true, 'timeout' => 10]]);
$body = @file_get_contents($url, false, $ctx);
$code = 0;
if (isset($http_response_header[0]) && preg_match('/\s(\d{3})\s/', $http_response_header[0], $m)) {
$code = (int) $m[1];
}
$blocked = in_array($code, [401, 403, 302, 303], true);
$reachable = $code >= 200 && $code < 500;
ok("HTTP {$label} (" . ($code ?: 'no connection') . ')', $blocked || $reachable, $url);
}
} else {
$results[] = '[SKIP] HTTP checks — app.baseURL not set in .env';
}
ob_end_clean();
echo '=== Claims Collection V2 smoke test (policy_id=' . $policyId . ') ===' . PHP_EOL . PHP_EOL;
echo implode(PHP_EOL, $results) . PHP_EOL;
echo PHP_EOL . "=== Summary: {$pass} passed, {$fail} failed ===" . PHP_EOL;
exit($fail > 0 ? 1 : 0);