| Server IP : 103.243.232.44 / Your IP : 216.73.216.250 Web Server : LiteSpeed System : Linux server17213-10344.hostycare.online 5.14.0-687.38.1.el9_8.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Aug 12 17:19:12 EDT 2026 x86_64 User : iamakash ( 1400) PHP Version : 8.1.34 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /home/iamakash/public_html/shaurya.meals28.com/ |
Upload File : |
<?php
require __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/includes/auth.php';
requireLogin();
$pdo = getPDO();
$baseYear = '2011-12';
// Get available period range for dropdowns (2011-12 series drives the assessment date)
$range = $pdo->query(
"SELECT MIN(period_year) AS min_y, MAX(period_year) AS max_y,
MIN(period_year * 100 + period_month) AS min_ym,
MAX(period_year * 100 + period_month) AS max_ym
FROM monthly_indices
WHERE index_value IS NOT NULL AND base_year = '2011-12'"
)->fetch();
$maxYear = ($range && $range['max_y'] !== null) ? (int)$range['max_y'] : (int)date('Y');
$maxMonth = ($range && $range['max_ym'] !== null) ? (int)$range['max_ym'] % 100 : (int)date('m');
$minYear = ($range && $range['min_y'] !== null) ? (int)$range['min_y'] : 2012;
$minMonth = ($range && $range['min_ym'] !== null) ? (int)$range['min_ym'] % 100 : 4;
// Series options (FY start month)
$seriesOptions = [
1 => 'January – December',
2 => 'February – January',
3 => 'March – February',
4 => 'April – March',
5 => 'May – April',
6 => 'June – May',
7 => 'July – June',
8 => 'August – July',
9 => 'September – August',
10 => 'October – September',
11 => 'November – October',
12 => 'December – November',
];
$monthNames = ['','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
$monthFull = ['','January','February','March','April','May','June','July','August','September','October','November','December'];
function getQuarterLabel(int $monthOffset): string {
return 'Q' . (intdiv($monthOffset, 3) + 1);
}
function getHalfLabel(int $monthOffset): string {
return $monthOffset < 6 ? 'H1' : 'H2';
}
// ---------- CALCULATION ----------
$results = null;
$assessLabel = '';
$commNames = [];
$errors = [];
$frequency = 'monthly';
$seriesStart = 4;
$assessYear = 0;
$assessMonth = 0;
$assessPeriodNum = 1;
$showIndex = false;
$showExtrapolation = true;
$beginMonth = 0;
$beginYear = 0;
$extrapolatedValues = [];
$extrapMonths = [];
$extrapLabel = $monthNames[(int)date('n')] . ' ' . date('Y');
$showInterpolation = false;
$interpolatedValues = [];
$interpMonths = [];
$overlapPrefer = '2011-12';
$hasOldData = false;
$newToOld = [];
$oldOverrides = [];
$needsOldOverride = [];
$overrideOptions = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$codes = array_filter(array_map('trim', explode(',', $_POST['codes'] ?? '')));
$showIndex = isset($_POST['show_index']);
$showExtrapolation = isset($_POST['show_extrapolation']);
$showInterpolation = isset($_POST['show_interpolation']);
$beginMonth = (int)($_POST['begin_month'] ?? 0);
$beginYear = (int)($_POST['begin_year'] ?? 0);
$frequency = $_POST['frequency'] ?? 'monthly';
$seriesStart = (int)($_POST['series_start'] ?? 4);
$assessYear = (int)($_POST['assess_year'] ?? 0);
$assessMonth = (int)($_POST['assess_month'] ?? 0);
$overlapPrefer = in_array($_POST['overlap_prefer'] ?? '', ['2004-05','2011-12'], true)
? $_POST['overlap_prefer'] : '2011-12';
foreach ($_POST['old_override'] ?? [] as $nc => $oc) {
$nc = trim($nc); $oc = trim($oc);
if ($nc !== '' && $oc !== '') $oldOverrides[$nc] = $oc;
}
$assessPeriodNum = $frequency === 'monthly'
? $assessMonth
: (int)($_POST['assess_period'] ?? 1);
if (!$codes) {
$errors[] = 'Select at least one commodity.';
}
if ($assessYear < ($showInterpolation ? 1920 : $minYear)) {
$errors[] = 'Select a valid assessment year.';
}
if ($frequency === 'monthly' && ($assessMonth < 1 || $assessMonth > 12)) {
$errors[] = 'Select a valid assessment month.';
}
if ($showExtrapolation) {
if ($beginMonth < 1 || $beginMonth > 12 || $beginYear < 2000) {
$errors[] = 'Select a valid beginning period for extrapolation.';
} elseif ($beginYear * 100 + $beginMonth >= $maxYear * 100 + $maxMonth) {
$errors[] = 'Beginning period must be earlier than the latest available period (' . $monthNames[$maxMonth] . ' ' . $maxYear . ').';
}
}
if (!$errors) {
// Determine assessment period label and months to average
if ($frequency === 'monthly') {
$assessLabel = $monthNames[$assessPeriodNum] . ' ' . $assessYear;
$assessPeriodMonths = [['year' => $assessYear, 'month' => $assessPeriodNum]];
} elseif ($frequency === 'quarterly') {
$startOff = ($assessPeriodNum - 1) * 3;
$assessLabel = "Q{$assessPeriodNum} - {$assessYear}";
$assessPeriodMonths = [];
for ($i = 0; $i < 3; $i++) {
$m = (($seriesStart - 1 + $startOff + $i) % 12) + 1;
$y = $m >= $seriesStart ? $assessYear : $assessYear + 1;
$assessPeriodMonths[] = ['year' => $y, 'month' => $m];
}
} elseif ($frequency === 'half-yearly') {
$startOff = ($assessPeriodNum - 1) * 6;
$assessLabel = "H{$assessPeriodNum} - {$assessYear}";
$assessPeriodMonths = [];
for ($i = 0; $i < 6; $i++) {
$m = (($seriesStart - 1 + $startOff + $i) % 12) + 1;
$y = $m >= $seriesStart ? $assessYear : $assessYear + 1;
$assessPeriodMonths[] = ['year' => $y, 'month' => $m];
}
} else { // annually
$assessLabel = (string)$assessYear;
$assessPeriodMonths = [];
for ($i = 0; $i < 12; $i++) {
$m = (($seriesStart - 1 + $i) % 12) + 1;
$y = $m >= $seriesStart ? $assessYear : $assessYear + 1;
$assessPeriodMonths[] = ['year' => $y, 'month' => $m];
}
}
$assessLastYm = 0;
foreach ($assessPeriodMonths as $apm) {
$assessLastYm = max($assessLastYm, $apm['year'] * 100 + $apm['month']);
}
if ($showExtrapolation && $assessLastYm <= $maxYear * 100 + $maxMonth) {
$showExtrapolation = false;
}
// Fetch assessment-period indices (always from 2011-12 series)
$placeholders = implode(',', array_fill(0, count($codes), '?'));
$assessRawPerYm = [];
if ($frequency === 'monthly') {
$stmt = $pdo->prepare(
"SELECT comm_code, index_value FROM monthly_indices
WHERE comm_code IN ($placeholders) AND base_year = ?
AND period_year = ? AND period_month = ?"
);
$stmt->execute(array_merge($codes, [$baseYear, $assessYear, $assessPeriodNum]));
$assessIndices = [];
foreach ($stmt->fetchAll() as $r) {
$assessIndices[$r['comm_code']] = (float)$r['index_value'];
}
} else {
$mConds = implode(' OR ', array_fill(0, count($assessPeriodMonths), '(period_year=? AND period_month=?)'));
$mParams = [];
foreach ($assessPeriodMonths as $pm) {
$mParams[] = $pm['year'];
$mParams[] = $pm['month'];
}
$stmt = $pdo->prepare(
"SELECT comm_code, period_year, period_month, index_value FROM monthly_indices
WHERE comm_code IN ($placeholders) AND base_year = ?
AND ($mConds) AND index_value IS NOT NULL"
);
$stmt->execute(array_merge($codes, [$baseYear], $mParams));
$assessRaw = [];
foreach ($stmt->fetchAll() as $r) {
$assessRaw[$r['comm_code']][] = (float)$r['index_value'];
$assessRawPerYm[$r['comm_code']][(int)$r['period_year'] * 100 + (int)$r['period_month']] = (float)$r['index_value'];
}
$assessIndices = [];
foreach ($assessRaw as $c => $vals) {
$assessIndices[$c] = count($vals) ? round(array_sum($vals) / count($vals), 2) : null;
}
}
// Extrapolation Part A
if ($showExtrapolation) {
$ey = $maxYear; $em_i = $maxMonth;
while (true) {
$em_i++; if ($em_i > 12) { $em_i = 1; $ey++; }
if ($ey * 100 + $em_i > $assessLastYm) break;
$extrapMonths[] = ['year' => $ey, 'month' => $em_i];
}
if ($extrapMonths) {
$stmt = $pdo->prepare(
"SELECT comm_code, index_value FROM monthly_indices
WHERE comm_code IN ($placeholders) AND base_year = ?
AND period_year = ? AND period_month = ?"
);
$stmt->execute(array_merge($codes, [$baseYear, $beginYear, $beginMonth]));
$beginIndices = [];
foreach ($stmt->fetchAll() as $r) {
$beginIndices[$r['comm_code']] = (float)$r['index_value'];
}
$stmt = $pdo->prepare(
"SELECT comm_code, index_value FROM monthly_indices
WHERE comm_code IN ($placeholders) AND base_year = ?
AND period_year = ? AND period_month = ?"
);
$stmt->execute(array_merge($codes, [$baseYear, $maxYear, $maxMonth]));
$endIndices = [];
foreach ($stmt->fetchAll() as $r) {
$endIndices[$r['comm_code']] = (float)$r['index_value'];
}
$beginPeriodYrs = $beginYear + $beginMonth / 12;
$endPeriodYrs = $maxYear + $maxMonth / 12;
$periodDiff = $endPeriodYrs - $beginPeriodYrs;
$maxDbYm = $maxYear * 100 + $maxMonth;
foreach ($codes as $code) {
$bIdx = $beginIndices[$code] ?? null;
$eIdx = $endIndices[$code] ?? null;
if ($bIdx !== null && $eIdx !== null && $bIdx > 0 && $periodDiff > 0) {
$cagr = pow($eIdx / $bIdx, 1 / $periodDiff) - 1;
foreach ($extrapMonths as $ep) {
$epYrs = $ep['year'] + $ep['month'] / 12;
$extrapolatedValues[$code][$ep['year'] * 100 + $ep['month']] =
round($eIdx * pow(1 + $cagr, $epYrs - $endPeriodYrs), 2);
}
}
}
foreach ($codes as $code) {
if (empty($extrapolatedValues[$code])) continue;
if ($frequency === 'monthly') {
$ym = $assessYear * 100 + $assessPeriodNum;
$assessIndices[$code] = $extrapolatedValues[$code][$ym] ?? null;
} else {
$vals = [];
foreach ($assessPeriodMonths as $apm) {
$apmYm = $apm['year'] * 100 + $apm['month'];
$v = $apmYm <= $maxDbYm
? ($assessRawPerYm[$code][$apmYm] ?? null)
: ($extrapolatedValues[$code][$apmYm] ?? null);
if ($v !== null) $vals[] = $v;
}
$assessIndices[$code] = count($vals) ? round(array_sum($vals) / count($vals), 2) : null;
}
}
}
}
// Interpolation Part A: back-project to Jan 1920 using full-series CAGR
if ($showInterpolation) {
$iy = $minYear; $im = $minMonth;
while (true) {
$im--; if ($im < 1) { $im = 12; $iy--; }
if ($iy < 1920) break;
$interpMonths[] = ['year' => $iy, 'month' => $im];
}
if ($interpMonths) {
$stmt = $pdo->prepare(
"SELECT comm_code, index_value FROM monthly_indices
WHERE comm_code IN ($placeholders) AND base_year = ?
AND period_year = ? AND period_month = ?"
);
$stmt->execute(array_merge($codes, [$baseYear, $minYear, $minMonth]));
$minIndices = [];
foreach ($stmt->fetchAll() as $r) {
$minIndices[$r['comm_code']] = (float)$r['index_value'];
}
$stmt->execute(array_merge($codes, [$baseYear, $maxYear, $maxMonth]));
$maxIndicesI = [];
foreach ($stmt->fetchAll() as $r) {
$maxIndicesI[$r['comm_code']] = (float)$r['index_value'];
}
$minPeriodYrs = $minYear + $minMonth / 12;
$maxPeriodYrs = $maxYear + $maxMonth / 12;
$periodDiffI = $maxPeriodYrs - $minPeriodYrs;
$minDbYm = $minYear * 100 + $minMonth;
foreach ($codes as $code) {
$mIdx = $minIndices[$code] ?? null;
$xIdx = $maxIndicesI[$code] ?? null;
if ($mIdx !== null && $xIdx !== null && $mIdx > 0 && $periodDiffI > 0) {
$cagr = pow($xIdx / $mIdx, 1 / $periodDiffI) - 1;
foreach ($interpMonths as $ep) {
$epYrs = $ep['year'] + $ep['month'] / 12;
$interpolatedValues[$code][$ep['year'] * 100 + $ep['month']] =
round($mIdx * pow(1 + $cagr, $epYrs - $minPeriodYrs), 2);
}
}
}
foreach ($codes as $code) {
if (empty($interpolatedValues[$code])) continue;
if ($frequency === 'monthly') {
$ym = $assessYear * 100 + $assessPeriodNum;
if ($ym < $minDbYm)
$assessIndices[$code] = $interpolatedValues[$code][$ym] ?? null;
} else {
$vals = [];
foreach ($assessPeriodMonths as $apm) {
$apmYm = $apm['year'] * 100 + $apm['month'];
$v = $apmYm >= $minDbYm
? ($assessRawPerYm[$code][$apmYm] ?? null)
: ($interpolatedValues[$code][$apmYm] ?? null);
if ($v !== null) $vals[] = $v;
}
if (count($vals)) $assessIndices[$code] = round(array_sum($vals) / count($vals), 2);
}
}
}
}
// Fetch commodity details (prefer 2011-12, fall back to 2004-05)
$stmt = $pdo->prepare(
"SELECT comm_code, comm_name, level, display_name FROM commodities
WHERE comm_code IN ($placeholders) AND base_year = '2011-12'"
);
$stmt->execute($codes);
foreach ($stmt->fetchAll() as $r) {
$commNames[$r['comm_code']] = $r;
}
$missing = array_diff($codes, array_keys($commNames));
if ($missing) {
$mp = implode(',', array_fill(0, count($missing), '?'));
$stmt = $pdo->prepare(
"SELECT comm_code, comm_name, level, display_name FROM commodities
WHERE comm_code IN ($mp) AND base_year = '2004-05'"
);
$stmt->execute(array_values($missing));
foreach ($stmt->fetchAll() as $r) {
$commNames[$r['comm_code']] = $r;
}
}
// Fetch 2011-12 series monthly data
$stmt = $pdo->prepare(
"SELECT comm_code, period_year, period_month, index_value, is_provisional
FROM monthly_indices
WHERE comm_code IN ($placeholders) AND base_year = '2011-12'
AND index_value IS NOT NULL"
);
$stmt->execute($codes);
$newRaw = []; // [code][ym] = [value, provisional]
foreach ($stmt->fetchAll() as $r) {
$ym = (int)$r['period_year'] * 100 + (int)$r['period_month'];
$newRaw[$r['comm_code']][$ym] = [(float)$r['index_value'], (int)$r['is_provisional']];
}
// Resolve 2004-05 codes via crosswalk
$cwStmt = $pdo->prepare(
"SELECT new_code, old_code FROM comm_crosswalk WHERE new_code IN ($placeholders)"
);
$cwStmt->execute($codes);
$newToOld = [];
foreach ($cwStmt->fetchAll() as $r) {
$newToOld[$r['new_code']] = $r['old_code'];
}
// Detect commodities where no crosswalk entry exists but the same code in 2004-05
// belongs to a different commodity (code collision) or has no 2004-05 entry at all.
$fallbackCodes = array_values(array_filter($codes, fn($c) => !isset($newToOld[$c]) && !isset($oldOverrides[$c])));
if ($fallbackCodes) {
$fbPh = implode(',', array_fill(0, count($fallbackCodes), '?'));
$fbStmt = $pdo->prepare(
"SELECT comm_code, comm_name FROM commodities WHERE comm_code IN ($fbPh) AND base_year = '2004-05'"
);
$fbStmt->execute($fallbackCodes);
$old04Names = [];
foreach ($fbStmt->fetchAll() as $r) $old04Names[$r['comm_code']] = $r['comm_name'];
foreach ($fallbackCodes as $c) {
$newName = $commNames[$c]['comm_name'] ?? null;
$old04Name = $old04Names[$c] ?? null;
if ($newName === null || $old04Name !== $newName) {
$needsOldOverride[$c] = true;
}
}
}
// Fetch dropdown options: 2004-05 children of the same parent group, with data only
foreach ($needsOldOverride as $c => $_) {
if (isset($oldOverrides[$c])) continue;
$parStmt = $pdo->prepare(
"SELECT parent_code FROM commodities WHERE comm_code = ? AND base_year = '2011-12'"
);
$parStmt->execute([$c]);
$parentCode = $parStmt->fetchColumn();
if ($parentCode) {
$optStmt = $pdo->prepare(
"SELECT c.comm_code, c.comm_name
FROM commodities c
WHERE (c.comm_code = ? OR c.parent_code = ?) AND c.base_year = '2004-05'
AND EXISTS (
SELECT 1 FROM monthly_indices m
WHERE m.comm_code = c.comm_code AND m.base_year = '2004-05'
)
ORDER BY c.comm_name"
);
$optStmt->execute([$parentCode, $parentCode]);
} else {
// Fallback: same level
$lvl = (int)($commNames[$c]['level'] ?? 6);
$optStmt = $pdo->prepare(
"SELECT comm_code, comm_name FROM commodities
WHERE base_year = '2004-05' AND level = ?
ORDER BY comm_name"
);
$optStmt->execute([$lvl]);
}
$overrideOptions[$c] = $optStmt->fetchAll();
}
// Build effective old-code map: crosswalk > manual override > same-code (validated safe)
$effectiveOldMap = [];
foreach ($codes as $c) {
if (isset($oldOverrides[$c])) $effectiveOldMap[$c] = $oldOverrides[$c];
elseif (isset($newToOld[$c])) $effectiveOldMap[$c] = $newToOld[$c];
elseif (!isset($needsOldOverride[$c])) $effectiveOldMap[$c] = $c;
// else: collision/missing and no override yet — skip, no old data fetched
}
$oldFetchCodes = array_values($effectiveOldMap);
$oldToNew = [];
foreach ($effectiveOldMap as $nc => $oc) $oldToNew[$oc] = $nc;
// Fetch 2004-05 series monthly data
$oldRaw = [];
if ($oldFetchCodes) {
$oldPh = implode(',', array_fill(0, count($oldFetchCodes), '?'));
$stmt = $pdo->prepare(
"SELECT comm_code, period_year, period_month, index_value, is_provisional
FROM monthly_indices
WHERE comm_code IN ($oldPh) AND base_year = '2004-05'
AND index_value IS NOT NULL"
);
$stmt->execute($oldFetchCodes);
foreach ($stmt->fetchAll() as $r) {
$ym = (int)$r['period_year'] * 100 + (int)$r['period_month'];
$key = $oldToNew[$r['comm_code']] ?? $r['comm_code'];
$oldRaw[$key][$ym] = [(float)$r['index_value'], (int)$r['is_provisional']];
}
}
// Linking factor from first common period (both series have data)
// Monthly: first common month's index values
// Q/H/Y: average of both series over the period that contains the first common month
$links = [];
foreach ($codes as $code) {
$commonYms = array_intersect(array_keys($newRaw[$code] ?? []), array_keys($oldRaw[$code] ?? []));
if (!$commonYms) continue;
sort($commonYms);
$minYm = $commonYms[0];
$fcY = intdiv($minYm, 100);
$fcM = $minYm % 100;
if ($frequency === 'monthly') {
$links[$code] = [
$oldRaw[$code][$minYm][0],
$newRaw[$code][$minYm][0],
$monthNames[$fcM] . ' ' . $fcY, // trans_label
];
} else {
$offset = ($fcM - $seriesStart + 12) % 12;
$fy = $fcM >= $seriesStart ? $fcY : $fcY - 1;
if ($frequency === 'quarterly') {
$pNum = intdiv($offset, 3) + 1;
$pStartOff = ($pNum - 1) * 3;
$periodLen = 3;
$transLabel = "Q{$pNum} {$fy}";
} elseif ($frequency === 'half-yearly') {
$pNum = $offset < 6 ? 1 : 2;
$pStartOff = ($pNum - 1) * 6;
$periodLen = 6;
$transLabel = "H{$pNum} {$fy}";
} else {
$pNum = 1; $pStartOff = 0; $periodLen = 12;
$transLabel = (string)$fy;
}
$oldVals = []; $newVals = [];
for ($gi = 0; $gi < $periodLen; $gi++) {
$gm = (($seriesStart - 1 + $pStartOff + $gi) % 12) + 1;
$gy = $gm >= $seriesStart ? $fy : $fy + 1;
$gym = $gy * 100 + $gm;
if (isset($oldRaw[$code][$gym]) && isset($newRaw[$code][$gym])) {
$oldVals[] = $oldRaw[$code][$gym][0];
$newVals[] = $newRaw[$code][$gym][0];
}
}
if ($oldVals && $newVals) {
$links[$code] = [
round(array_sum($oldVals) / count($oldVals), 4),
round(array_sum($newVals) / count($newVals), 4),
$transLabel,
];
}
}
}
// Build unified allData: merge both series, compute composite per period
// Composite is always expressed in 2011-12 scale.
// Overlap (Apr 2012–Mar 2017): use $overlapPrefer to decide which drives composite.
$allData = [];
$hasOldData = false;
foreach ($codes as $code) {
$allYms = array_unique(array_merge(
array_keys($newRaw[$code] ?? []),
array_keys($oldRaw[$code] ?? [])
));
sort($allYms);
$link = $links[$code] ?? null; // [old_avg, new_avg]
foreach ($allYms as $ym) {
$yr = intdiv($ym, 100);
$mo = $ym % 100;
$ne = $newRaw[$code][$ym] ?? null; // [value, prov]
$oe = $oldRaw[$code][$ym] ?? null;
$nv = $ne !== null ? $ne[0] : null;
$ov = $oe !== null ? $oe[0] : null;
// Composite expressed in the selected series' scale.
// 2004-05 preferred: old periods → old directly; new-only → new × (old_avg/new_avg)
// 2011-12 preferred: new periods → new directly; old-only → old × (new_avg/old_avg)
if ($overlapPrefer === '2004-05') {
// Primary: new × (old_avg/new_avg); fallback to old when no new data
if ($nv !== null) {
$composite = ($link && $link[1] > 0)
? round($nv * ($link[0] / $link[1]), 2)
: $nv;
} elseif ($ov !== null) {
$composite = $ov;
} else {
$composite = null;
}
} else {
// Prefer 2011-12 series where available; fall back to old as-is
if ($nv !== null) {
$composite = $nv;
} elseif ($ov !== null) {
$composite = $ov;
} else {
$composite = null;
}
}
if ($ov !== null) $hasOldData = true;
$allData[$code][] = [
'comm_code' => $code,
'period_year' => $yr,
'period_month' => $mo,
'old_index' => $ov,
'new_index' => $nv,
'composite' => $composite,
'index_value' => $composite, // used by grouped logic
'is_provisional' => max($ne ? $ne[1] : 0, $oe ? $oe[1] : 0),
];
}
}
// Convert assessment index to match the composite's scale.
// 2004-05 preferred: composite in old scale; assessment (2011-12) → old via × (old_avg/new_avg)
// 2011-12 preferred: composite in new scale; assessment already in 2011-12, no conversion.
$assessComposites = [];
foreach ($codes as $code) {
$ai = $assessIndices[$code] ?? null;
$lk = $links[$code] ?? null;
if ($overlapPrefer === '2004-05') {
$assessComposites[$code] = ($ai !== null && $lk && $lk[1] > 0)
? round($ai * ($lk[0] / $lk[1]), 2)
: $ai;
} else {
$assessComposites[$code] = $ai;
}
}
$results = [];
foreach ($codes as $code) {
$assessIdx = $assessComposites[$code] ?? null;
if ($assessIdx === null) continue;
$rows = $allData[$code] ?? [];
if ($frequency === 'monthly') {
foreach ($rows as $r) {
$composite = $r['composite'];
if ($composite === null) continue;
$factor = $composite > 0 ? round($assessIdx / $composite, 4) : null;
$period = sprintf('INDX%02d%04d', (int)$r['period_month'], (int)$r['period_year']);
$periodLabel = $monthNames[(int)$r['period_month']] . ' ' . $r['period_year'];
$results[] = [
'comm_code' => $code,
'period' => $period,
'period_label' => $periodLabel,
'period_sort' => (int)$r['period_year'] * 100 + (int)$r['period_month'],
'old_index' => $r['old_index'],
'new_index' => $r['new_index'],
'composite' => $composite,
'index_value' => $composite,
'assess_index' => $assessIdx,
'factor' => $factor,
'provisional' => (int)$r['is_provisional'],
];
}
} else {
// Group months into periods based on series start and frequency
$grouped = [];
foreach ($rows as $r) {
$y = (int)$r['period_year'];
$m = (int)$r['period_month'];
$offset = ($m - $seriesStart + 12) % 12;
$fy = $m >= $seriesStart ? $y : $y - 1;
if ($frequency === 'quarterly') {
$qNum = intdiv($offset, 3) + 1;
$key = "Q{$qNum} - {$fy}";
$sort = $fy * 10 + $qNum;
$grouped[$key]['period_num'] = $qNum;
} elseif ($frequency === 'half-yearly') {
$hNum = $offset < 6 ? 1 : 2;
$key = "H{$hNum} - {$fy}";
$sort = $fy * 10 + $hNum;
$grouped[$key]['period_num'] = $hNum;
} else {
$key = (string)$fy;
$sort = $fy;
$grouped[$key]['period_num'] = 1;
}
if ($r['composite'] !== null) $grouped[$key]['values'][] = $r['composite'];
if ($r['old_index'] !== null) $grouped[$key]['old_values'][] = $r['old_index'];
if ($r['new_index'] !== null) $grouped[$key]['new_values'][] = $r['new_index'];
$grouped[$key]['sort'] = $sort;
$grouped[$key]['fy'] = $fy;
$grouped[$key]['prov'] = max($grouped[$key]['prov'] ?? 0, (int)$r['is_provisional']);
}
// Fill extrapolated months into partially-covered periods
if ($showExtrapolation && !empty($extrapolatedValues[$code])) {
$maxDbYmLocal = $maxYear * 100 + $maxMonth;
$periodLen = $frequency === 'quarterly' ? 3 : ($frequency === 'half-yearly' ? 6 : 12);
$startStep = $frequency === 'quarterly' ? 3 : ($frequency === 'half-yearly' ? 6 : 0);
$lkQ = $links[$code] ?? null;
foreach ($grouped as $gKey => &$gData) {
$gFy = $gData['fy'];
$gPNum = $gData['period_num'];
$gStartOff = ($gPNum - 1) * $startStep;
for ($gi = 0; $gi < $periodLen; $gi++) {
$gm = (($seriesStart - 1 + $gStartOff + $gi) % 12) + 1;
$gy = $gm >= $seriesStart ? $gFy : $gFy + 1;
$gym = $gy * 100 + $gm;
if ($gym <= $maxDbYmLocal) continue;
$ev = $extrapolatedValues[$code][$gym] ?? null;
if ($ev !== null) {
$evComp = ($overlapPrefer === '2004-05' && $lkQ && $lkQ[1] > 0)
? round($ev * ($lkQ[0] / $lkQ[1]), 2)
: $ev;
$gData['values'][] = $evComp;
$gData['new_values'][] = $ev;
$gData['extrapolated'] = true;
}
}
}
unset($gData);
$aAssessSort = $frequency === 'annually'
? $assessYear
: $assessYear * 10 + $assessPeriodNum;
foreach ($extrapMonths as $ep) {
$em = $ep['month']; $ey = $ep['year'];
$offset = ($em - $seriesStart + 12) % 12;
$pFy = $em >= $seriesStart ? $ey : $ey - 1;
if ($frequency === 'quarterly') {
$pNum = intdiv($offset, 3) + 1;
$pKey = "Q{$pNum} - {$pFy}";
$pSort = $pFy * 10 + $pNum;
} elseif ($frequency === 'half-yearly') {
$pNum = $offset < 6 ? 1 : 2;
$pKey = "H{$pNum} - {$pFy}";
$pSort = $pFy * 10 + $pNum;
} else {
$pNum = 1;
$pKey = (string)$pFy;
$pSort = $pFy;
}
if (isset($grouped[$pKey]) || $pSort > $aAssessSort) continue;
$pStartOff = ($pNum - 1) * $startStep;
$pVals = $pNewVals = [];
for ($gi = 0; $gi < $periodLen; $gi++) {
$gm = (($seriesStart - 1 + $pStartOff + $gi) % 12) + 1;
$gy = $gm >= $seriesStart ? $pFy : $pFy + 1;
$gym = $gy * 100 + $gm;
$ev = $gym <= $maxDbYmLocal
? ($assessRawPerYm[$code][$gym] ?? null)
: ($extrapolatedValues[$code][$gym] ?? null);
if ($ev !== null) {
$evComp = ($overlapPrefer === '2004-05' && $lkQ && $lkQ[1] > 0)
? round($ev * ($lkQ[0] / $lkQ[1]), 2)
: $ev;
$pVals[] = $evComp;
$pNewVals[] = $ev;
}
}
if ($pVals) {
$grouped[$pKey] = [
'values' => $pVals,
'new_values' => $pNewVals,
'old_values' => [],
'sort' => $pSort,
'fy' => $pFy,
'period_num' => $pNum,
'prov' => 0,
'extrapolated'=> true,
];
}
}
}
// Fill interpolated months into partially-covered periods (Q/H/A)
if ($showInterpolation && !empty($interpolatedValues[$code])) {
$minDbYmLocal = $minYear * 100 + $minMonth;
$periodLen = $frequency === 'quarterly' ? 3 : ($frequency === 'half-yearly' ? 6 : 12);
$startStep = $frequency === 'quarterly' ? 3 : ($frequency === 'half-yearly' ? 6 : 0);
$lkI = $links[$code] ?? null;
foreach ($grouped as $gKey => &$gData) {
$gFy = $gData['fy'];
$gPNum = $gData['period_num'];
$gStartOff = ($gPNum - 1) * $startStep;
for ($gi = 0; $gi < $periodLen; $gi++) {
$gm = (($seriesStart - 1 + $gStartOff + $gi) % 12) + 1;
$gy = $gm >= $seriesStart ? $gFy : $gFy + 1;
$gym = $gy * 100 + $gm;
if ($gym >= $minDbYmLocal) continue;
$iv = $interpolatedValues[$code][$gym] ?? null;
if ($iv !== null) {
$ivComp = ($overlapPrefer === '2004-05' && $lkI && $lkI[1] > 0)
? round($iv * ($lkI[0] / $lkI[1]), 2) : $iv;
$gData['values'][] = $ivComp;
$gData['new_values'][] = $iv;
$gData['interpolated'] = true;
}
}
}
unset($gData);
$aAssessSortI = $frequency === 'annually'
? $assessYear : $assessYear * 10 + $assessPeriodNum;
foreach ($interpMonths as $ep) {
$em = $ep['month']; $ey = $ep['year'];
$offset = ($em - $seriesStart + 12) % 12;
$pFy = $em >= $seriesStart ? $ey : $ey - 1;
if ($frequency === 'quarterly') {
$pNum = intdiv($offset, 3) + 1;
$pKey = "Q{$pNum} - {$pFy}";
$pSort = $pFy * 10 + $pNum;
} elseif ($frequency === 'half-yearly') {
$pNum = $offset < 6 ? 1 : 2;
$pKey = "H{$pNum} - {$pFy}";
$pSort = $pFy * 10 + $pNum;
} else {
$pNum = 1;
$pKey = (string)$pFy;
$pSort = $pFy;
}
if (isset($grouped[$pKey]) || $pSort < $aAssessSortI) continue;
$pStartOff = ($pNum - 1) * $startStep;
$pVals = $pNewVals = [];
for ($gi = 0; $gi < $periodLen; $gi++) {
$gm = (($seriesStart - 1 + $pStartOff + $gi) % 12) + 1;
$gy = $gm >= $seriesStart ? $pFy : $pFy + 1;
$gym = $gy * 100 + $gm;
$iv = $gym < $minDbYmLocal
? ($interpolatedValues[$code][$gym] ?? null)
: ($assessRawPerYm[$code][$gym] ?? null);
if ($iv !== null) {
$ivComp = ($overlapPrefer === '2004-05' && $lkI && $lkI[1] > 0)
? round($iv * ($lkI[0] / $lkI[1]), 2) : $iv;
$pVals[] = $ivComp;
$pNewVals[] = $iv;
}
}
if ($pVals) {
$grouped[$pKey] = [
'values' => $pVals,
'new_values' => $pNewVals,
'old_values' => [],
'sort' => $pSort,
'fy' => $pFy,
'period_num' => $pNum,
'prov' => 0,
'interpolated' => true,
];
}
}
}
foreach ($grouped as $label => $g) {
$avg = !empty($g['values']) ? round(array_sum($g['values']) / count($g['values']), 2) : null;
$oldAvg = !empty($g['old_values']) ? round(array_sum($g['old_values']) / count($g['old_values']), 2) : null;
$newAvg = !empty($g['new_values']) ? round(array_sum($g['new_values']) / count($g['new_values']), 2) : null;
$factor = ($avg && $avg > 0) ? round($assessIdx / $avg, 4) : null;
$results[] = [
'comm_code' => $code,
'period' => $label,
'period_label' => $label,
'period_sort' => $g['sort'],
'old_index' => $oldAvg,
'new_index' => $newAvg,
'composite' => $avg,
'index_value' => $avg,
'assess_index' => $assessIdx,
'factor' => $factor,
'provisional' => $g['prov'],
'extrapolated' => $g['extrapolated'] ?? false,
'interpolated' => $g['interpolated'] ?? false,
];
}
}
}
// Save to history
$histUser = currentUser();
if ($histUser) {
$commLabels = [];
foreach ($codes as $c) {
$commLabels[] = [$c, $commNames[$c]['comm_name'] ?? $c];
}
$pdo->prepare(
'INSERT INTO calc_history
(user_id, codes, comm_labels, frequency, series_start, assess_year, assess_period,
assess_label, show_index, show_extrap, begin_month, begin_year, result_count)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)'
)->execute([
$histUser['id'],
implode(',', $codes),
json_encode($commLabels, JSON_UNESCAPED_UNICODE),
$frequency,
$seriesStart,
$assessYear,
$assessPeriodNum,
$assessLabel,
$showIndex ? 1 : 0,
$showExtrapolation ? 1 : 0,
$showExtrapolation ? $beginMonth : null,
$showExtrapolation ? $beginYear : null,
0,
]);
$histLastId = (int)$pdo->lastInsertId();
}
// Drop periods after the assessment period
if ($frequency === 'monthly') {
$assessPeriodSort = $assessYear * 100 + $assessPeriodNum;
} elseif ($frequency === 'quarterly' || $frequency === 'half-yearly') {
$assessPeriodSort = $assessYear * 10 + $assessPeriodNum;
} else {
$assessPeriodSort = $assessYear;
}
$results = array_values(array_filter($results, fn($r) => $r['period_sort'] <= $assessPeriodSort));
usort($results, function ($a, $b) {
$c = strcmp($a['comm_code'], $b['comm_code']);
return $c !== 0 ? $c : $b['period_sort'] - $a['period_sort'];
});
if (!empty($histLastId)) {
$pdo->prepare('UPDATE calc_history SET result_count = ? WHERE id = ?')
->execute([count($results), $histLastId]);
}
// Extrapolation Part B: inject extrapolated rows at top (monthly only)
if ($showExtrapolation && $extrapMonths && $frequency === 'monthly') {
$newResults = [];
$seenCodes = [];
foreach ($results as $row) {
if (!isset($seenCodes[$row['comm_code']])) {
$seenCodes[$row['comm_code']] = true;
$code = $row['comm_code'];
foreach (array_reverse($extrapMonths) as $ep) {
$ym = $ep['year'] * 100 + $ep['month'];
$ev = $extrapolatedValues[$code][$ym] ?? null;
if ($ev === null) continue;
$aIdx = $assessComposites[$code] ?? null;
$lk = $links[$code] ?? null;
$evComp = ($overlapPrefer === '2004-05' && $lk && $lk[1] > 0)
? round($ev * ($lk[0] / $lk[1]), 2)
: $ev;
$factor = ($evComp > 0 && $aIdx !== null) ? round($aIdx / $evComp, 4) : null;
$newResults[] = [
'comm_code' => $code,
'period' => sprintf('INDX%02d%04d', $ep['month'], $ep['year']),
'period_label' => $monthNames[$ep['month']] . ' ' . $ep['year'],
'period_sort' => $ym,
'old_index' => null,
'new_index' => $ev,
'composite' => $evComp,
'index_value' => $evComp,
'assess_index' => $aIdx ?? 0,
'factor' => $factor,
'provisional' => 0,
'extrapolated' => true,
'interpolated' => false,
];
}
}
$newResults[] = $row;
}
$results = $newResults;
}
// Interpolation Part B: append interpolated rows at bottom (monthly only)
if ($showInterpolation && $interpMonths && $frequency === 'monthly') {
$seenCodes = [];
$interpRows = [];
foreach ($results as $row) {
if (!isset($seenCodes[$row['comm_code']])) {
$seenCodes[$row['comm_code']] = true;
$code = $row['comm_code'];
$aIdx = $assessComposites[$code] ?? null;
$lk = $links[$code] ?? null;
foreach ($interpMonths as $ep) {
$ym = $ep['year'] * 100 + $ep['month'];
$iv = $interpolatedValues[$code][$ym] ?? null;
if ($iv === null) continue;
$ivComp = ($overlapPrefer === '2004-05' && $lk && $lk[1] > 0)
? round($iv * ($lk[0] / $lk[1]), 2) : $iv;
$factor = ($ivComp > 0 && $aIdx !== null) ? round($aIdx / $ivComp, 4) : null;
$interpRows[] = [
'comm_code' => $code,
'period' => sprintf('INTP%02d%04d', $ep['month'], $ep['year']),
'period_label' => $monthNames[$ep['month']] . ' ' . $ep['year'],
'period_sort' => $ym,
'old_index' => null,
'new_index' => $iv,
'composite' => $ivComp,
'index_value' => $ivComp,
'assess_index' => $aIdx ?? 0,
'factor' => $factor,
'provisional' => 0,
'extrapolated' => false,
'interpolated' => true,
];
}
}
}
$results = array_merge($results, $interpRows);
}
}
}
$pageTitle = 'Calculate Index Factor — Shaurya';
require __DIR__ . '/includes/header.php';
?>
<div class="row g-4">
<div class="col-lg-4">
<div class="card"><div class="card-body">
<h5 class="mb-3">Index Factor Calculator</h5>
<?php if ($errors): ?>
<div class="alert alert-danger alert-sm py-2">
<?php foreach ($errors as $e): ?><div><?= h($e) ?></div><?php endforeach; ?>
</div>
<?php endif; ?>
<form method="post" id="calcForm">
<!-- Commodity search -->
<div class="mb-3">
<label class="form-label">Commodities</label>
<input type="text" id="commSearch" class="form-control" placeholder="Search by name or code..." autocomplete="off">
<div id="searchResults" class="list-group mt-1" style="max-height:200px;overflow-y:auto;display:none;position:absolute;z-index:99;width:calc(100% - 2rem);"></div>
<input type="hidden" name="codes" id="codesInput" value="">
<div id="selectedComms" class="mt-2"></div>
</div>
<!-- Frequency -->
<div class="mb-3">
<label class="form-label">Frequency</label>
<select name="frequency" id="freqSelect" class="form-select">
<option value="monthly" <?= ($frequency ?? 'monthly') === 'monthly' ? 'selected' : '' ?>>Monthly</option>
<option value="quarterly" <?= ($frequency ?? '') === 'quarterly' ? 'selected' : '' ?>>Quarterly</option>
<option value="half-yearly" <?= ($frequency ?? '') === 'half-yearly' ? 'selected' : '' ?>>Half-yearly</option>
<option value="annually" <?= ($frequency ?? '') === 'annually' ? 'selected' : '' ?>>Annually</option>
</select>
</div>
<!-- Financial Year Series (Q/H/A only) -->
<div class="mb-3" id="seriesGroup" style="display:none;">
<label class="form-label">Financial Year Series</label>
<select name="series_start" class="form-select">
<?php foreach ($seriesOptions as $sm => $slabel): ?>
<option value="<?= $sm ?>" <?= $sm === ($seriesStart ?? 4) ? 'selected' : '' ?>><?= h($slabel) ?></option>
<?php endforeach; ?>
</select>
<div class="form-text">Defines which month starts the financial year.</div>
</div>
<!-- Assessment Period + Year -->
<div class="row mb-3">
<div class="col-6" id="assessPeriodCol">
<label class="form-label" id="assessPeriodLabel">Assessment Month</label>
<select name="assess_month" id="assessMonthSel" class="form-select">
<?php for ($m = 1; $m <= 12; $m++): ?>
<option value="<?= $m ?>" <?= $m === ($assessMonth ?: $maxMonth) ? 'selected' : '' ?>><?= $monthFull[$m] ?></option>
<?php endfor; ?>
</select>
<select name="assess_period" id="assessPeriodSel" class="form-select" style="display:none;" disabled></select>
</div>
<div class="col-6">
<label class="form-label">Assessment Year</label>
<select name="assess_year" id="assessYearSel" class="form-select">
<?php
$yearFloor = $showInterpolation ? 1920 : $minYear;
for ($y = $maxYear; $y >= $yearFloor; $y--): ?>
<option value="<?= $y ?>" <?= $y === ($assessYear ?: $maxYear) ? 'selected' : '' ?>><?= $y ?></option>
<?php endfor; ?>
</select>
</div>
</div>
<!-- Overlap series preference -->
<div class="mb-3 p-2 border rounded bg-light">
<div class="form-label mb-1 small fw-semibold">Overlap period preference <span class="text-muted fw-normal">(Apr 2012 – Mar 2017)</span></div>
<div class="d-flex gap-3">
<div class="form-check mb-0">
<input class="form-check-input" type="radio" name="overlap_prefer" id="overlapNew" value="2011-12"
<?= $overlapPrefer === '2011-12' ? 'checked' : '' ?>>
<label class="form-check-label small" for="overlapNew">2011‑12 series</label>
</div>
<div class="form-check mb-0">
<input class="form-check-input" type="radio" name="overlap_prefer" id="overlapOld" value="2004-05"
<?= $overlapPrefer === '2004-05' ? 'checked' : '' ?>>
<label class="form-check-label small" for="overlapOld">2004‑05 series</label>
</div>
</div>
<div class="form-text">For periods where both series have data, which drives the composite index.</div>
</div>
<div class="mb-2 form-check form-switch">
<input class="form-check-input" type="checkbox" name="show_index" id="showIndexToggle" <?= $showIndex ? 'checked' : '' ?>>
<label class="form-check-label" for="showIndexToggle">Show Index Values</label>
</div>
<div class="mb-2 form-check form-switch" id="extrapToggleWrap">
<input class="form-check-input" type="checkbox" name="show_extrapolation" id="showExtrapToggle" <?= $showExtrapolation ? 'checked' : '' ?>>
<label class="form-check-label" for="showExtrapToggle">Show Extrapolated Value</label>
</div>
<div class="mb-2 form-check form-switch">
<input class="form-check-input" type="checkbox" name="show_interpolation" id="showInterpToggle" <?= $showInterpolation ? 'checked' : '' ?>>
<label class="form-check-label" for="showInterpToggle">Show Back-interpolated Values <span class="text-muted small">(to 1920)</span></label>
</div>
<div id="extrapOptions" style="<?= $showExtrapolation ? '' : 'display:none;' ?>">
<div class="row g-2 mb-1">
<div class="col-6">
<label class="form-label form-label-sm mb-1">Beginning Month</label>
<select name="begin_month" class="form-select form-select-sm">
<?php for ($m = 1; $m <= 12; $m++): ?>
<option value="<?= $m ?>" <?= $m === ($beginMonth ?: $maxMonth) ? 'selected' : '' ?>><?= $monthFull[$m] ?></option>
<?php endfor; ?>
</select>
</div>
<div class="col-6">
<label class="form-label form-label-sm mb-1">Beginning Year</label>
<select name="begin_year" class="form-select form-select-sm">
<?php
$defaultBeginYear = max($minYear, $maxYear - 10);
for ($y = $maxYear - 1; $y >= $minYear; $y--): ?>
<option value="<?= $y ?>" <?= $y === ($beginYear ?: $defaultBeginYear) ? 'selected' : '' ?>><?= $y ?></option>
<?php endfor; ?>
</select>
</div>
</div>
<div class="small text-muted mb-2">
Latest available: <?= $monthNames[$maxMonth] ?> <?= $maxYear ?>.
Will extrapolate up to the selected assessment period.
</div>
</div>
<button type="submit" class="btn btn-primary w-100">Calculate</button>
</form>
<div class="text-center mt-2">
<a href="<?= base_url('history.php') ?>" class="small text-muted">View calculation history</a>
</div>
</div></div>
</div>
<div class="col-lg-8">
<?php if ($results !== null): ?>
<style>
.table-compact { font-size: 0.75rem; }
.table-compact th, .table-compact td { padding: 0.18rem 0.35rem !important; line-height: 1.3; }
.table-compact .code-chip { font-size: 0.68rem; }
.extrap-row td { background-color: rgba(255,193,7,0.18) !important; font-style: italic; }
.interp-row td { background-color: rgba(13,202,240,0.12) !important; font-style: italic; }
.col-old { background-color: rgba(13,110,253,0.06); }
.col-new { background-color: rgba(25,135,84,0.06); }
.col-comp { background-color: rgba(108,117,125,0.08); font-weight: 500; }
</style>
<div class="card"><div class="card-body p-2">
<div class="d-flex justify-content-between align-items-center mb-2">
<div>
<span class="fw-semibold" style="font-size:0.85rem;">Results — Assessment: <?= h($assessLabel) ?></span>
<?php if ($hasOldData): ?>
<span class="badge bg-info text-dark ms-2" style="font-size:0.65rem;">Series linked</span>
<?php endif; ?>
</div>
<?php if ($results): ?>
<a href="<?= base_url('export.php') ?>?<?= http_build_query($_POST) ?>" class="btn btn-sm btn-success">Export Excel</a>
<?php endif; ?>
</div>
<?php if (!empty($overrideOptions)): ?>
<div class="alert alert-warning py-2 px-3 mb-2" style="font-size:0.82rem;">
<strong>2004‑05 data not available</strong> for the following commodit<?= count($overrideOptions) > 1 ? 'ies' : 'y' ?>.
Select a reference commodity from the 2004‑05 series to include historical data in the calculation.
<form method="post" class="mt-2 mb-0">
<?php
// Preserve all original calculation params
$preserveFields = ['codes','frequency','series_start','assess_year','assess_month',
'assess_period','begin_month','begin_year','overlap_prefer'];
foreach ($preserveFields as $f):
if (isset($_POST[$f])): ?>
<input type="hidden" name="<?= h($f) ?>" value="<?= h($_POST[$f]) ?>">
<?php endif; endforeach;
if ($showIndex): ?><input type="hidden" name="show_index" value="1"><?php endif;
if ($showExtrapolation): ?><input type="hidden" name="show_extrapolation" value="1"><?php endif;
// Preserve already-resolved overrides
foreach ($oldOverrides as $nc => $oc): ?>
<input type="hidden" name="old_override[<?= h($nc) ?>]" value="<?= h($oc) ?>">
<?php endforeach; ?>
<?php foreach ($overrideOptions as $nc => $opts):
$commLabel = $commNames[$nc]['comm_name'] ?? $nc;
// Get parent name for context label
$parNameStmt = $pdo->prepare(
"SELECT p.comm_name FROM commodities c
JOIN commodities p ON p.comm_code = c.parent_code AND p.base_year = '2004-05'
WHERE c.comm_code = (
SELECT parent_code FROM commodities WHERE comm_code = ? AND base_year = '2011-12'
) AND c.base_year = '2004-05' LIMIT 1"
);
$parNameStmt->execute([$nc]);
$parentGroupName = $parNameStmt->fetchColumn() ?: '';
?>
<div class="mb-1">
<div class="mb-1"><span class="fw-semibold"><?= h($commLabel) ?></span><?php if ($parentGroupName): ?> <span class="text-muted" style="font-size:0.78rem;">— under <?= h($parentGroupName) ?></span><?php endif; ?></div>
<select name="old_override[<?= h($nc) ?>]" class="form-select form-select-sm" style="max-width:360px;" required>
<option value="">— Select 2004‑05 reference —</option>
<?php foreach ($opts as $opt): ?>
<option value="<?= h($opt['comm_code']) ?>"><?= h($opt['comm_name']) ?> (<?= h($opt['comm_code']) ?>)</option>
<?php endforeach; ?>
</select>
</div>
<?php endforeach; ?>
<button type="submit" class="btn btn-warning btn-sm mt-1">Apply & Recalculate</button>
</form>
</div>
<?php endif; ?>
<?php if (!$results): ?>
<p class="text-muted mb-0">No results. Check that the assessment date has data for the selected commodities.</p>
<?php else: ?>
<div class="table-responsive" style="max-height: 78vh; overflow-y: auto;">
<table class="table table-sm table-bordered align-middle text-end table-compact">
<thead class="table-light sticky-top">
<tr>
<th class="text-start">Commodity</th>
<th class="text-start">Period</th>
<?php
$thOldCodes = implode(', ', array_map(fn($c) => $newToOld[$c] ?? $c, $codes ?? []));
$thNewCodes = implode(', ', $codes ?? []);
$thMfParts = [];
foreach ($codes ?? [] as $c) {
$lkTh = $links[$c] ?? null;
$mfTh = ($overlapPrefer === '2004-05')
? (($lkTh && $lkTh[1] > 0) ? round($lkTh[0] / $lkTh[1], 4) : null)
: (($lkTh && $lkTh[0] > 0) ? round($lkTh[1] / $lkTh[0], 4) : null);
if ($mfTh !== null) $thMfParts[] = number_format($mfTh, 4);
}
$thMf = implode(', ', $thMfParts);
?>
<?php if ($hasOldData && $showIndex): ?>
<th class="col-old">2004‑05 Index<br><code class="code-chip fw-normal text-muted"><?= h($thOldCodes) ?></code></th>
<th class="col-new">2011‑12 Index<br><code class="code-chip fw-normal text-muted"><?= h($thNewCodes) ?></code></th>
<th class="col-comp">Composite (<?= h($overlapPrefer) ?>)<?php if ($thMf): ?><br><span class="fw-normal text-muted" style="font-size:0.68rem;">MF: <?= h($thMf) ?></span><?php endif; ?></th>
<th>Assess Index</th>
<?php elseif ($hasOldData): ?>
<th class="col-old">2004‑05 Index<br><code class="code-chip fw-normal text-muted"><?= h($thOldCodes) ?></code></th>
<th class="col-new">2011‑12 Index<br><code class="code-chip fw-normal text-muted"><?= h($thNewCodes) ?></code></th>
<th class="col-comp">Composite (<?= h($overlapPrefer) ?>)<?php if ($thMf): ?><br><span class="fw-normal text-muted" style="font-size:0.68rem;">MF: <?= h($thMf) ?></span><?php endif; ?></th>
<?php elseif ($showIndex): ?>
<th>Index</th>
<th>Assessment Index</th>
<?php endif; ?>
<th>Index Factor</th>
</tr>
</thead>
<tbody>
<?php
$prevCode = '';
foreach ($results as $r):
$showComm = $r['comm_code'] !== $prevCode;
$prevCode = $r['comm_code'];
$comm = $commNames[$r['comm_code']] ?? null;
$lvl = $comm ? (int)$comm['level'] : 6;
$safeCode = h($r['comm_code']);
$lk = $links[$r['comm_code']] ?? null;
$mf = ($overlapPrefer === '2004-05')
? (($lk && $lk[1] > 0) ? round($lk[0] / $lk[1], 4) : null)
: (($lk && $lk[0] > 0) ? round($lk[1] / $lk[0], 4) : null);
?>
<tr class="<?= ($r['provisional'] ?? 0) ? 'table-warning' : (($r['extrapolated'] ?? false) ? 'extrap-row' : (($r['interpolated'] ?? false) ? 'interp-row' : '')) ?> <?= $showComm ? 'comm-header' : 'comm-detail' ?>"
data-comm="<?= $safeCode ?>">
<td class="text-start level-<?= $lvl ?>">
<?php if ($showComm && $comm): ?>
<span class="comm-toggle" style="cursor:pointer;user-select:none;">
<span class="comm-arrow me-1">▼</span>
<strong><?= h($comm['comm_name']) ?></strong>
</span>
<?php endif; ?>
</td>
<td class="text-start"><?= h($r['period_label']) ?><?php if ($r['extrapolated'] ?? false): ?> <span class="badge bg-warning text-dark" style="font-size:0.6rem;">extrap</span><?php elseif ($r['interpolated'] ?? false): ?> <span class="badge bg-info text-dark" style="font-size:0.6rem;">interp</span><?php endif; ?></td>
<?php if ($hasOldData && $showIndex): ?>
<td class="col-old"><?= $r['old_index'] !== null ? number_format($r['old_index'], 2) : '—' ?></td>
<td class="col-new"><?= $r['new_index'] !== null ? number_format($r['new_index'], 2) : '—' ?></td>
<td class="col-comp"><?= $r['composite'] !== null ? number_format($r['composite'], 2) : '—' ?></td>
<td><?= number_format($r['assess_index'], 2) ?></td>
<?php elseif ($hasOldData): ?>
<td class="col-old"><?= $r['old_index'] !== null ? number_format($r['old_index'], 2) : '—' ?></td>
<td class="col-new"><?= $r['new_index'] !== null ? number_format($r['new_index'], 2) : '—' ?></td>
<td class="col-comp"><?= $r['composite'] !== null ? number_format($r['composite'], 2) : '—' ?></td>
<?php elseif ($showIndex): ?>
<td><?= $r['index_value'] !== null ? number_format($r['index_value'], 2) : '—' ?></td>
<td><?= number_format($r['assess_index'], 2) ?></td>
<?php endif; ?>
<td class="fw-semibold"><?= $r['factor'] !== null ? number_format($r['factor'], 4) : '—' ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="small text-muted mt-2">
Yellow = provisional | Amber italic = extrapolated | Cyan italic = back-interpolated.
<?php if ($hasOldData): ?>
<span class="col-old px-1">▮</span> 2004‑05 raw
<span class="col-new px-1">▮</span> 2011‑12 raw
<span class="col-comp px-1">▮</span> Composite (<?= h($overlapPrefer) ?> scale)
<?php if ($overlapPrefer === '2004-05'): ?>
= New Index × MF. MF = Old ÷ New Transition Index (shown below composite on first row).
<?php else: ?>
= New Index directly; old-only periods show raw value. MF shown below composite on first row.
<?php endif; ?>
Index Factor = Assessment Index ÷ Composite.
Overlap preference: <strong><?= h($overlapPrefer) ?></strong>.
<?php else: ?>
Index Factor = Assessment Index ÷ Period Index.
<?php endif; ?>
<?= $frequency !== 'monthly' ? 'Period indices are averaged across constituent months.' : '' ?>
</div>
<?php endif; ?>
</div></div>
<?php else: ?>
<div class="card"><div class="card-body text-center text-muted py-5">
<h5>Select commodities and an assessment date, then click Calculate.</h5>
<p>Index Factor = Assessment Date Index ÷ Index of the Period</p>
</div></div>
<?php endif; ?>
</div>
</div>
<script>
const selected = {};
const searchInput = document.getElementById('commSearch');
const resultsDiv = document.getElementById('searchResults');
const codesInput = document.getElementById('codesInput');
const selectedDiv = document.getElementById('selectedComms');
let searchTimeout;
searchInput.addEventListener('input', function() {
clearTimeout(searchTimeout);
const q = this.value.trim();
if (q.length < 1) { resultsDiv.style.display = 'none'; return; }
searchTimeout = setTimeout(() => {
fetch('<?= base_url("api_search.php") ?>?q=' + encodeURIComponent(q))
.then(r => r.json())
.then(data => {
resultsDiv.innerHTML = '';
if (!data.length) {
resultsDiv.innerHTML = '<div class="list-group-item text-muted">No results</div>';
}
data.forEach(c => {
if (selected[c.comm_code]) return;
const item = document.createElement('a');
item.href = '#';
item.className = 'list-group-item list-group-item-action py-1 small';
item.innerHTML = '<code>' + c.comm_code + '</code> ' + c.comm_name + ' <span class="text-muted">(L' + c.level + ')</span>';
item.onclick = (e) => { e.preventDefault(); addComm(c); resultsDiv.style.display = 'none'; searchInput.value = ''; };
resultsDiv.appendChild(item);
});
resultsDiv.style.display = 'block';
});
}, 250);
});
document.addEventListener('click', (e) => {
if (!resultsDiv.contains(e.target) && e.target !== searchInput) resultsDiv.style.display = 'none';
});
function addComm(c) { selected[c.comm_code] = c; renderSelected(); }
function removeComm(code) { delete selected[code]; renderSelected(); }
function renderSelected() {
codesInput.value = Object.keys(selected).join(',');
selectedDiv.innerHTML = '';
Object.values(selected).forEach(c => {
const tag = document.createElement('span');
tag.className = 'badge bg-primary me-1 mb-1';
tag.style.cursor = 'pointer';
tag.title = 'Click to remove';
tag.innerHTML = c.comm_code + ' - ' + c.comm_name + ' ×';
tag.onclick = () => removeComm(c.comm_code);
selectedDiv.appendChild(tag);
});
}
const freqSelect = document.getElementById('freqSelect');
const seriesGroup = document.getElementById('seriesGroup');
const seriesStartSel = document.querySelector('select[name="series_start"]');
const assessPeriodCol = document.getElementById('assessPeriodCol');
const assessPeriodLbl = document.getElementById('assessPeriodLabel');
const assessMonthSel = document.getElementById('assessMonthSel');
const assessPeriodSel = document.getElementById('assessPeriodSel');
const assessYearSel = document.getElementById('assessYearSel');
const periodOptions = {
quarterly: [{v:1,t:'Q1'},{v:2,t:'Q2'},{v:3,t:'Q3'},{v:4,t:'Q4'}],
'half-yearly':[{v:1,t:'H1'},{v:2,t:'H2'}],
};
const maxYm = <?= json_encode(($range && $range['max_ym'] !== null) ? (int)$range['max_ym'] : (int)date('Ym')) ?>;
function getDefaultFyYear(freq, periodNum, seriesStart) {
let count, startOff;
if (freq === 'quarterly') { count = 3; startOff = (periodNum - 1) * 3; }
else if (freq === 'half-yearly') { count = 6; startOff = (periodNum - 1) * 6; }
else { count = 12; startOff = 0; }
const lastOff = startOff + count - 1;
const lastM = ((seriesStart - 1 + lastOff) % 12) + 1;
const base = Math.floor((maxYm - lastM) / 100);
return lastM >= seriesStart ? base : base - 1;
}
function updateDefaultYear() {
const freq = freqSelect.value;
const seriesStart = parseInt(seriesStartSel.value) || 4;
const periodNum = (freq === 'annually') ? 1 : (parseInt(assessPeriodSel.value) || 1);
const yr = getDefaultFyYear(freq, periodNum, seriesStart);
if (assessYearSel.querySelector(`option[value="${yr}"]`)) {
assessYearSel.value = String(yr);
}
}
function applyFrequency(freq, restoreVal, skipYearUpdate) {
const isMonthly = freq === 'monthly';
const isAnnually = freq === 'annually';
seriesGroup.style.display = isMonthly ? 'none' : 'block';
if (isMonthly) {
assessPeriodLbl.textContent = 'Assessment Month';
assessMonthSel.style.display = '';
assessMonthSel.disabled = false;
assessPeriodSel.style.display = 'none';
assessPeriodSel.disabled = true;
assessPeriodCol.style.display = '';
} else if (isAnnually) {
assessPeriodCol.style.display = 'none';
assessMonthSel.disabled = true;
assessPeriodSel.disabled = true;
if (!skipYearUpdate) updateDefaultYear();
} else {
assessPeriodLbl.textContent = freq === 'quarterly' ? 'Assessment Quarter' : 'Assessment Half';
assessPeriodCol.style.display = '';
assessMonthSel.style.display = 'none';
assessMonthSel.disabled = true;
assessPeriodSel.style.display = '';
assessPeriodSel.disabled = false;
assessPeriodSel.innerHTML = periodOptions[freq]
.map(o => `<option value="${o.v}"${o.v == restoreVal ? ' selected' : ''}>${o.t}</option>`)
.join('');
if (!skipYearUpdate) updateDefaultYear();
}
updateExtrapToggle();
}
freqSelect.addEventListener('change', function() { applyFrequency(this.value, 1, false); });
function getAssessmentLastYm() {
const freq = freqSelect.value;
const assessYr = parseInt(assessYearSel.value) || 0;
const seriesStart = parseInt(seriesStartSel.value) || 4;
if (freq === 'monthly') {
const mo = parseInt(assessMonthSel.value) || 0;
return assessYr * 100 + mo;
}
let lastOff;
if (freq === 'quarterly') {
const qNum = parseInt(assessPeriodSel.value) || 1;
lastOff = (qNum - 1) * 3 + 2;
} else if (freq === 'half-yearly') {
const hNum = parseInt(assessPeriodSel.value) || 1;
lastOff = (hNum - 1) * 6 + 5;
} else {
lastOff = 11;
}
const lastM = ((seriesStart - 1 + lastOff) % 12) + 1;
const lastY = lastM >= seriesStart ? assessYr : assessYr + 1;
return lastY * 100 + lastM;
}
let _extrapPrevAllowed = false;
function updateExtrapToggle() {
const allowed = getAssessmentLastYm() > maxYm;
const toggle = document.getElementById('showExtrapToggle');
const wrap = document.getElementById('extrapToggleWrap');
const opts = document.getElementById('extrapOptions');
toggle.disabled = !allowed;
wrap.style.opacity = allowed ? '1' : '0.4';
wrap.title = allowed ? '' : 'Select an assessment period beyond the latest available data to enable extrapolation';
if (!allowed) {
toggle.checked = false;
opts.style.display = 'none';
toggle._userUnchecked = false;
} else if (!_extrapPrevAllowed || !toggle._userUnchecked) {
toggle.checked = true;
opts.style.display = '';
}
_extrapPrevAllowed = allowed;
}
document.getElementById('showExtrapToggle').addEventListener('change', function() {
this._userUnchecked = !this.checked;
document.getElementById('extrapOptions').style.display = this.checked ? '' : 'none';
});
(function () {
const interpToggle = document.getElementById('showInterpToggle');
const yearSel = document.getElementById('assessYearSel');
const minYearDb = <?= (int)$minYear ?>;
const maxYearDb = <?= (int)$maxYear ?>;
const curYear = parseInt(yearSel.value) || maxYearDb;
function rebuildYearOptions(withInterp) {
const floor = withInterp ? 1920 : minYearDb;
const selVal = parseInt(yearSel.value) || maxYearDb;
yearSel.innerHTML = '';
for (let y = maxYearDb; y >= floor; y--) {
const o = document.createElement('option');
o.value = y; o.textContent = y;
if (y === selVal) o.selected = true;
yearSel.appendChild(o);
}
}
interpToggle.addEventListener('change', function() {
rebuildYearOptions(this.checked);
updateExtrapToggle();
});
})();
assessPeriodSel.addEventListener('change', function() { updateDefaultYear(); updateExtrapToggle(); });
assessMonthSel.addEventListener('change', updateExtrapToggle);
assessYearSel.addEventListener('change', updateExtrapToggle);
seriesStartSel.addEventListener('change', function() { updateDefaultYear(); updateExtrapToggle(); });
const isPostRender = <?= json_encode($_SERVER['REQUEST_METHOD'] === 'POST') ?>;
applyFrequency(freqSelect.value, <?= json_encode($assessPeriodNum ?? 1) ?>, isPostRender);
// Restore pre-selected commodities on POST re-render
<?php if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($codes)): ?>
<?php foreach ($codes as $c): ?>
<?php if (isset($commNames[$c])): ?>
selected[<?= json_encode($c) ?>] = {
comm_code: <?= json_encode($c) ?>,
comm_name: <?= json_encode($commNames[$c]['comm_name'] ?? $c) ?>,
level: <?= (int)($commNames[$c]['level'] ?? 6) ?>
};
<?php endif; ?>
<?php endforeach; ?>
renderSelected();
<?php endif; ?>
// Collapsible commodity rows
document.querySelectorAll('tr.comm-header').forEach(headerRow => {
headerRow.style.cursor = 'pointer';
headerRow.addEventListener('click', function () {
const code = this.dataset.comm;
const details = document.querySelectorAll(`tr.comm-detail[data-comm="${code}"]`);
const arrow = this.querySelector('.comm-arrow');
const collapsed = details.length && details[0].style.display === 'none';
details.forEach(r => r.style.display = collapsed ? '' : 'none');
if (arrow) arrow.textContent = collapsed ? '▼' : '►';
});
});
</script>
<?php require __DIR__ . '/includes/footer.php'; ?>