# Furnace Group Strategy Documentation ## Overview The technology strategy system for furnace groups operates through a two-function architecture that separates **economic analysis** from **strategic decision-making**: ### Economic Analysis Engine: `optimal_technology_name()` The `optimal_technology_name` function in the `FurnaceGroup` class is a critical economic analysis component that evaluates the financial viability of technology transitions in steel production facilities. It is a pure analytical function that returns economic metrics without making decisions or modifying any state. It performs a comprehensive Net Present Value (NPV) analysis by: - Calculating the Cost of Stranded Assets (COSA) for abandoning current technology - Evaluating NPV for each allowed technology transition - Accounting for subsidies, carbon costs, and operational differences - Comparing greenfield (new) vs brownfield (retrofit) installations ### Strategic Decision Orchestrator: `evaluate_furnace_group_strategy()` The `evaluate_furnace_group_strategy` function in the `Plant` class is the strategic decision-maker that uses NPV analysis to make investment decisions, thereby modifying the state of furnace groups. It: - Calls `optimal_technology_name()` to obtain economic analysis - Applies business logic (affordability checks, capacity limits, risk assessment) - Makes strategic decisions based on economic and operational constraints - Generates actionable commands (renovate, switch technologies, or close facilities) - Updates plant balance sheets to reflect investment costs ## Architecture: How the Functions Interact ``` evaluate_furnace_group_strategy (Plant) │ ├─> Stage 1-4: Pre-flight checks │ ├─> Check plant financial health │ ├─> Check furnace group status │ ├─> Evaluate forced closure │ └─> Filter allowed transitions │ ├─> Stage 5: Call optimal_technology_name (FurnaceGroup) │ │ │ │ [Economic Analysis - No Decisions Made] │ │ ├─> Calculate COSA │ │ ├─> Evaluate NPV for each allowed technology │ │ │ ├─> Brownfield (renovation): reduced capex │ │ │ └─> Greenfield (new tech): full capex + COSA penalty │ │ └─> Return: NPV dict, CAPEX dict, COSA, BOM dict │ │ │ └─< Returns economic metrics │ ├─> Stage 6-8: Analyze results │ ├─> Check if any option is profitable │ ├─> Identify optimal technology │ └─> Select technology (weighted random or deterministic) │ ├─> Stage 9-10: Execute decision path │ ├─> Path A: Renovation (same tech, lifetime expired) │ └─> Path B: Technology switch (different tech) │ ├─> Stage 11-12: Final checks │ ├─> Probabilistic adoption filter │ └─> Capacity limit enforcement │ └─> Stage 13: Return command ├─> RenovateFurnaceGroup ├─> ChangeFurnaceGroupTechnology ├─> CloseFurnaceGroup └─> None (no action) ``` # Part 1: Economic Analysis ## Overview **Function**: `FurnaceGroup.optimal_technology_name` **Purpose**: Pure NPV calculation and economic evaluation - determines the optimal technology by comparing economic returns across all allowed technology transitions. **Key Characteristics**: - **No decisions made** - only returns economic metrics - **No side effects** - does not modify any state - **Comprehensive evaluation** - calculates NPV for all allowed transitions - **COSA-adjusted** - accounts for stranded asset costs when switching **High-Level Workflow**: ``` Start │ ├─> Stage 1: Initialize and import dependencies │ ├─> Stage 2: Prepare Operating Expenses List and Calculate Operating Subsidies │ ├─> Stage 3: Calculate Carbon Costs for Current Technology and Combine with Subsidised OPEX │ ├─> Stage 4: Calculate Cost of Stranded Assets (COSA) │ ├─> Stage 5: Check for allowed technology transitions │ └─> If no transitions allowed: Return empty results │ ├─> Stage 6-9: For each allowed technology: │ ├─> Check if technology has capex data │ ├─> Check special requirements (e.g., BOF needs smelter) │ ├─> Branch A (Current Tech): Brownfield evaluation │ │ ├─> Apply brownfield capex reduction │ │ ├─> Use existing BOM and emissions │ │ └─> Calculate carbon costs │ ├─> Branch B (New Tech): Greenfield evaluation │ │ ├─> Fetch average BOM │ │ ├─> Match business cases │ │ ├─> Calculate new emissions │ │ └─> Calculate carbon costs │ ├─> Stage 8: Calculate NPV if valid BOM exists │ │ ├─> Apply subsidies (capex and opex) │ │ ├─> Calculate full NPV │ │ └─> Store results │ └─> Stage 9: Adjust NPV for COSA (if switching) │ └─> Stage 10: Return results (NPVs, capex dict, COSA, BOMs) ``` ## Detailed Stage Breakdown ### Stage 1: Initialize and Import Dependencies - **Purpose**: Set up logging and import required calculation functions - **Debug prefix**: `[OPTIMAL TECHNOLOGY]:` - **Key actions**: - Log current furnace group state - Import cost and emissions calculation functions - Initialize return dictionaries ### Stage 2: Prepare Operating Expenses List and Calculate Operating Subsidies - **Purpose**: Apply OPEX subsidies to the current OPEX value - **Output**: List of subsidised OPEX values by year for the period of remaining lifetime ### Stage 3: Calculate Carbon Costs for Current Technology and Combine with Subsidised OPEX - **Purpose**: Determine carbon cost series for existing operations - **Key decision**: Use current emissions and chosen boundary - **Inputs**: Current emissions, carbon price series, emission boundary - **Output**: List of carbon costs over facility lifetime combined with list of subsidised OPEX values ### Stage 4: Calculate Cost of Stranded Assets (COSA) - **Purpose**: Determine the cost of abandoning current technology - **Key calculation**: NPV of remaining debt + operating costs - **Decision**: COSA = max(calculated_COSA, remaining_debt) - **Rationale**: Must at minimum pay off remaining debt ### Stage 5: Check for Allowed Technology Transitions - **Decision point**: Are transitions defined for current technology? - If no: Return empty results (no switch possible) - If yes: Continue to evaluation - **Key check**: `allowed_furnace_transitions` dictionary ### Stage 6: Evaluate Each Allowed Technology - **For each technology in allowed transitions**: - **Decision 1**: Does technology have capex data? - If no: Skip this technology - **Decision 2**: Special requirements check - BOF requires smelter furnace - If not met: Skip this technology ### Stage 7: Branch Based on Technology Type #### Branch A: Current Technology (Brownfield) - **Characteristics**: - Retrofit of existing facility - Reduced capex (typically 20% of greenfield) - Uses existing BOM and utilization rate - Uses existing emissions profile - **Key adjustments**: - Apply brownfield_capex_share multiplier - Validate existing BOM structure #### Branch B: New Technology (Greenfield) - **Characteristics**: - Complete replacement with new technology - Full capex cost - Requires fetching average BOM - Must calculate new emissions profile - **Process**: 1. Fetch average BOM for technology 2. Match business cases with BOM 3. Calculate emissions for new technology 4. Determine carbon costs based on new emissions ### Stage 8: Calculate NPV - **Prerequisite**: Valid BOM must exist - **Process**: 1. Store BOM for technology 2. Get market price for product type 3. Calculate operating subsidies 4. Apply capex subsidies 5. Calculate full NPV including: - Capex (after subsidies) - Operating costs and revenues - Carbon costs - Debt service - **Key parameters**: Capacity, utilization, lifetime, financing costs ### Stage 9: Adjust NPV for COSA - **Decision**: Is this a technology switch? - If yes (tech != current): Subtract COSA from NPV - If no (same tech): No COSA adjustment - **Rationale**: Switching technologies incurs stranded asset cost ### Stage 10: Return Results - **Outputs**: 1. `npv_dict`: NPV values for each technology (COSA-adjusted) 2. `npv_capex_dict`: Effective capex after subsidies 3. `cosa`: Cost of Stranded Assets value 4. `bom_dict`: Bill of Materials for each technology ## Key Dependencies and Interactions ### External Functions Called - `calculate_emissions_cost_series`: Converts emissions to monetary costs - `calculate_opex_subsidies`: Determines operating subsidy values - `stranding_asset_cost`: Calculates COSA - `get_bom_from_avg_boms`: Retrieves average BOMs for technologies - `materiall_bill_business_case_match`: Matches feedstocks to BOMs - `calculate_emissions`: Computes emission profiles - `calculate_capex_with_subsidies`: Applies capital subsidies - `calculate_variable_opex`: Computes variable operating costs - `calculate_npv_full`: Performs complete NPV calculation ### Data Dependencies - `self.bill_of_materials`: Current technology's BOM - `self.emissions`: Current emission profile - `self.lifetime`: Facility lifetime information - `self.debt_repayment_per_year`: Debt service schedule - `self.utilization_rate`: Current capacity utilization - `self.production`: Current production volume ## Input Parameters ### Required Market Data - `market_price_series`: Product prices by type (steel, iron, etc.) - `carbon_cost_series`: Carbon prices over time - `cost_of_debt`: Interest rate on debt - `cost_of_equity`: Required return on equity ### Technology Configuration - `capex_dict`: Capital costs by technology - `capex_renovation_share`: Retrofit cost multipliers (brownfield) - `technology_fopex_dict`: Fixed operating costs - `allowed_furnace_transitions`: Valid technology switches - `dynamic_business_cases`: Feedstock configurations ### Subsidy Information - `tech_capex_subsidies`: Capital subsidies by technology - `tech_opex_subsidies`: Operating subsidies by technology - `tech_debt_subsidies`: Debt subsidies by technology ## Common Issues and Debugging Tips ### Issue 1: No Transitions Found - **Symptom**: Function returns empty NPV dictionary - **Check**: `allowed_furnace_transitions` contains current technology - **Debug**: Look for `"NO TRANSITIONS ALLOWED"` in logs ### Issue 2: Technology Skipped - **Symptom**: Expected technology not in results - **Possible causes**: - Missing capex data - BOF without smelter - Failed BOM retrieval - **Debug**: Look for `"SKIPPING"` messages in logs ### Issue 3: Invalid BOM Structure - **Symptom**: Technology evaluation fails - **Check**: BOM contains both "materials" and "energy" keys - **Debug**: Look for `"Invalid or missing BOM"` warnings ### Issue 4: Negative NPV After COSA - **Symptom**: All technologies show negative NPV - **Explanation**: COSA exceeds benefits of switching - **Debug**: Check COSA calculation and remaining debt # Part 2: Strategic Decision-Making ## Overview **Function**: `Plant.evaluate_furnace_group_strategy` **Purpose**: Strategic decision orchestrator that uses NPV analysis to make investment decisions **Key Characteristics**: - **Makes decisions** - determines which action to take - **Has side effects** - updates plant balance sheet - **Business logic** - applies affordability checks, capacity limits, probabilistic adoption - **Command generation** - returns executable commands for the simulation **Decision Workflow** The function follows a 13-stage workflow from pre-flight checks through command generation: ``` Stage 1-4: Pre-Flight Checks ├─> Plant financial health ├─> Furnace group status ├─> Forced closure evaluation └─> Technology transition filtering Stage 5: Economic Analysis └─> Call optimal_technology_name() → Get NPV analysis Stage 6-8: Results Analysis ├─> Check profitability (any NPV > 0?) ├─> Identify optimal technology └─> Technology selection (weighted random or deterministic) Stage 9-10: Decision Execution ├─> Renovation path (if best tech = current tech) └─> Technology switch path (if best tech ≠ current tech) Stage 11-13: Final Checks & Command Generation ├─> Probabilistic adoption filter ├─> Capacity limit enforcement └─> Generate command (Renovate/Switch/Close/None) ``` ## Detailed Stage Breakdown ### Stage 1: Check Plant Financial Health **Decision**: Can the plant afford to invest? - **Check**: `plant.balance >= 0` - **If negative**: Return `None` (no action possible) - **Rationale**: Negative balance = no capital available for investment ### Stage 2: Check Furnace Group Status **Decision**: Is the furnace group eligible for strategy evaluation? - **Check**: Status is not "operating pre-retirement" - **If pre-retirement**: Return `None` (already scheduled to close) - **Rationale**: Don't invest in facilities about to close ### Stage 3: Check for Forced Closure **Decision**: Have accumulated losses exceeded write-off threshold? - **Threshold calculation**: `CAPEX × capacity` - **Check**: `furnace_group.historic_balance < -threshold` - **If exceeded**: Return `CloseFurnaceGroup` command - **Rationale**: Losses too great - better to close than continue operating **Example**: - CAPEX: $800/tonne - Capacity: 5,000,000 tonnes (5 Mt) - Threshold: -$4,000,000,000 - Historic balance: -$4,500,000,000 → **Close** ### Stage 4: Filter Allowed Technology Transitions **Purpose**: Narrow down technology options based on what's allowed in the current year - **Process**: Intersect `allowed_techs[current_year]` with `allowed_furnace_transitions[current_tech]` - **Example**: - Current tech: BF-BOF - All possible transitions: [BF-BOF, EAF, DRI-EAF, H2-DRI-EAF] - Allowed in 2030: [BF-BOF, EAF, DRI-EAF] (H2-DRI-EAF not yet available) - Filtered transitions: [BF-BOF, EAF, DRI-EAF] ### Stage 5: Calculate NPV for All Technology Options **Key Action**: Call `furnace_group.optimal_technology_name()` to get economic analysis **Inputs passed to analysis engine**: - Market price forecasts - Technology costs (CAPEX, OPEX, financing) - Bill of materials function - Carbon cost projections - Subsidy configurations **Outputs received**: - `tech_npv_dict`: NPV for each technology (already COSA-adjusted) - `npv_capex_dict`: Subsidized CAPEX for each technology - `cosa`: Cost of stranded assets - `bom_dict`: Bills of materials for each technology ### Stage 6: Check if Any Technology Option is Profitable **Decision**: Is investing worthwhile? - **Check**: `max(tech_npv_dict.values()) > 0` - **If all NPVs ≤ 0**: Return `None` (no profitable option exists) - **Rationale**: Don't invest if all options lose money ### Stage 7: Identify Optimal Technology **Process**: Find technology with highest NPV - **Calculation**: `optimal_tech = max(tech_npv_dict, key=tech_npv_dict.get)` - **Check**: Is current technology already optimal? - `is_current_best = (current_tech == optimal_tech)` ### Stage 8: Technology Selection **Decision Mode**: Weighted random (realistic) or deterministic (pure optimization) #### If Current Tech is NOT Optimal: **Weighted Random Selection** (when `probabilistic_agents=True`): - **Purpose**: Model real-world decision uncertainty and strategic preferences - **Process**: 1. Filter out invalid NPVs (NaN, infinite) 2. Calculate weights: `weight = max(NPV, 0)` (negative NPVs get zero weight) 3. Randomly select technology with probability ∝ NPV - **Example**: - BF-BOF: NPV = $5M → weight = 5M → 37% selection probability - EAF: NPV = $8.5M → weight = 8.5M → 63% selection probability - DRI-EAF: NPV = -$1M → weight = 0 → 0% selection probability #### If Current Tech IS Optimal: - **Selection**: Keep current technology ### Stage 9: Handle Renovation Scenario **Condition**: Best technology = current technology AND lifetime expired **Decision Path**: 1. **Calculate renovation cost**: - CAPEX = subsidized renovation CAPEX from NPV analysis - Cost = `CAPEX × capacity × equity_share` 2. **Affordability check**: - If `renovation_cost > plant.balance` → Return `CloseFurnaceGroup` - Rationale: Can't afford to renovate, must close 3. **Execute renovation**: - Deduct cost from plant balance: `plant.balance -= renovation_cost` - Return `RenovateFurnaceGroup` command **Example Calculation**: - Subsidized renovation CAPEX: $160/tonne - Capacity: 5 Mt - Equity share: 30% - Renovation cost: $160 × 5,000,000 × 0.30 = $240,000,000 - Plant balance: $300,000,000 → **Affordable, renovate** ### Stage 10: Handle Technology Switch Scenario **Condition**: Best technology ≠ current technology **Process**: 1. **Calculate switching cost**: - CAPEX = subsidized greenfield CAPEX from NPV analysis - Cost = `CAPEX × capacity × equity_share` 2. **Affordability check**: - If `switch_cost > plant.balance` → Return `None` - Rationale: Can't afford to switch 3. **Continue to probabilistic adoption** (Stage 11) **Example Calculation**: - Subsidized greenfield CAPEX: $650/tonne - Capacity: 5 Mt - Equity share: 30% - Switch cost: $650 × 5,000,000 × 0.30 = $975,000,000 - Plant balance: $1,200,000,000 → **Affordable, proceed** ### Stage 11: Probabilistic Adoption Decision **Purpose**: Model real-world hesitation in technology adoption (financing risk, permit delays, market uncertainty) **Formula**: ``` acceptance_probability = exp(-switch_cost / NPV) ``` **Interpretation**: - Higher cost relative to benefit → Lower probability - Cost << NPV → High probability (close to 100%) - Cost ≈ NPV → Moderate probability (≈37%) - Cost >> NPV → Low probability (close to 0%) **Example**: - Switch cost: $975M - NPV: $8.5M - Ratio: 975 / 8.5 = 114.7 - Acceptance probability: exp(-114.7) ≈ 0% (very unlikely) **Alternative Example** (more favorable): - Switch cost: $100M - NPV: $200M - Ratio: 100 / 200 = 0.5 - Acceptance probability: exp(-0.5) ≈ 61% **Decision Process**: 1. Calculate acceptance probability 2. Draw random number (0-1) 3. If `random_draw < acceptance_probability` AND furnace has no CCS/CCU → Proceed to Stage 12 4. Otherwise → Return `None` (probabilistic rejection) **Special Case**: Furnaces with CCS/CCU equipment are never switched (representing significant sunk investment) ### Stage 12: Check Capacity Limits **Purpose**: Enforce annual capacity expansion limits (supply chain constraints) **Context**: - Total annual capacity is limited (default: 100 Mt for both steel and iron) - Limit is split between: - **New plants**: Greenfield facilities (handled by Geospatial Model) - **Expansions & switches**: Capacity added to existing plants (handled by PAM) **Calculation**: ``` total_installed_capacity = installed_capacity_in_year(product) # All new capacity this year new_plant_capacity = new_plant_capacity_in_year(product) # Greenfield only expansion_and_switch_capacity = total - new_plant_capacity # Existing plants if expansion_and_switch_capacity + furnace_capacity > expansion_limit: BLOCKED ``` **Example**: - Product: Steel - Total expansion limit for steel: 100 Mt/year - New plant share: 40% → New plant limit: 40 Mt, Expansion/switch limit: 60 Mt - Current new expansion/switch capacity already installed this year: 55 Mt - Furnace capacity to switch: 8 Mt - Total after switch: 55 + 8 = 63 Mt > 60 Mt → **BLOCKED** **If within limits**: Proceed to Stage 13 ### Stage 13: Execute Technology Switch **Final Actions**: 1. **Update plant balance**: `plant.balance -= switch_cost` 2. **Generate command**: Return `ChangeFurnaceGroupTechnology` **Command includes**: - Technology change details (old → new) - Economic metrics (NPV, COSA) - Cost details (CAPEX with/without subsidies, cost of debt) - Technical details (BOM, utilization, capacity) - Subsidy configurations ## Command Types Returned ### 1. `RenovateFurnaceGroup` - **When**: Current tech optimal AND lifetime expired AND affordable - **Includes**: Renovation CAPEX, subsidies, cost of debt ### 2. `ChangeFurnaceGroupTechnology` - **When**: Different tech selected AND affordable AND passes probability filter AND within capacity limits - **Includes**: Full technology switch details, NPV, COSA, BOM, subsidies ### 3. `CloseFurnaceGroup` - **When**: - Forced closure (historic losses exceed threshold) - Cannot afford renovation when lifetime expired - **Includes**: Plant ID and furnace group ID ### 4. `None` - **When**: - Plant has negative balance - Furnace group pre-retirement - All NPVs negative or zero - Cannot afford switch - Probabilistic rejection - Capacity limit exceeded - Current tech optimal but lifetime not expired ## Data Dependencies ### For Strategic Decisions (`evaluate_furnace_group_strategy`) - `plant.balance`: Available capital for investment - `furnace_group.status`: Operating status - `furnace_group.historic_balance`: Cumulative profit/loss - `furnace_group.lifetime.expired`: Whether renovation is needed - `furnace_group.has_ccs_or_ccu`: CCS/CCU equipment flag ### External Functions Called - `furnace_group.optimal_technology_name()`: Gets NPV analysis - `filter_active_subsidies()`: Filters subsidies by year - `calculate_debt_with_subsidies()`: Applies debt subsidies # Function Relationship Summary ## Division of Responsibilities | Aspect | `optimal_technology_name()` | `evaluate_furnace_group_strategy()` | |--------|----------------------------|-------------------------------------| | **Role** | Economic analyst | Strategic decision-maker | | **Question** | "What are the economics?" | "What should we do?" | | **Type** | Pure function (no side effects) | Impure function (updates state) | | **Returns** | NPV dictionaries, COSA, BOMs | Command objects or None | | **Considers** | Market prices, costs, subsidies | Affordability, risk, capacity limits | | **Modifies** | Nothing | Plant balance sheet | | **Calls** | Calculate_costs functions | optimal_technology_name() | | **Level** | FurnaceGroup level | Plant level | ## Why This Architecture? ### Separation of Concerns - **Economic analysis** is complex and reusable - **Strategic decisions** depend on context (plant finances, capacity limits, policy) - Separating them allows testing economics independently of business rules ### Testability - Can test NPV calculations without mocking entire decision system - Can test decision logic with predetermined NPV inputs ### Clarity - Clear distinction between "what's optimal economically" and "what's feasible practically" - Makes code easier to understand and maintain ### Flexibility - Can change decision rules without touching NPV calculations - Can add new economic factors without changing decision logic ## Key Metrics to Monitor ### Economic Analysis Metrics 1. **COSA vs NPV**: If COSA > NPV, switch is unprofitable 2. **Utilization rates**: Lower utilization reduces NPV 3. **Carbon cost impact**: Rising carbon prices favor cleaner technologies 4. **Subsidy effectiveness**: Track NPV changes with/without subsidies 5. **Brownfield advantage**: Compare brownfield vs greenfield NPVs ### Decision Metrics 1. **Acceptance rate**: % of positive-NPV switches actually executed 2. **Capacity utilization**: How close to annual limits 3. **Rejection reasons**: Categorize why switches don't happen 4. **Technology distribution**: Which technologies are being adopted ### Performance Indicators 1. **Average NPV of executed switches**: Quality of decisions 2. **Renovation vs. switch ratio**: Technology transition pace 3. **Closure rate**: Industry health indicator 4. **Balance sheet health**: Plant financial sustainability # Glossary - **COSA**: Cost of Stranded Assets - economic loss from abandoning existing technology (includes remaining debt and foregone profits) - **NPV**: Net Present Value - present value of future cash flows minus initial investment - **BOM**: Bill of Materials - resource requirements for production (materials and energy) - **Brownfield**: Retrofit/upgrade of existing facility (reduced CAPEX) - **Greenfield**: Complete new installation (full CAPEX) - **CAPEX**: Capital Expenditure - upfront investment cost - **OPEX**: Operating Expenditure - ongoing operational costs - **FOPEX**: Fixed Operating Expenditure - costs independent of production volume - **VOPEX**: Variable Operating Expenditure - costs that scale with production - **Equity Share**: Fraction of investment paid from company funds (vs. debt financing) - **Utilization Rate**: Actual production as percentage of capacity - **Acceptance Probability**: Likelihood of executing a switch given positive NPV (models risk aversion)