7.5 KiB
IPI Index Calculation Document
Purpose
This document explains what happens when runCompleteCalculation(year, month) is executed in app/services/ipi_calculation_service.js, including formulas, data flow, and output tables.
Runtime Entry Flow (Actual Start Point)
Calculation does not start by directly calling runCompleteCalculation from server.js.
It starts through scheduler wiring:
server.jscreates scheduler:const scheduler = new AutomatedSchedulerService();
server.jsstarts jobs:scheduler.start();(IPI calculation schedule)scheduler.startRemainderEmail();(email reminders)
start()inapp/services/scheduler.service.jsregisters cron:cron.schedule('0 2 * * *', ...)=> runs daily at 2:00 AM
- Cron calls
runScheduledTask() runScheduledTask()flow:- fetch latest unprocessed quarter (
ipi_calculated = 0) - check current date >= quarter
end_date - run auto submission fill (
executeSurveyAutoSubmit) - run IPI quarter calculation (
executeCalculateQuarter) - set
ipi_calculated = 1
- fetch latest unprocessed quarter (
executeCalculateQuarter(year, quarter)loops through quarter months and calls:runCompleteCalculation(year, month)for each month
Manual trigger path also exists:
POST /api/admin/trigger-scheduler->scheduler.manualTrigger()->runScheduledTask()
Calculation Pipeline Entry
Function: runCompleteCalculation(year, month)
Execution order:
aggregateMonthlyProduction(year, month)calculateItemLevelIndices(year, month)calculateISIC4DigitIndices(year, month)calculateISIC3DigitIndices(year, month)calculateISIC2DigitIndices(year, month)calculateManufacturingIPI(year, month)
If any step throws an error, the pipeline stops and the error is propagated.
Data Prerequisite (Base Year)
Before running the monthly pipeline, base-year data must exist in base_year_production (typically by running calculateBaseYearProduction(2022)).
Base-year average production per product is calculated as:
BaseAvg_i = (Jan_i + Feb_i + Mar_i + Apr_i + May_i + Jun_i + Jul_i + Aug_i + Sep_i + Oct_i + Nov_i + Dec_i) / 12
Stored in:
base_year_production.avg_by_production
Only approved submissions and active submission products are used.
Step-by-Step Flow in runCompleteCalculation
Step 1: Aggregate Monthly Production
Function: aggregateMonthlyProduction(year, month)
What it does:
- Maps the target month to quarter and period field:
- Month 1 ->
Q1/current_quantity_period_one - Month 2 ->
Q1/current_quantity_period_two - ...
- Month 12 ->
Q4/current_quantity_period_three
- Month 1 ->
- Sums product quantity across all approved submissions for that period.
- Writes one record per product to
monthly_production.
Formula:
MonthlyProduction_i(y,m) = SUM( submission_products.period_quantity )
Output table:
monthly_production(product_id, year, month, production_quantity, ...)
Step 2: Calculate Item Level Indices
Function: calculateItemLevelIndices(year, month)
What it does:
- Joins current month production with base-year average production by product.
- Excludes products where base average is zero or null (
avg_by_production > 0). - Stores item-level relative and item index.
Formulas:
Ri = CurrentProduction_i / BaseAvg_i
Ii = Ri * 100
Expanded:
ItemIndex_i(y,m) = (MonthlyProduction_i(y,m) / BaseAvg_i) * 100
Output table:
item_level_indices(product_id, current_production, base_year_avg_production, production_relative, item_index, ...)
Interpretation:
100means equal to base-year monthly average.>100means above base-year average.<100means below base-year average.
Step 3: Calculate ISIC 4-Digit Indices
Function: calculateISIC4DigitIndices(year, month)
What it does:
- Assigns each product to ISIC group using
LEFT(establishments.isic_code, 4). - Uses item-level index values belonging to that 4-digit group.
- Stores count and group index.
Implemented formulas:
total_weight_g = COUNT(DISTINCT product_id)
weighted_index_sum_g = SUM(ItemIndex_i)
ISIC4Index_g = AVG(ItemIndex_i)
Note:
- In this implementation, "weight" is the number of distinct products (equal weight per product), not an external official weight table.
Output table:
isic_4digit_indices(isic_4digit_code, total_weight, weighted_index_sum, isic_4digit_index, ...)
Step 4: Calculate ISIC 3-Digit Indices
Function: calculateISIC3DigitIndices(year, month)
What it does:
- Rolls up 4-digit results to 3-digit groups via
LEFT(isic_4digit_code, 3). - Uses weighted average based on
total_weightfrom 4-digit level.
Formulas:
W_h = SUM(total_weight_g) for all 4-digit groups g under 3-digit group h
WeightedSum_h = SUM(total_weight_g * ISIC4Index_g)
ISIC3Index_h = WeightedSum_h / W_h
Output table:
isic_3digit_indices(isic_3digit_code, total_weight, weighted_index_sum, isic_3digit_index, ...)
Step 5: Calculate ISIC 2-Digit Indices
Function: calculateISIC2DigitIndices(year, month)
What it does:
- Rolls up 3-digit results to 2-digit groups via
LEFT(isic_3digit_code, 2). - Uses weighted average based on
total_weightfrom 3-digit level.
Formulas:
W_k = SUM(total_weight_h) for all 3-digit groups h under 2-digit group k
WeightedSum_k = SUM(total_weight_h * ISIC3Index_h)
ISIC2Index_k = WeightedSum_k / W_k
Output table:
isic_2digit_indices(isic_2digit_code, total_weight, weighted_index_sum, isic_2digit_index, ...)
Step 6: Calculate Manufacturing IPI (Headline)
Function: calculateManufacturingIPI(year, month)
What it does:
- Computes overall manufacturing index from all 2-digit ISIC groups.
- Then computes Month-on-Month (MoM) and Year-on-Year (YoY) percentage changes.
- Writes final result in
manufacturing_ipi.
Headline formulas:
TotalWeight = SUM(total_weight_k)
TotalWeightedSum = SUM(total_weight_k * ISIC2Index_k)
ManufacturingIPI(y,m) = TotalWeightedSum / TotalWeight
Change formulas:
MoM(%) = ((IPI(y,m) - IPI(prev_month)) / IPI(prev_month)) * 100
YoY(%) = ((IPI(y,m) - IPI(y-1,m)) / IPI(y-1,m)) * 100
Reference date:
- First day of month:
YYYY-MM-01
Output table:
manufacturing_ipi(year, month, manufacturing_index, mom_change, yoy_change, total_weight, weighted_index_sum, status, generated_on, ...)
Logging and Transactions
Each calculation step:
- Opens its own DB transaction.
- Creates a start log entry in
calculation_log. - Updates log as Completed/Failed with record count.
- Commits on success and rolls back on error.
This means:
- A step is atomic by itself.
- The full pipeline is not one global transaction across all six steps.
End-to-End Formula Chain (Compact View)
BaseAvg_i = Avg monthly production in base year (2022)ItemIndex_i = (CurrentProduction_i / BaseAvg_i) * 100ISIC4Index = Avg(ItemIndex_i within ISIC4)ISIC3Index = Weighted avg(ISIC4Index by ISIC4 total_weight)ISIC2Index = Weighted avg(ISIC3Index by ISIC3 total_weight)ManufacturingIPI = Weighted avg(ISIC2Index by ISIC2 total_weight)MoM,YoYfrom prior stored headline indices.
Client Explanation Script (Short)
- We first aggregate monthly production quantity by product from approved submissions.
- Each product is compared against its base-year monthly average (2022), giving an item index where 100 equals base-year average output.
- Product indices are grouped by ISIC 4-digit, then rolled up to ISIC 3-digit and 2-digit using weighted averages.
- The final manufacturing IPI is the weighted average of all ISIC 2-digit indices.
- We then compute MoM and YoY growth rates using previously stored headline values.