235 lines
7.5 KiB
Markdown
235 lines
7.5 KiB
Markdown
# 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.
|
|
|