Files
calcserubi/tests/validate_engine.php
T
2026-10-20 13:16:38 +09:00

71 lines
3.6 KiB
PHP

<?php
// Validates the server-side engine (includes/engine.php) against the values
// recalculated by Microsoft Excel (tests/excel-fixtures.json), using the
// parameters stored in MySQL. Same checks and tolerances as tests/validate.js.
// php tests/validate_engine.php
declare(strict_types=1);
if (PHP_SAPI !== 'cli') {
http_response_code(404);
exit;
}
require __DIR__ . '/../includes/params.php';
require __DIR__ . '/../includes/engine.php';
$fixtures = json_decode(preg_replace('/^\xEF\xBB\xBF/', '', (string)file_get_contents(__DIR__ . '/excel-fixtures.json')), true);
$params = load_params_db();
$rateFields = ['equiv', 'rateGiro', 'rateSimanja', 'rateSimpeda', 'rateDeposito', 'totalRateGiro', 'totalRateSimanja', 'totalRateSimpeda', 'shareNasabah', 'shareBank'];
function engine_view(array $r): array
{
$by = array_column($r['comparison'], null, 'key');
$ok = $r['eligible'];
$pr = $r['products'];
return [
'cashbackGross' => $ok ? $r['cashback']['gross'] : null, 'cashbackNet' => $ok ? $r['cashback']['net'] : null,
'giroNet' => $pr['giro']['bungaNet'], 'simanjaNet' => $pr['simanja']['bungaNet'], 'simpedaNet' => $pr['simpeda']['bungaNet'],
'depositoNet' => $r['deposito']['net'], 'depositoGross' => $r['deposito']['gross'], 'rateDeposito' => $r['deposito']['rate'],
'totalGiro' => $ok ? $by['giro']['total'] : null, 'totalSimanja' => $ok ? $by['simanja']['total'] : null, 'totalSimpeda' => $ok ? $by['simpeda']['total'] : null,
'totalDeposito' => $r['deposito']['net'],
'rateGiro' => $pr['giro']['rate'], 'rateSimanja' => $pr['simanja']['rate'], 'rateSimpeda' => $pr['simpeda']['rate'],
'equiv' => $ok ? $r['cashback']['equiv'] : null,
'totalRateGiro' => $ok ? $by['giro']['totalRate'] : null, 'totalRateSimanja' => $ok ? $by['simanja']['totalRate'] : null, 'totalRateSimpeda' => $ok ? $by['simpeda']['totalRate'] : null,
'penaltyRefund' => $ok ? $r['penalty']['refund'] : null, 'penaltyTax' => $ok ? $r['penalty']['tax'] : null,
'penaltyFee' => $ok ? $r['penalty']['fee'] : null, 'penaltyTotal' => $ok ? $r['penalty']['total'] : null,
'shareNasabah' => $ok ? $r['share'] : null, 'shareBank' => $ok ? $r['shareBank'] : null,
'income' => $r['fund']['income'], 'puabIncome' => $r['fund']['puabIncome'], 'creditIncome' => $r['fund']['creditIncome'],
'spreadSimanja' => $pr['simanja']['spread'], 'spreadSimpeda' => $pr['simpeda']['spread'], 'spreadGiro' => $pr['giro']['spread'],
];
}
$checks = 0;
$failures = [];
foreach ($fixtures['cases'] as $c) {
$got = engine_view(engine_calculate($params, (float)$c['nominal'], (int)$c['tenor'], 'perorangan'));
foreach ($c as $field => $expected) {
if ($field === 'nominal' || $field === 'tenor' || !array_key_exists($field, $got)) {
continue;
}
$checks++;
$actual = $got[$field];
if ($expected === null) {
$pass = $actual === null;
} elseif ($actual === null) {
$pass = false;
} elseif (in_array($field, $rateFields, true)) {
$pass = abs($actual - $expected) < 1e-9;
} else {
$pass = round($actual) == round($expected) && abs($actual - $expected) < 0.01;
}
if (!$pass) {
$failures[] = sprintf('%s / %d bln / %s: excel=%s app=%s', $c['nominal'], $c['tenor'], $field, var_export($expected, true), var_export($actual, true));
}
}
}
printf("PHP engine: %d/%d checks match Excel over %d cases\n", $checks - count($failures), $checks, count($fixtures['cases']));
foreach (array_slice($failures, 0, 20) as $f) {
echo " FAIL $f\n";
}
exit($failures ? 1 : 0);