fcsc_ipi_backend/ipi-index-calculation-document.md

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:

  1. server.js creates scheduler:
    • const scheduler = new AutomatedSchedulerService();
  2. server.js starts jobs:
    • scheduler.start(); (IPI calculation schedule)
    • scheduler.startRemainderEmail(); (email reminders)
  3. start() in app/services/scheduler.service.js registers cron:
    • cron.schedule('0 2 * * *', ...) => runs daily at 2:00 AM
  4. Cron calls runScheduledTask()
  5. 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
  6. 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:

  1. aggregateMonthlyProduction(year, month)
  2. calculateItemLevelIndices(year, month)
  3. calculateISIC4DigitIndices(year, month)
  4. calculateISIC3DigitIndices(year, month)
  5. calculateISIC2DigitIndices(year, month)
  6. 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
  • 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:

  • 100 means equal to base-year monthly average.
  • >100 means above base-year average.
  • <100 means 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_weight from 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_weight from 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)

  1. BaseAvg_i = Avg monthly production in base year (2022)
  2. ItemIndex_i = (CurrentProduction_i / BaseAvg_i) * 100
  3. ISIC4Index = Avg(ItemIndex_i within ISIC4)
  4. ISIC3Index = Weighted avg(ISIC4Index by ISIC4 total_weight)
  5. ISIC2Index = Weighted avg(ISIC3Index by ISIC3 total_weight)
  6. ManufacturingIPI = Weighted avg(ISIC2Index by ISIC2 total_weight)
  7. MoM, YoY from 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.