1313log = logging .getLogger (__name__ )
1414
1515
16+ def _generate_vetting_csv (
17+ original_demand : pd .DataFrame ,
18+ modified_demand : pd .DataFrame ,
19+ output_path : str ,
20+ ) -> None :
21+ """Generate a CSV file showing material demand subtraction details.
22+
23+ Parameters
24+ ----------
25+ original_demand : pd.DataFrame
26+ Original demand data before subtraction
27+ modified_demand : pd.DataFrame
28+ Modified demand data after subtraction
29+ output_path : str
30+ Path where to save the vetting CSV file
31+ """
32+ # Reset index to work with columns
33+ orig = original_demand .reset_index ()
34+ mod = modified_demand .reset_index ()
35+
36+ # Merge original and modified data
37+ vetting_data = orig .merge (
38+ mod ,
39+ on = ["node" , "year" , "commodity" ],
40+ suffixes = ("_original" , "_modified" ),
41+ how = "outer" ,
42+ ).fillna (0 )
43+
44+ # Calculate subtraction amounts and percentages
45+ vetting_data ["subtracted_amount" ] = (
46+ vetting_data ["value_original" ] - vetting_data ["value_modified" ]
47+ )
48+
49+ # Calculate percentage subtracted (avoid division by zero)
50+ vetting_data ["subtraction_percentage" ] = (
51+ vetting_data ["subtracted_amount" ]
52+ / vetting_data ["value_original" ].replace (0 , 1 )
53+ * 100
54+ )
55+
56+ # Replace infinite values with 0 (when original was 0)
57+ vetting_data ["subtraction_percentage" ] = vetting_data [
58+ "subtraction_percentage"
59+ ].replace ([float ("inf" ), - float ("inf" )], 0 )
60+
61+ # Round to reasonable precision
62+ vetting_data ["subtraction_percentage" ] = vetting_data [
63+ "subtraction_percentage"
64+ ].round (2 )
65+
66+ # Select and rename columns for clarity
67+ output_columns = [
68+ "node" ,
69+ "year" ,
70+ "commodity" ,
71+ "value_original" ,
72+ "value_modified" ,
73+ "subtracted_amount" ,
74+ "subtraction_percentage" ,
75+ ]
76+
77+ vetting_data = vetting_data [output_columns ].copy ()
78+ vetting_data .columns = [
79+ "node" ,
80+ "year" ,
81+ "commodity" ,
82+ "original_demand" ,
83+ "modified_demand" ,
84+ "subtracted_amount" ,
85+ "subtraction_percentage" ,
86+ ]
87+
88+ # # Filter out rows where no subtraction occurred
89+ # vetting_data = vetting_data[vetting_data["subtracted_amount"] > 0]
90+
91+ # Sort by commodity, node, year for better readability
92+ vetting_data = vetting_data .sort_values (["commodity" , "node" , "year" ])
93+
94+ # Save to CSV
95+ vetting_data .to_csv (output_path , index = False )
96+
97+ log .info (f"Vetting CSV saved to: { output_path } " )
98+
99+ # Log summary statistics
100+ if len (vetting_data ) > 0 :
101+ avg_pct = vetting_data ["subtraction_percentage" ].mean ()
102+ max_pct = vetting_data ["subtraction_percentage" ].max ()
103+ log .info (f"Average subtraction percentage: { avg_pct :.2f} %" )
104+ log .info (f"Max subtraction percentage: { max_pct :.2f} %" )
105+
106+
107+ # Maybe it is better to have one function for each method?
16108def subtract_material_demand (
17109 scenario : "Scenario" ,
18110 info : "ScenarioInfo" ,
19111 sturm_r : pd .DataFrame ,
20112 sturm_c : pd .DataFrame ,
21113 method : str = "bm_subtraction" ,
114+ generate_vetting_csv : bool = True ,
115+ vetting_output_path : str = "material_demand_subtraction_vetting.csv" ,
22116) -> pd .DataFrame :
23117 """Subtract inter-sector material demand from existing demands in scenario.
24118
@@ -43,6 +137,11 @@ def subtract_material_demand(
43137 - "im_subtraction": substract base year and rerun material demand projection
44138 - "pm_subtraction": to be determined (currently treated as additional demand)
45139 - "tm_subtraction": to be determined
140+ generate_vetting_csv : bool, optional
141+ Whether to generate a CSV file showing subtraction details (default: True)
142+ vetting_output_path : str, optional
143+ Path for the vetting CSV file (default:
144+ "material_demand_subtraction_vetting.csv")
46145
47146 Returns
48147 -------
@@ -54,6 +153,9 @@ def subtract_material_demand(
54153 index_cols = ["node" , "year" , "commodity" ]
55154
56155 if method == "bm_subtraction" :
156+ # Store original demand for vetting if requested
157+ original_demand = mat_demand .copy () if generate_vetting_csv else None
158+
57159 # Subtract the building material demand trajectory from existing demands
58160 for rc , base_data , how in (
59161 ("resid" , sturm_r , "right" ),
@@ -99,6 +201,10 @@ def subtract_material_demand(
99201 .drop (columns = ["demand_comm_const" , "demand_resid_const" ])
100202 )
101203
204+ # Generate vetting CSV if requested
205+ if generate_vetting_csv and original_demand is not None :
206+ _generate_vetting_csv (original_demand , mat_demand , vetting_output_path )
207+
102208 elif method == "im_subtraction" :
103209 # TODO: to be implemented
104210 log .warning ("Method 'im_subtraction' not implemented yet, using bm_subtraction" )
0 commit comments