From d144bae058919f4cbb0a1fa0458014efe5ad86c8 Mon Sep 17 00:00:00 2001 From: "jia.zhou" <48254033+JiaZhou-PU@users.noreply.github.com> Date: Tue, 10 Feb 2026 08:34:10 -0600 Subject: [PATCH 01/25] 1st crf api --- docs/source/reference/main.rst | 106 ---------------- src/crf/api.py | 107 +++++++++++++++++ src/crf/io/excel_inputs.py | 39 ++++++ src/crf/io/excel_levers.py | 13 ++ src/crf/model/avg_runner.py | 54 +++++++++ src/crf/model/core_accounts.py | 164 +++++++++++++++++++++++++ src/crf/model/direct_cost.py | 199 +++++++++++++++++++++++++++++++ src/crf/model/finance.py | 112 +++++++++++++++++ src/crf/model/indirect_cost.py | 50 ++++++++ src/crf/model/learning.py | 98 +++++++++++++++ src/crf/model/pipeline.py | 62 ++++++++++ src/crf/sampling/lever_schema.py | 28 +++++ src/crf/sampling/postprocess.py | 6 + src/crf/sampling/sampler.py | 62 ++++++++++ src/crf/types.py | 42 +++++++ src/crf/utils/df_ops.py | 16 +++ src/crf/utils/serialize.py | 8 ++ 17 files changed, 1060 insertions(+), 106 deletions(-) delete mode 100644 docs/source/reference/main.rst create mode 100644 src/crf/api.py create mode 100644 src/crf/io/excel_inputs.py create mode 100644 src/crf/io/excel_levers.py create mode 100644 src/crf/model/avg_runner.py create mode 100644 src/crf/model/core_accounts.py create mode 100644 src/crf/model/direct_cost.py create mode 100644 src/crf/model/finance.py create mode 100644 src/crf/model/indirect_cost.py create mode 100644 src/crf/model/learning.py create mode 100644 src/crf/model/pipeline.py create mode 100644 src/crf/sampling/lever_schema.py create mode 100644 src/crf/sampling/postprocess.py create mode 100644 src/crf/sampling/sampler.py create mode 100644 src/crf/types.py create mode 100644 src/crf/utils/df_ops.py create mode 100644 src/crf/utils/serialize.py diff --git a/docs/source/reference/main.rst b/docs/source/reference/main.rst deleted file mode 100644 index 7b06104..0000000 --- a/docs/source/reference/main.rst +++ /dev/null @@ -1,106 +0,0 @@ - - -Accert Code Reference -========================== -This section provides detailed information on the Accert codebase. The Accert codebase is divided into two main sections: the main Accert class and utility functions. The main Accert class contains the main functions and methods for the Accert model, while the utility functions contain helper functions for the Accert model. - -.. toctree:: - :maxdepth: 1 - :caption: Contents: - -Accert Class --------------------- - -.. autosummary:: - :toctree: main/ - :template: function.rst - - - Main.Accert.__init__ - Main.Accert.setup_table_names - Main.Accert.load_obj - Main.Accert.get_current_COAs - Main.Accert.update_account_before_insert - Main.Accert.insert_new_COA - Main.Accert.insert_COA - Main.Accert.extract_variable_info_on_name - Main.Accert.extract_super_val - Main.Accert.update_input_variable - Main.Accert.update_variable_info_on_name - Main.Accert.update_super_variable - Main.Accert.extract_total_cost_on_name - Main.Accert.check_unit_conversion - Main.Accert.convert_unit - Main.Accert.convert_unit_scale - Main.Accert.update_total_cost - Main.Accert.update_total_cost_on_name - Main.Accert.get_var_value_by_name - Main.Accert.run_pre_alg - Main.Accert.update_account_value - Main.Accert.update_cost_element_on_name - Main.Accert.update_new_cost_elements - Main.Accert.update_new_accounts - Main.Accert.update_account_table_by_cost_elements - Main.Accert.roll_up_cost_elements - Main.Accert.roll_up_cost_elements_by_level - Main.Accert.roll_up_account_table - Main.Accert.roll_up_account_table_by_level - Main.Accert.roll_up_account_table_GNCOA - Main.Accert.sum_cost_elements_2C - Main.Accert.fetch_sum_and_update - Main.Accert.roll_up_lmt_account_2C - Main.Accert.roll_up_lmt_direct_cost - Main.Accert.cal_direct_cost_elements - Main.Accert.roll_up_lmt_account_table - Main.Accert.print_logo - Main.Accert.execute_accert - Main.Accert.process_reference_model - Main.Accert.process_power_inputs - Main.Accert.process_variables - Main.Accert.process_super_values - Main.Accert.process_COA - Main.Accert.process_level_accounts - Main.Accert.process_ce - Main.Accert.process_var - Main.Accert.process_alg - Main.Accert.check_and_process_total_cost - Main.Accert.check_total_cost_changed - Main.Accert.check_total_cost_accounts - Main.Accert.process_total_cost - Main.Accert.process_total_cost_accounts - Main.Accert.exit_with_error - Main.Accert.finalize_process - Main.Accert.generate_results - Main.Accert._generate_common_results - Main.Accert._common_cost_processing - Main.Accert._print_results - Main.Accert._pwr12be_processing - Main.Accert._no_cost_element_processing - Main.Accert.generate_results_table_with_cost_elements - Main.Accert._generate_excel - Main.Accert.generate_results_table - - -Accert Utility Functions --------------------------- -.. autosummary:: - :toctree: utility/ - :template: function.rst - - utility_accert.Utility_methods.__init__ - utility_accert.Utility_methods.setup_table_names - utility_accert.Utility_methods.print_table - utility_accert.Utility_methods.print_account - utility_accert.Utility_methods.print_leveled_accounts - utility_accert.Utility_methods.print_leveled_accounts_gncoa - utility_accert.Utility_methods.print_algorithm - utility_accert.Utility_methods.print_cost_element - utility_accert.Utility_methods.print_facility - utility_accert.Utility_methods.print_escalation - utility_accert.Utility_methods.print_variable - utility_accert.Utility_methods.print_user_request_parameter - utility_accert.Utility_methods.print_updated_cost_elements - utility_accert.Utility_methods.extract_affected_cost_elements - utility_accert.Utility_methods.extract_affected_accounts - utility_accert.Utility_methods.extract_user_changed_variables - utility_accert.Utility_methods.extract_changed_cost_elements diff --git a/src/crf/api.py b/src/crf/api.py new file mode 100644 index 0000000..5723ea4 --- /dev/null +++ b/src/crf/api.py @@ -0,0 +1,107 @@ +import csv +import pickle +import numpy as np + +from .io.excel_inputs import InputStore +from .io.excel_levers import read_lever_sheet +from .sampling.sampler import sample_levers +from .sampling.lever_schema import unpack_sample_column, STATIC_KEYS +from .sampling.postprocess import apply_itc_rounding +from .model.avg_runner import run_avg_all_units +from .utils.serialize import write_csv_row, stream_pickle_dump + + +def normalize_levers(levers: dict) -> dict: + """Convert raw lever dict to model-ready normalized values.""" + def label01(x, zero_label, one_label): + if x == 0: return zero_label + if x == 1: return one_label + return x + + return { + "num_orders": int(levers["num_orders"]), + "ITC_0": float(levers["itc_percent"]) / 100.0, + "n_ITC": levers["n_itc"], + "interest_rate_0": float(levers["interest_percent"]) / 100.0, + "design_completion_0": float(levers["design_completion_percent"]) / 100.0, + "Design_Maturity_0": levers["design_maturity"], + "proc_exp_0": levers["proc_exp"], + "N_proc": levers["N_proc"], + "ce_exp_0": levers["ce_exp"], + "N_cons": levers["N_cons"], + "ae_exp_0": levers["ae_exp"], + "N_AE": levers["N_AE"], + "standardization_0": float(levers["standardization_percent"]) / 100.0, + "mod_0": label01(levers["modularity_code"], "stick_built", "modularized"), + "BOP_grade_0": label01(levers["bop_grade_code"], "nuclear", "non_nuclear"), + "RB_grade_0": label01(levers["rb_grade_code"], "nuclear", "non_nuclear"), + } + + +def run_one_scenario(config: dict, levers: dict) -> dict: + store = InputStore(config["inputs_xlsx"]) + levers = apply_itc_rounding(levers) + inp = normalize_levers(levers) + + result = run_avg_all_units(config=config, inp=inp, store=store) + + # attach static input columns (raw sampled values, matching your previous CSV meaning) + # IMPORTANT: keep the same static key ordering as before + static_vals = { + "Num_orders": levers["num_orders"], + "ITC": levers["itc_percent"], + "n_ITC": levers["n_itc"], + "interest rate": levers["interest_percent"], + "Design completion": levers["design_completion_percent"], + "Design_Maturity_0": levers["design_maturity"], + "supply chain exp_0": levers["proc_exp"], + "N supply chain": levers["N_proc"], + "Const Proficiency": levers["ce_exp"], + "N const prof": levers["N_cons"], + "AE": levers["ae_exp"], + "N AE prof": levers["N_AE"], + "standardization": levers["standardization_percent"], + "modularity": levers["modularity_code"], + "BOP commercial": levers["bop_grade_code"], + "RB Safety Related": levers["rb_grade_code"], + } + + return {**static_vals, **result} + + +def run_sampling_from_excel( + config: dict, + levers_xlsx: str, + n_samples: int, + out_csv: str, + out_pkl: str, + seed: int | None = None +): + levers_df = read_lever_sheet(levers_xlsx, sheet_name="Levers") + samples = sample_levers(n_samples, levers_df, seed=seed) # shape (n_levers, n_samples) + + # headers: build from max num_orders in sampled set + max_orders = int(np.max(samples[0, :])) + + headers = ( + STATIC_KEYS + + [f"OCC_{i}" for i in range(max_orders)] + + [f"TCI_{i}" for i in range(max_orders)] + + [f"duration_{i}" for i in range(max_orders)] + + [ + "cons_duration_cumulative_wz_startup", + "occLastUnit", "TCILastUnit", "durationsLastUnit", + "avg_OCC", "avg_TCI", "avg_duration", + ] + ) + + with open(out_csv, "w", newline="", encoding="utf-8") as csv_f, open(out_pkl, "wb") as pkl_f: + writer = csv.DictWriter(csv_f, fieldnames=headers) + writer.writeheader() + + for i in range(n_samples): + levers = unpack_sample_column(samples[:, i]) + row = run_one_scenario(config, levers) + + write_csv_row(writer, row, headers) + stream_pickle_dump(row, pkl_f) diff --git a/src/crf/io/excel_inputs.py b/src/crf/io/excel_inputs.py new file mode 100644 index 0000000..7c39f59 --- /dev/null +++ b/src/crf/io/excel_inputs.py @@ -0,0 +1,39 @@ +import pandas as pd + +COLS = [ + "Account", "Title", "Total Cost (USD)", + "Factory Equipment Cost", "Site Labor Hours", + "Site Labor Cost", "Site Material Cost" +] + +class InputStore: + def __init__(self, inputs_xlsx: str): + self.inputs_xlsx = inputs_xlsx + self._baseline = {} + self._spending = None + + def get_baseline(self, reactor_type: str): + if reactor_type in self._baseline: + df, power = self._baseline[reactor_type] + return df.copy(), power + + if reactor_type == "Concept A": + df = pd.read_excel(self.inputs_xlsx, sheet_name="Concept_A", nrows=69)[COLS].copy() + power = 1056 * 1000 + elif reactor_type == "Concept B": + df = pd.read_excel(self.inputs_xlsx, sheet_name="Concept_B", nrows=69)[COLS].copy() + power = 310.8 * 1000 + else: + raise ValueError(f"Unknown reactor_type: {reactor_type}") + + self._baseline[reactor_type] = (df, power) + return df.copy(), power + + def get_spending_curve(self): + if self._spending is not None: + return self._spending + sp = pd.read_excel(self.inputs_xlsx, sheet_name="Ref Spending Curve", nrows=104, usecols="A:D") + months = sp["Month"].to_numpy(dtype=float) + cdfs = sp["CDF"].to_numpy(dtype=float) + self._spending = (months, cdfs) + return self._spending diff --git a/src/crf/io/excel_levers.py b/src/crf/io/excel_levers.py new file mode 100644 index 0000000..d17d281 --- /dev/null +++ b/src/crf/io/excel_levers.py @@ -0,0 +1,13 @@ +import pandas as pd + +REQUIRED_COLS = { + "Distribution", "Min", "Max", "Median", "Low", "High", + "Set", "Probabilities", "Type" +} + +def read_lever_sheet(levers_xlsx: str, sheet_name="Levers") -> pd.DataFrame: + df = pd.read_excel(levers_xlsx, sheet_name=sheet_name) + missing = REQUIRED_COLS - set(df.columns) + if missing: + raise ValueError(f"Levers sheet missing columns: {sorted(missing)}") + return df diff --git a/src/crf/model/avg_runner.py b/src/crf/model/avg_runner.py new file mode 100644 index 0000000..c8dc0b7 --- /dev/null +++ b/src/crf/model/avg_runner.py @@ -0,0 +1,54 @@ +import numpy as np +from .pipeline import calculate_final_result + +def run_avg_all_units(config: dict, inp: dict, store): + """ + Runs n_th=1..num_orders and returns a CSV-friendly dict: + OCC_i, TCI_i, duration_i plus summary metrics. + """ + num_orders = int(inp["num_orders"]) + OCC, TCI, DUR = [], [], [] + + for n_th in range(1, num_orders + 1): + _, occ, tci, dur = calculate_final_result(config, inp, store, n_th=n_th) + OCC.append(occ) + TCI.append(tci) + DUR.append(dur) + + OCC = np.array(OCC, dtype=float) + TCI = np.array(TCI, dtype=float) + DUR = np.array(DUR, dtype=float) + + # summaries + occLastUnit = float(OCC[-1]) + TCILastUnit = float(TCI[-1]) + durationsLastUnit = float(DUR[-1]) + + avg_OCC = float(np.mean(OCC)) + avg_TCI = float(np.mean(TCI)) + avg_duration = float(np.mean(DUR)) + + final_startup_duration = max(7, config["startup_0"] * (1 - 0.3) ** np.log2(num_orders)) + cons_duration_cumulative_wz_startup = ( + (1 - config["staggering_ratio"]) * np.sum(DUR[:-1]) + DUR[-1] + final_startup_duration + ) + + # expand arrays to OCC_i / TCI_i / duration_i + out = {} + for i, v in enumerate(OCC): + out[f"OCC_{i}"] = float(v) + for i, v in enumerate(TCI): + out[f"TCI_{i}"] = float(v) + for i, v in enumerate(DUR): + out[f"duration_{i}"] = float(v) + + out.update({ + "cons_duration_cumulative_wz_startup": float(cons_duration_cumulative_wz_startup), + "occLastUnit": occLastUnit, + "TCILastUnit": TCILastUnit, + "durationsLastUnit": durationsLastUnit, + "avg_OCC": avg_OCC, + "avg_TCI": avg_TCI, + "avg_duration": avg_duration, + }) + return out diff --git a/src/crf/model/core_accounts.py b/src/crf/model/core_accounts.py new file mode 100644 index 0000000..4b89d31 --- /dev/null +++ b/src/crf/model/core_accounts.py @@ -0,0 +1,164 @@ +import numpy as np +import pandas as pd + +def update_high_level_costs(db: pd.DataFrame, reactor_power: float) -> pd.DataFrame: + # account 21 + db.loc[db.Account == 21, "Factory Equipment Cost"] = ( + db.loc[db.Account == 212, "Factory Equipment Cost"].values + + db.loc[db.Account == 213, "Factory Equipment Cost"].values + + db.loc[db.Account == "211 plus 214 to 219", "Factory Equipment Cost"].values + ) + db.loc[db.Account == 21, "Site Material Cost"] = ( + db.loc[db.Account == 212, "Site Material Cost"].values + + db.loc[db.Account == 213, "Site Material Cost"].values + + db.loc[db.Account == "211 plus 214 to 219", "Site Material Cost"].values + ) + db.loc[db.Account == 21, "Site Labor Cost"] = ( + db.loc[db.Account == 212, "Site Labor Cost"].values + + db.loc[db.Account == 213, "Site Labor Cost"].values + + db.loc[db.Account == "211 plus 214 to 219", "Site Labor Cost"].values + ) + db.loc[db.Account == 21, "Site Labor Hours"] = ( + db.loc[db.Account == 212, "Site Labor Hours"].values + + db.loc[db.Account == 213, "Site Labor Hours"].values + + db.loc[db.Account == "211 plus 214 to 219", "Site Labor Hours"].values + ) + + # account 23 + db.loc[db.Account == 23, "Factory Equipment Cost"] = ( + db.loc[db.Account == "232.1", "Factory Equipment Cost"].values + + db.loc[db.Account == 233, "Factory Equipment Cost"].values + ) + db.loc[db.Account == 23, "Site Material Cost"] = ( + db.loc[db.Account == "232.1", "Site Material Cost"].values + + db.loc[db.Account == 233, "Site Material Cost"].values + ) + db.loc[db.Account == 23, "Site Labor Cost"] = ( + db.loc[db.Account == "232.1", "Site Labor Cost"].values + + db.loc[db.Account == 233, "Site Labor Cost"].values + ) + db.loc[db.Account == 23, "Site Labor Hours"] = ( + db.loc[db.Account == "232.1", "Site Labor Hours"].values + + db.loc[db.Account == 233, "Site Labor Hours"].values + ) + + # total costs for 21..26 components + for x in [21, 212, 213, "211 plus 214 to 219", 22, 23, "232.1", 233, 24, 26]: + db.loc[db["Account"] == x, "Total Cost (USD)"] = ( + db.loc[db["Account"] == x, "Factory Equipment Cost"] + + db.loc[db["Account"] == x, "Site Labor Cost"] + + db.loc[db["Account"] == x, "Site Material Cost"] + ) + + # subtotals + db.loc[db["Title"] == "10s - Subtotal", "Total Cost (USD)"] = db.loc[ + db["Account"].isin([11, 12, 13, 14, 15, 16, 18]), "Total Cost (USD)" + ].sum() + + db.loc[db["Title"] == "20s - Subtotal", "Total Cost (USD)"] = db.loc[ + db["Account"].isin([21, 22, 23, 24, 25, 26, 28]), "Total Cost (USD)" + ].sum() + + db.loc[db["Title"] == "30s - Subtotal", "Total Cost (USD)"] = db.loc[ + db["Account"].isin([31, 32, 33, 34, 35]), "Total Cost (USD)" + ].sum() + + db.loc[db["Title"] == "50s - Subtotal", "Total Cost (USD)"] = db.loc[ + db["Account"].isin([51, 52, 54]), "Total Cost (USD)" + ].sum() + + db.loc[db["Title"] == "60s - Subtotal", "Total Cost (USD)"] = db.loc[ + db["Account"].isin([62]), "Total Cost (USD)" + ].sum() + + # $/kWe lines + for t in ["10s", "20s", "30s", "40s", "50s", "60s"]: + db.loc[db["Title"] == f"{t} - $/kWe", "Total Cost (USD)"] = ( + db.loc[db["Title"] == f"{t} - Subtotal", "Total Cost (USD)"].values / reactor_power + ) + + # final rollups + db.loc[db["Title"] == "Total Direct Capital Cost (Accounts 10 to 20)", "Total Cost (USD)"] = ( + db.loc[db["Title"] == "10s - Subtotal", "Total Cost (USD)"].values + + db.loc[db["Title"] == "20s - Subtotal", "Total Cost (USD)"].values + ) + + db.loc[db["Title"] == "Base Construction Cost (Accounts 10 to 30)", "Total Cost (USD)"] = ( + db.loc[db["Title"] == "Total Direct Capital Cost (Accounts 10 to 20)", "Total Cost (USD)"].values + + db.loc[db["Title"] == "30s - Subtotal", "Total Cost (USD)"].values + ) + + db.loc[db["Title"] == "Total Overnight Cost (Accounts 10 to 50)", "Total Cost (USD)"] = ( + db.loc[db["Title"] == "Base Construction Cost (Accounts 10 to 30)", "Total Cost (USD)"].values + + db.loc[db["Title"] == "50s - Subtotal", "Total Cost (USD)"].values + ) + + db.loc[db["Title"] == "Total Capital Investment Cost (All Accounts)", "Total Cost (USD)"] = ( + db.loc[db["Title"] == "Total Overnight Cost (Accounts 10 to 50)", "Total Cost (USD)"].values + + db.loc[db["Title"] == "60s - Subtotal", "Total Cost (USD)"].values + ) + + # final $/kWe + db.loc[db["Title"] == "(Accounts 10 to 20) US$/kWe", "Total Cost (USD)"] = ( + db.loc[db["Title"] == "Total Direct Capital Cost (Accounts 10 to 20)", "Total Cost (USD)"].values / reactor_power + ) + db.loc[db["Title"] == "(Accounts 10 to 30) US$/kWe", "Total Cost (USD)"] = ( + db.loc[db["Title"] == "Base Construction Cost (Accounts 10 to 30)", "Total Cost (USD)"].values / reactor_power + ) + db.loc[db["Title"] == "(Accounts 10 to 50) US$/kWe", "Total Cost (USD)"] = ( + db.loc[db["Title"] == "Total Overnight Cost (Accounts 10 to 50)", "Total Cost (USD)"].values / reactor_power + ) + db.loc[db["Title"] == "(Accounts 10 to 60) US$/kWe", "Total Cost (USD)"] = ( + db.loc[db["Title"] == "Total Capital Investment Cost (All Accounts)", "Total Cost (USD)"].values / reactor_power + ) + return db + +def ITC_reduction_factor(itc_level: float) -> float: + itc_values = [0, 0.06, 0.3, 0.4, 0.5] + factors = [1, 0.95, 0.73, 0.63, 0.53] + return float(np.interp(itc_level, itc_values, factors)) + +def sum_lab_hrs(db: pd.DataFrame): + return ( + db.loc[db.Account == 21, "Site Labor Hours"].values + + db.loc[db.Account == 22, "Site Labor Hours"].values + + db.loc[db.Account == 23, "Site Labor Hours"].values + + db.loc[db.Account == 24, "Site Labor Hours"].values + + db.loc[db.Account == 26, "Site Labor Hours"].values + ) + +def update_cons_duration(db0: pd.DataFrame, db1: pd.DataFrame, ref_duration: float): + sum_old = ( + db0.loc[db0.Account == 21, "Site Labor Hours"].values + + db0.loc[db0.Account == 22, "Site Labor Hours"].values + + db0.loc[db0.Account == 23, "Site Labor Hours"].values + + db0.loc[db0.Account == 24, "Site Labor Hours"].values + + db0.loc[db0.Account == 26, "Site Labor Hours"].values + ) + sum_new = ( + db1.loc[db1.Account == 21, "Site Labor Hours"].values + + db1.loc[db1.Account == 22, "Site Labor Hours"].values + + db1.loc[db1.Account == 23, "Site Labor Hours"].values + + db1.loc[db1.Account == 24, "Site Labor Hours"].values + + db1.loc[db1.Account == 26, "Site Labor Hours"].values + ) + lab_delta = (sum_new - sum_old) / sum_old + return 0.3 * lab_delta * ref_duration + ref_duration + +def update_cons_duration_2(db0, db1, ref_duration, prev_cons_duration, baseline_lab_hours): + sum_old = ( + db0.loc[db0.Account == 21, "Site Labor Hours"].values + + db0.loc[db0.Account == 22, "Site Labor Hours"].values + + db0.loc[db0.Account == 23, "Site Labor Hours"].values + + db0.loc[db0.Account == 24, "Site Labor Hours"].values + + db0.loc[db0.Account == 26, "Site Labor Hours"].values + ) + sum_new = ( + db1.loc[db1.Account == 21, "Site Labor Hours"].values + + db1.loc[db1.Account == 22, "Site Labor Hours"].values + + db1.loc[db1.Account == 23, "Site Labor Hours"].values + + db1.loc[db1.Account == 24, "Site Labor Hours"].values + + db1.loc[db1.Account == 26, "Site Labor Hours"].values + ) + lab_delta = (sum_new - sum_old) / baseline_lab_hours + return 0.3 * lab_delta * ref_duration + prev_cons_duration diff --git a/src/crf/model/direct_cost.py b/src/crf/model/direct_cost.py new file mode 100644 index 0000000..1e68631 --- /dev/null +++ b/src/crf/model/direct_cost.py @@ -0,0 +1,199 @@ +# src/crf/model/direct_cost.py +# Direct-cost pipeline: +# add_factory_cost -> add_land_cost -> add_BOP_RP_grades -> add_bulk_ordering -> add_reworking_productivity +# Returns updated df and construction duration (no supply-chain delay yet) + +import numpy as np +import pandas as pd + +from ..utils.df_ops import getv, setv, mulv +from .core_accounts import ( + update_high_level_costs, + sum_lab_hrs, + update_cons_duration, + update_cons_duration_2, +) + +COLS = [ + "Account", "Title", "Total Cost (USD)", + "Factory Equipment Cost", "Site Labor Hours", + "Site Labor Cost", "Site Material Cost" +] + +ACCT_DIRECT = [212, 213, "211 plus 214 to 219", 22, "232.1", 233, 24, 26] + + +def add_factory_cost(df: pd.DataFrame, power: float, f_22: float, f_2321: float, num_orders: int): + db = df.copy() + + # Add factory-building shares; clear old values first (keep numeric dtype) + setv(db, 22, "Factory Equipment Cost", np.nan) + setv(db, "232.1", "Factory Equipment Cost", np.nan) + + setv(db, 22, "Factory Equipment Cost", float(getv(df, 22, "Factory Equipment Cost")) + f_22 / num_orders) + setv(db, "232.1", "Factory Equipment Cost", float(getv(df, "232.1", "Factory Equipment Cost")) + f_2321 / num_orders) + + db = update_high_level_costs(db, power)[COLS].copy() + baseline_lab_hours = sum_lab_hrs(db) + return db, baseline_lab_hours + + +def add_land_cost(df: pd.DataFrame, land_cost_per_acre: float, power: float): + db = df.copy() + factor = land_cost_per_acre / 22000.0 + for acct in [11, 12, 51]: + setv(db, acct, "Total Cost (USD)", np.nan) + setv(db, acct, "Total Cost (USD)", float(getv(df, acct, "Total Cost (USD)")) * factor) + return update_high_level_costs(db, power)[COLS].copy() + + +def add_BOP_RP_grades( + df: pd.DataFrame, + RB_grade_0: str, + BOP_grade_0: str, + power: float, + reactor_type: str, + n_th: int, + mod_0: str +): + # RB grade does not change; BOP becomes non_nuclear after FOAK + RB_grade = RB_grade_0 + BOP_grade = BOP_grade_0 if n_th == 1 else "non_nuclear" + + db = df.copy() + + if RB_grade == "non_nuclear": + mulv( + db, [212], + ["Site Material Cost", "Site Labor Cost", "Site Labor Hours", "Factory Equipment Cost"], + 0.6 + ) + + if BOP_grade == "non_nuclear": + mulv( + db, [213], + ["Site Material Cost", "Site Labor Cost", "Site Labor Hours", "Factory Equipment Cost"], + 0.6 + ) + # for 232.1 apply to factory+labor but NOT material (matches your original) + mulv( + db, ["232.1"], + ["Factory Equipment Cost", "Site Labor Cost", "Site Labor Hours"], + 0.6 + ) + + db2 = update_high_level_costs(db, power)[COLS].copy() + + # duration update from grade change + duration_ref = 125 if reactor_type == "Concept A" else 80 + new_dur = float(update_cons_duration(df, db2, duration_ref)) + + # modularity factor on duration; for n>=2 assume modularized + mod = mod_0 if n_th == 1 else "modularized" + mod_factor = 0.8 if mod == "modularized" else 1.0 + + return db2, new_dur * mod_factor + + +def add_bulk_ordering(df: pd.DataFrame, num_orders: int, f_22: float, f_2321: float, power: float): + """ + Applies average learning reduction to factory equipment cost of 22 and 232.1, + but keeps factory-building portion intact. + """ + db = df.copy() + + lr22 = 0.1802341659291420 + lr2321 = 0.2607462372040820 + + red22 = 0.0 + red2321 = 0.0 + for ith in range(1, num_orders + 1): + red22 += ((1 - lr22) ** np.log2(ith)) / num_orders + red2321 += ((1 - lr2321) ** np.log2(ith)) / num_orders + + old22 = float(getv(df, 22, "Factory Equipment Cost")) + new22 = red22 * (old22 - (f_22 / num_orders)) + (f_22 / num_orders) + setv(db, 22, "Factory Equipment Cost", new22) + + old2321 = float(getv(df, "232.1", "Factory Equipment Cost")) + new2321 = red2321 * (old2321 - (f_2321 / num_orders)) + (f_2321 / num_orders) + setv(db, "232.1", "Factory Equipment Cost", new2321) + + return update_high_level_costs(db, power)[COLS].copy() + + +def add_reworking_productivity( + df: pd.DataFrame, + reactor_type: str, + n_th: int, + design_completion_0: float, + ae_exp_0: float, + N_AE: float, + ce_exp_0: float, + N_cons: float, + power: float, + prev_cons_duration: float, + baseline_lab_hours +): + if n_th == 1: + design_completion = design_completion_0 + ae_exp = ae_exp_0 + ce_exp = ce_exp_0 + else: + design_completion = 1.0 + ae_exp = min(ae_exp_0 + (2 / N_AE) * (n_th - 1), 2) + ce_exp = min(ce_exp_0 + (2 / N_cons) * (n_th - 1), 2) + + productivity = 0.145 * ce_exp + 0.71 + + if reactor_type == "Concept B": + rework = (-0.9 * design_completion + 1.9) * (-0.15 * ae_exp + 1.3) * (-0.15 * ce_exp + 1.3) + ref_duration = 80 + else: + rework = (-0.69 * design_completion + 1.69) * (-0.125 * ae_exp + 1.25) * (-0.125 * ce_exp + 1.25) + ref_duration = 125 + + db = df.copy() + + for acct in ACCT_DIRECT: + setv(db, acct, "Factory Equipment Cost", float(getv(df, acct, "Factory Equipment Cost")) * rework) + setv(db, acct, "Site Material Cost", float(getv(df, acct, "Site Material Cost")) * rework) + setv(db, acct, "Site Labor Hours", float(getv(df, acct, "Site Labor Hours")) * rework / productivity) + setv(db, acct, "Site Labor Cost", float(getv(df, acct, "Site Labor Cost")) * rework / productivity) + + db2 = update_high_level_costs(db, power)[COLS].copy() + + new_dur = float(update_cons_duration_2(df, db2, ref_duration, prev_cons_duration, baseline_lab_hours)) + return db2, new_dur + + +def update_direct_cost( + store, + reactor_type: str, + n_th: int, + f_22: float, + f_2321: float, + land_cost_per_acre_0: float, + RB_grade_0: str, + BOP_grade_0: str, + num_orders: int, + design_completion_0: float, + ae_exp_0: float, + N_AE: float, + ce_exp_0: float, + N_cons: float, + mod_0: str +): + reactor_df, power = store.get_baseline(reactor_type) + + db, baseline_lab_hours = add_factory_cost(reactor_df, power, f_22, f_2321, num_orders) + db = add_land_cost(db, land_cost_per_acre_0, power) + db, prev_dur = add_BOP_RP_grades(db, RB_grade_0, BOP_grade_0, power, reactor_type, n_th, mod_0) + db = add_bulk_ordering(db, num_orders, f_22, f_2321, power) + db, dur_no_delay = add_reworking_productivity( + db, reactor_type, n_th, + design_completion_0, ae_exp_0, N_AE, ce_exp_0, N_cons, + power, prev_dur, baseline_lab_hours + ) + + return db, dur_no_delay diff --git a/src/crf/model/finance.py b/src/crf/model/finance.py new file mode 100644 index 0000000..6191352 --- /dev/null +++ b/src/crf/model/finance.py @@ -0,0 +1,112 @@ +# src/crf/model/finance.py +# Insurance, interest, ITC application + +import numpy as np +import pandas as pd + +from .core_accounts import update_high_level_costs, ITC_reduction_factor + +COLS = [ + "Account", "Title", "Total Cost (USD)", + "Factory Equipment Cost", "Site Labor Hours", + "Site Labor Cost", "Site Material Cost" +] + + +def insurance_cost_update(base_df: pd.DataFrame, df: pd.DataFrame, power: float): + db = df.copy() + + new_tot = float(db.loc[db["Title"].eq("20s - Subtotal"), "Total Cost (USD)"].iloc[0]) + \ + float(db.loc[db["Title"].eq("30s - Subtotal"), "Total Cost (USD)"].iloc[0]) + + base_tot = float(base_df.loc[base_df["Title"].eq("20s - Subtotal"), "Total Cost (USD)"].iloc[0]) + \ + float(base_df.loc[base_df["Title"].eq("30s - Subtotal"), "Total Cost (USD)"].iloc[0]) + + factor = new_tot / base_tot + + old_52 = float(df.loc[df["Account"].eq(52), "Total Cost (USD)"].iloc[0]) + db.loc[db["Account"].eq(52), "Total Cost (USD)"] = old_52 * factor + + return update_high_level_costs(db, power)[COLS].copy() + + +def update_interest_cost( + store, + df: pd.DataFrame, + final_construction_duration: float, + interest_rate: float, + startup_0: float, + n_th: int, + power: float +): + Months, CDFs = store.get_spending_curve() + dur = float(final_construction_duration) + + n_years = int(dur / 12) + if n_years <= 0: + annual_periods = np.array([dur - 1]) + else: + annual_periods = np.linspace(12, 12 * n_years, n_years) + if max(annual_periods) < int(dur) - 1: + annual_periods = np.append(annual_periods, dur - 1) + + new_period = 103 * annual_periods / dur + annual_cum_spend = np.interp(new_period, Months, CDFs) + annual_spend = np.append(annual_cum_spend[0], np.diff(annual_cum_spend)) + + tot_overnight_cost = float( + df.loc[df["Title"].eq("Total Overnight Cost (Accounts 10 to 50)"), "Total Cost (USD)"].iloc[0] + ) + + annual_loan_add = annual_spend * tot_overnight_cost + interest_exp = ((1 + interest_rate) ** ((dur - annual_periods) / 12)) * annual_loan_add - annual_loan_add + tot_int_exp_construction = float(np.sum(interest_exp)) + + if n_th == 1: + startup = startup_0 + else: + startup = max(7, startup_0 * (1 - 0.3) ** np.log2(n_th)) + + int_exp_startup = (tot_int_exp_construction + tot_overnight_cost) * ((1 + interest_rate) ** (startup / 12)) \ + - (tot_int_exp_construction + tot_overnight_cost) + + db = df.copy() + db.loc[db["Account"].eq(62), "Total Cost (USD)"] = float(int_exp_startup + tot_int_exp_construction) + + db2 = update_high_level_costs(db, power)[COLS].copy() + + tot_cap_investment = float( + db2.loc[db2["Title"].eq("Total Capital Investment Cost (All Accounts)"), "Total Cost (USD)"].iloc[0] + ) + return db2, tot_overnight_cost, tot_cap_investment + + +def update_itc( + df: pd.DataFrame, + tot_overnight_cost: float, + tot_cap_investment: float, + n_th: int, + ITC_0: float, + n_ITC: int, + reactor_power: float +): + ITC = ITC_0 if n_th <= n_ITC else 0.0 + + db = df.copy() + + itc_factor = ITC_reduction_factor(ITC) + itc_reduced_occ = tot_overnight_cost * itc_factor + occ_reduction = tot_overnight_cost - itc_reduced_occ + + # Titles must exist (same as your original) + db.loc[db["Title"].eq("Total Overnight Cost - ITC reduced"), "Total Cost (USD)"] = itc_reduced_occ + db.loc[db["Title"].eq("Total Overnight Cost -ITC reduced (US$/kWe)"), "Total Cost (USD)"] = itc_reduced_occ / reactor_power + db.loc[db["Title"].eq("Total Capital Investment Cost - ITC reduced"), "Total Cost (USD)"] = tot_cap_investment - occ_reduction + + levelized_NCI = float( + db.loc[db["Title"].eq("Total Capital Investment Cost - ITC reduced"), "Total Cost (USD)"].iloc[0] / reactor_power + ) + db.loc[db["Title"].eq("Total Capital Investment Cost - ITC reduced (US$/kWe)"), "Total Cost (USD)"] = levelized_NCI + + db2 = update_high_level_costs(db, reactor_power)[COLS].copy() + return db2, itc_reduced_occ / reactor_power, levelized_NCI diff --git a/src/crf/model/indirect_cost.py b/src/crf/model/indirect_cost.py new file mode 100644 index 0000000..939cf5f --- /dev/null +++ b/src/crf/model/indirect_cost.py @@ -0,0 +1,50 @@ +# src/crf/model/indirect_cost.py +# Indirect costs accounts 31–35, unchanged equations + +import pandas as pd + +from ..utils.df_ops import setv +from .core_accounts import update_high_level_costs + +COLS = [ + "Account", "Title", "Total Cost (USD)", + "Factory Equipment Cost", "Site Labor Hours", + "Site Labor Cost", "Site Material Cost" +] + + +def update_indirect_cost( + n_th: int, + standardization_0: float, + df: pd.DataFrame, + final_construction_duration: float, + power: float +): + standardization = min(0.7, standardization_0) if n_th == 1 else standardization_0 + factor_35 = (10 / 3) * (1 - standardization) + + db = df.copy() + + sum_new_mat_cost = 0.0 + sum_new_lab_cost = 0.0 + sum_new_lab_hrs = 0.0 + for acct in [21, 22, 23, 24, 26]: + sum_new_mat_cost += float(db.loc[db["Account"].eq(acct), "Site Material Cost"].iloc[0]) + sum_new_lab_cost += float(db.loc[db["Account"].eq(acct), "Site Labor Cost"].iloc[0]) + sum_new_lab_hrs += float(db.loc[db["Account"].eq(acct), "Site Labor Hours"].iloc[0]) + + dur = float(final_construction_duration) + + val31 = (sum_new_mat_cost * 0.785 * sum_new_lab_hrs / dur / 160 / 1058) + sum_new_lab_cost * 0.36 + val32 = sum_new_lab_cost * 0.36 * 3.661 * dur / 72 + val33 = 0.04207006 * val32 + val34 = 0.00354234616938 * val32 + val35 = (0.27017603 * val32) * factor_35 + + setv(db, 31, "Total Cost (USD)", val31) + setv(db, 32, "Total Cost (USD)", val32) + setv(db, 33, "Total Cost (USD)", val33) + setv(db, 34, "Total Cost (USD)", val34) + setv(db, 35, "Total Cost (USD)", val35) + + return update_high_level_costs(db, power)[COLS].copy() diff --git a/src/crf/model/learning.py b/src/crf/model/learning.py new file mode 100644 index 0000000..413f9c8 --- /dev/null +++ b/src/crf/model/learning.py @@ -0,0 +1,98 @@ +# src/crf/model/learning.py +# Learning effects: +# - learning_effect (cost learning by doing on direct accounts) +# - act_cons_duration_plus_delay (supply chain delay model) +# - duration_learning_effect (duration learning by doing) + +import numpy as np +import pandas as pd + +from ..utils.df_ops import getv, setv +from .core_accounts import update_high_level_costs + +COLS = [ + "Account", "Title", "Total Cost (USD)", + "Factory Equipment Cost", "Site Labor Hours", + "Site Labor Cost", "Site Material Cost" +] + +ACCT_DIRECT = [212, 213, "211 plus 214 to 219", 22, "232.1", 233, 24, 26] + + +def learning_effect(df: pd.DataFrame, n_th: int, standardization_0: float, power: float): + # standardization cap for FOAK + standardization = min(0.7, standardization_0) if n_th == 1 else standardization_0 + + # fitted learning rates (same order as ACCT_DIRECT) + mat_lr = np.array([ + 0.099588665391, 0.099588665391, 0.099588665391, 0.080817992281, + 0.0, 0.099588665391, 0.099588665391, 0.099588665391 + ]) * standardization / 0.7 + + lab_lr = np.array([ + 0.180678729399, 0.180678729399, 0.180678729399, 0.146555539499, + 0.137148574884, 0.180678729399, 0.180678729399, 0.180678729399 + ]) * standardization / 0.7 + + db = df.copy() + + for idx, acct in enumerate(ACCT_DIRECT): + mat_mult = (1 - float(mat_lr[idx])) ** np.log2(n_th) + lab_mult = (1 - float(lab_lr[idx])) ** np.log2(n_th) + + setv(db, acct, "Site Material Cost", float(getv(df, acct, "Site Material Cost")) * mat_mult) + setv(db, acct, "Site Labor Hours", float(getv(df, acct, "Site Labor Hours")) * lab_mult) + setv(db, acct, "Site Labor Cost", float(getv(df, acct, "Site Labor Cost")) * lab_mult) + + return update_high_level_costs(db, power)[COLS].copy() + + +def act_cons_duration_plus_delay( + reactor_type: str, + n_th: int, + Design_Maturity_0, + proc_exp_0, + N_proc, + cons_duration_no_delay: float +): + if n_th == 1: + Design_Maturity = Design_Maturity_0 + proc_exp = proc_exp_0 + else: + Design_Maturity = 2 + proc_exp = min(proc_exp_0 + (2 / N_proc) * (n_th - 1), 2) + + if reactor_type == "Concept B": + task_length_multiplier = 1.0 + ref_construction_duration = 64 + else: # Concept A + task_length_multiplier = 100 / 64 + ref_construction_duration = 100 + + B_21 = 42.1 * task_length_multiplier + B_22 = 60.2 * task_length_multiplier + B_23 = 14.8 * task_length_multiplier + B_24 = 3.6 * task_length_multiplier + B_25 = 10.1 * task_length_multiplier + B_26 = 43.9 * task_length_multiplier + + D = -6 * Design_Maturity - 3 * proc_exp + 18 + + T_21 = B_21 + D + T_22 = 0.09 * (B_21 + D) + B_22 + D + T_23 = 0.24 * (B_21 + D) + B_23 + D + T_24 = 0.24 * (B_21 + D) + 0.34 * (B_23 + D) + B_24 + D + T_25 = 0.18 * (B_21 + D) + B_25 + D + T_26 = 0.21 * (B_21 + D) + B_26 + D + + T_end = max(T_21, T_22, T_23, T_24, T_25, T_26) + + supply_chain_delay = max(T_end - ref_construction_duration, 0) + return float(cons_duration_no_delay) + float(supply_chain_delay) + + +def duration_learning_effect(n_th: int, standardization_0: float, actual_construction_duration_plus_delay: float): + standardization = min(0.7, standardization_0) if n_th == 1 else standardization_0 + fitted_LR_duration = 0.103719051 * standardization / 0.7 + duration_multiplier = (1 - fitted_LR_duration) ** np.log2(n_th) + return float(duration_multiplier) * float(actual_construction_duration_plus_delay) diff --git a/src/crf/model/pipeline.py b/src/crf/model/pipeline.py new file mode 100644 index 0000000..139b288 --- /dev/null +++ b/src/crf/model/pipeline.py @@ -0,0 +1,62 @@ +from .direct_cost import update_direct_cost +from .learning import learning_effect, act_cons_duration_plus_delay, duration_learning_effect +from .indirect_cost import update_indirect_cost +from .finance import insurance_cost_update, update_interest_cost, update_itc + +def calculate_final_result(config: dict, inp: dict, store, n_th: int): + """ + One unit (n_th) calculation. + Returns: (final_df, levelized_net_OCC, levelized_NCI, final_construction_duration_scalar) + """ + base0, power = store.get_baseline(config["reactor_type"]) + + # direct updates (returns df + duration without supply chain delay) + direct_df, dur_no_delay = update_direct_cost( + store=store, + reactor_type=config["reactor_type"], + n_th=n_th, + f_22=config["f_22"], + f_2321=config["f_2321"], + land_cost_per_acre_0=config["land_cost_per_acre_0"], + RB_grade_0=inp["RB_grade_0"], + BOP_grade_0=inp["BOP_grade_0"], + num_orders=inp["num_orders"], + design_completion_0=inp["design_completion_0"], + ae_exp_0=inp["ae_exp_0"], + N_AE=inp["N_AE"], + ce_exp_0=inp["ce_exp_0"], + N_cons=inp["N_cons"], + mod_0=inp["mod_0"], + ) + + # duration: add delay + learning + dur_plus_delay = act_cons_duration_plus_delay( + reactor_type=config["reactor_type"], + n_th=n_th, + Design_Maturity_0=inp["Design_Maturity_0"], + proc_exp_0=inp["proc_exp_0"], + N_proc=inp["N_proc"], + cons_duration_no_delay=dur_no_delay, + ) + final_dur = duration_learning_effect(n_th, inp["standardization_0"], dur_plus_delay) + + # learning on direct costs + direct_plus_learning = learning_effect(direct_df, n_th, inp["standardization_0"], power) + + # indirect costs + with_indirect = update_indirect_cost(n_th, inp["standardization_0"], direct_plus_learning, final_dur, power) + + # insurance + interest + ITC + with_insurance = insurance_cost_update(base0, with_indirect, power) + with_interest, tot_occ, tot_cap = update_interest_cost( + store=store, + df=with_insurance, + final_construction_duration=final_dur, + interest_rate=inp["interest_rate_0"], + startup_0=config["startup_0"], + n_th=n_th, + power=power, + ) + final_df, net_occ, nci = update_itc(with_interest, tot_occ, tot_cap, n_th, inp["ITC_0"], inp["n_ITC"], power) + + return final_df, net_occ, nci, float(final_dur) diff --git a/src/crf/sampling/lever_schema.py b/src/crf/sampling/lever_schema.py new file mode 100644 index 0000000..81b7df2 --- /dev/null +++ b/src/crf/sampling/lever_schema.py @@ -0,0 +1,28 @@ +LEVER_ORDER = [ + "num_orders", + "itc_percent", + "n_itc", + "interest_percent", + "design_completion_percent", + "design_maturity", + "proc_exp", + "N_proc", + "ce_exp", + "N_cons", + "ae_exp", + "N_AE", + "standardization_percent", + "modularity_code", + "bop_grade_code", + "rb_grade_code", +] + +STATIC_KEYS = [ + "Num_orders", "ITC", "n_ITC", "interest rate", "Design completion", "Design_Maturity_0", + "supply chain exp_0", "N supply chain", "Const Proficiency", "N const prof", "AE", + "N AE prof", "standardization", "modularity", "BOP commercial", "RB Safety Related", +] + +def unpack_sample_column(col_vec): + # col_vec length must match LEVER_ORDER + return dict(zip(LEVER_ORDER, col_vec)) diff --git a/src/crf/sampling/postprocess.py b/src/crf/sampling/postprocess.py new file mode 100644 index 0000000..0f21292 --- /dev/null +++ b/src/crf/sampling/postprocess.py @@ -0,0 +1,6 @@ +def closest(value, options): + return min(options, key=lambda x: abs(x - value)) + +def apply_itc_rounding(lever_dict, allowed=(6, 30, 40, 50)): + lever_dict["itc_percent"] = closest(lever_dict["itc_percent"], allowed) + return lever_dict diff --git a/src/crf/sampling/sampler.py b/src/crf/sampling/sampler.py new file mode 100644 index 0000000..7a080c6 --- /dev/null +++ b/src/crf/sampling/sampler.py @@ -0,0 +1,62 @@ +# src/crf/sampling/sampler.py +import numpy as np +import scipy.stats as stats + +def trunc_normal(min_, low_, med_, high_, max_, n): + std = (high_ - low_) / 4 + mean = med_ + a, b = (min_ - mean) / std, (max_ - mean) / std + return stats.truncnorm.rvs(a=a, b=b, loc=mean, scale=std, size=n) + +def truncate_shift_lognormal(min_, low_, med_, high_, max_, n): + std = (np.log(high_) - np.log(low_)) / 4 + mean = np.log(med_) + a, b = (np.log(min_) - mean) / std, (np.log(max_) - mean) / std + x = stats.truncnorm.rvs(a=a, b=b, loc=mean, scale=std, size=n) + return np.exp(x) + +def sample_levers(n_samples: int, levers_df, seed: int | None = None) -> np.ndarray: + if seed is not None: + np.random.seed(seed) + + n_var = levers_df.shape[0] + out = np.empty((n_var, n_samples), dtype=float) + + dist = levers_df["Distribution"] + min_ = levers_df["Min"]; max_ = levers_df["Max"]; med_ = levers_df["Median"] + low_ = levers_df["Low"]; high_ = levers_df["High"] + set_ = levers_df["Set"]; p_ = levers_df["Probabilities"] + typ = levers_df["Type"] + + for i in range(n_var): + d = str(dist.iloc[i]).lower() + + if d in ["normal", "gaussian"]: + out[i, :] = trunc_normal(min_.iloc[i], low_.iloc[i], med_.iloc[i], high_.iloc[i], max_.iloc[i], n_samples) + if str(typ.iloc[i]).lower() in ["discrete", "integer"]: + out[i, :] = np.round(out[i, :]) + + elif d in ["lognormal"]: + out[i, :] = truncate_shift_lognormal(min_.iloc[i], low_.iloc[i], med_.iloc[i], high_.iloc[i], max_.iloc[i], n_samples) + if str(typ.iloc[i]).lower() in ["discrete", "integer"]: + out[i, :] = np.round(out[i, :]) + + elif d in ["triangular"]: + out[i, :] = np.random.triangular(left=min_.iloc[i], mode=med_.iloc[i], right=max_.iloc[i], size=n_samples) + if str(typ.iloc[i]).lower() in ["discrete", "integer"]: + out[i, :] = np.round(out[i, :]) + + elif d in ["uniform"]: + out[i, :] = np.random.uniform(left=min_.iloc[i], right=max_.iloc[i], size=n_samples) + if str(typ.iloc[i]).lower() in ["discrete", "integer"]: + out[i, :] = np.round(out[i, :]) + + elif d in ["binary", "boolean", "set"]: + choices = eval("[" + str(set_.iloc[i]) + "]") + probs = eval("[" + str(p_.iloc[i]) + "]") + out[i, :] = np.random.choice(choices, size=n_samples, p=probs) + + else: + raise ValueError(f"Invalid Distribution='{dist.iloc[i]}' at row {i}") + + return out diff --git a/src/crf/types.py b/src/crf/types.py new file mode 100644 index 0000000..752f253 --- /dev/null +++ b/src/crf/types.py @@ -0,0 +1,42 @@ +# src/crf/types.py + +STATIC_KEYS = [ + "Num_orders", "ITC", "n_ITC", "interest rate", "Design completion", "Design_Maturity_0", + "supply chain exp_0", "N supply chain", "Const Proficiency", "N const prof", "AE", + "N AE prof", "standardization", "modularity", "BOP commercial", "RB Safety Related" +] + +SUMMARY_KEYS = [ + "cons_duration_cumulative_wz_startup", + "occLastUnit", "TCILastUnit", "durationsLastUnit", + "avg_OCC", "avg_TCI", "avg_duration", +] + +def build_headers(max_orders: int) -> list[str]: + return ( + STATIC_KEYS + + [f"OCC_{i}" for i in range(max_orders)] + + [f"TCI_{i}" for i in range(max_orders)] + + [f"duration_{i}" for i in range(max_orders)] + + SUMMARY_KEYS + ) + +def validate_config(config: dict): + required = { + "inputs_xlsx", "reactor_type", "f_22", "f_2321", + "land_cost_per_acre_0", "startup_0", "staggering_ratio", + } + missing = required - set(config) + if missing: + raise ValueError(f"Missing config keys: {sorted(missing)}") + +def validate_levers(levers: dict): + required = { + "num_orders", "itc_percent", "n_itc", "interest_percent", + "design_completion_percent", "design_maturity", "proc_exp", "N_proc", + "ce_exp", "N_cons", "ae_exp", "N_AE", + "standardization_percent", "modularity_code", "bop_grade_code", "rb_grade_code", + } + missing = required - set(levers) + if missing: + raise ValueError(f"Missing lever keys: {sorted(missing)}") diff --git a/src/crf/utils/df_ops.py b/src/crf/utils/df_ops.py new file mode 100644 index 0000000..da441ae --- /dev/null +++ b/src/crf/utils/df_ops.py @@ -0,0 +1,16 @@ +import numpy as np +import pandas as pd + +def getv(df: pd.DataFrame, account, col: str): + return df.loc[df["Account"].eq(account), col].iloc[0] + +def setv(df: pd.DataFrame, account, col: str, value): + df.loc[df["Account"].eq(account), col] = value + +def mulv(df: pd.DataFrame, accounts, cols, factor: float): + m = df["Account"].isin(accounts) + df.loc[m, cols] = df.loc[m, cols].to_numpy() * factor + +def resetv(df: pd.DataFrame, accounts, cols): + m = df["Account"].isin(accounts) + df.loc[m, cols] = np.nan diff --git a/src/crf/utils/serialize.py b/src/crf/utils/serialize.py new file mode 100644 index 0000000..726ddd7 --- /dev/null +++ b/src/crf/utils/serialize.py @@ -0,0 +1,8 @@ +import csv +import pickle + +def stream_pickle_dump(obj, file_handle): + pickle.dump(obj, file_handle) + +def write_csv_row(writer: csv.DictWriter, row: dict, headers: list[str]): + writer.writerow({h: row.get(h, None) for h in headers}) From b36ce97d00dec04a020cde780c4be6e53b8ebca9 Mon Sep 17 00:00:00 2001 From: "jia.zhou" <48254033+JiaZhou-PU@users.noreply.github.com> Date: Thu, 12 Feb 2026 16:21:18 -0600 Subject: [PATCH 02/25] first test pass --- .../CostReduction_exploration_mode.ipynb | 2 +- Cost_Reduction/scenarios.ipynb | 23 +-- Cost_Reduction/sensitivity_analysis.ipynb | 2 +- requirements.txt | 1 + src/__init__.py | 5 +- src/crf/__init__.py | 14 ++ src/crf/api.py | 44 +++--- src/crf/io/__init__.py | 12 ++ src/crf/io/excel_levers.py | 18 ++- src/crf/model/__init__.py | 7 + src/crf/sampling/__init__.py | 17 +++ src/crf/sampling/lever_schema.py | 139 +++++++++++++++--- src/crf/types.py | 34 ++--- src/crf/utils/__init__.py | 9 ++ test/test_crf_smoke.py | 52 +++++++ 15 files changed, 282 insertions(+), 97 deletions(-) create mode 100644 src/crf/__init__.py create mode 100644 src/crf/io/__init__.py create mode 100644 src/crf/model/__init__.py create mode 100644 src/crf/sampling/__init__.py create mode 100644 src/crf/utils/__init__.py create mode 100644 test/test_crf_smoke.py diff --git a/Cost_Reduction/CostReduction_exploration_mode.ipynb b/Cost_Reduction/CostReduction_exploration_mode.ipynb index f6800e6..1c2cb30 100644 --- a/Cost_Reduction/CostReduction_exploration_mode.ipynb +++ b/Cost_Reduction/CostReduction_exploration_mode.ipynb @@ -5734,7 +5734,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.8" + "version": "3.10.18" } }, "nbformat": 4, diff --git a/Cost_Reduction/scenarios.ipynb b/Cost_Reduction/scenarios.ipynb index 5bb5cfd..50f3a0e 100644 --- a/Cost_Reduction/scenarios.ipynb +++ b/Cost_Reduction/scenarios.ipynb @@ -44,6 +44,7 @@ "\n", "import warnings\n", "warnings.simplefilter(action='ignore', category=FutureWarning)\n", + "warnings.simplefilter(action='ignore', category=DeprecationWarning)\n", "\n", "pd.set_option('display.max_rows', None)" ] @@ -113,23 +114,7 @@ "execution_count": 4, "id": "6a4a23ef", "metadata": {}, - "outputs": [ - { - "ename": "UnboundLocalError", - "evalue": "cannot access local variable 'task_length_multiplier' where it is not associated with a value", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mUnboundLocalError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[4], line 57\u001b[0m\n\u001b[1;32m 54\u001b[0m ITC_0 \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m0.4\u001b[39m\n\u001b[1;32m 55\u001b[0m n_ITC \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m4\u001b[39m \n\u001b[0;32m---> 57\u001b[0m avg_results \u001b[38;5;241m=\u001b[39m \u001b[43mcalculate_final_result_avg\u001b[49m\u001b[43m(\u001b[49m\u001b[43mreactor_type\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mf_22\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mf_2321\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mland_cost_per_acre_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mRB_grade_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mBOP_grade_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m\\\u001b[49m\n\u001b[1;32m 58\u001b[0m \u001b[43m\u001b[49m\u001b[43mnum_orders\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdesign_completion_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mae_exp_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mN_AE\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mce_exp_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mN_cons\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmod_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mDesign_Maturity_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mproc_exp_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mN_proc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstandardization_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43minterest_rate_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstartup_0\u001b[49m\u001b[43m)\u001b[49m \n", - "Cell \u001b[0;32mIn[3], line 11\u001b[0m, in \u001b[0;36mcalculate_final_result_avg\u001b[0;34m(reactor_type, f_22, f_2321, land_cost_per_acre_0, RB_grade_0, BOP_grade_0, num_orders, design_completion_0, ae_exp_0, N_AE, ce_exp_0, N_cons, mod_0, Design_Maturity_0, proc_exp_0, N_proc, standardization_0, interest_rate_0, startup_0)\u001b[0m\n\u001b[1;32m 8\u001b[0m durations_list \u001b[38;5;241m=\u001b[39m []\n\u001b[1;32m 10\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m n_th \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mrange\u001b[39m(\u001b[38;5;241m1\u001b[39m, num_orders\u001b[38;5;241m+\u001b[39m\u001b[38;5;241m1\u001b[39m):\n\u001b[0;32m---> 11\u001b[0m results \u001b[38;5;241m=\u001b[39m \u001b[43mcalculate_final_result\u001b[49m\u001b[43m(\u001b[49m\u001b[43mreactor_type\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mn_th\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mf_22\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mf_2321\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mland_cost_per_acre_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mRB_grade_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mBOP_grade_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m\\\u001b[49m\n\u001b[1;32m 12\u001b[0m \u001b[43m \u001b[49m\u001b[43mnum_orders\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdesign_completion_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mae_exp_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mN_AE\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mce_exp_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mN_cons\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmod_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mDesign_Maturity_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mproc_exp_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mN_proc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstandardization_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43minterest_rate_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstartup_0\u001b[49m\u001b[43m)\u001b[49m \n\u001b[1;32m 14\u001b[0m OCC_result \u001b[38;5;241m=\u001b[39m results[\u001b[38;5;241m1\u001b[39m] \u001b[38;5;66;03m# The ITC_reduced OCC (levelized)\u001b[39;00m\n\u001b[1;32m 15\u001b[0m TCI_result \u001b[38;5;241m=\u001b[39m results[\u001b[38;5;241m2\u001b[39m] \u001b[38;5;66;03m# The ITC_reduced TCI(levelized)\u001b[39;00m\n", - "File \u001b[0;32m/var/folders/fn/9991pz_174vgdscjw2zf2tbxtn5546/T/ipykernel_63785/3212539402.py:6\u001b[0m, in \u001b[0;36mcalculate_final_result\u001b[0;34m(reactor_type, n_th, f_22, f_2321, land_cost_per_acre_0, RB_grade_0, BOP_grade_0, num_orders, design_completion_0, ae_exp_0, N_AE, ce_exp_0, N_cons, mod_0, Design_Maturity_0, proc_exp_0, N_proc, standardization_0, interest_rate_0, startup_0)\u001b[0m\n\u001b[1;32m 4\u001b[0m reactor_data \u001b[38;5;241m=\u001b[39m (reactor_data_read(reactor_type ))[\u001b[38;5;241m0\u001b[39m]\n\u001b[1;32m 5\u001b[0m reactor_power \u001b[38;5;241m=\u001b[39m (reactor_data_read(reactor_type ))[\u001b[38;5;241m1\u001b[39m]\n\u001b[0;32m----> 6\u001b[0m tot_base_cost_results \u001b[38;5;241m=\u001b[39m \u001b[43mcalculate_base_cost\u001b[49m\u001b[43m(\u001b[49m\u001b[43mreactor_type\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mn_th\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mf_22\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mf_2321\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mland_cost_per_acre_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mRB_grade_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mBOP_grade_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnum_orders\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdesign_completion_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mae_exp_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mN_AE\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mce_exp_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mN_cons\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmod_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mDesign_Maturity_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mproc_exp_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mN_proc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstandardization_0\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 7\u001b[0m tot_base_cost \u001b[38;5;241m=\u001b[39m tot_base_cost_results[\u001b[38;5;241m0\u001b[39m]\n\u001b[1;32m 8\u001b[0m final_construction_duration \u001b[38;5;241m=\u001b[39m tot_base_cost_results[\u001b[38;5;241m1\u001b[39m]\n", - "File \u001b[0;32m/var/folders/fn/9991pz_174vgdscjw2zf2tbxtn5546/T/ipykernel_63785/896935451.py:7\u001b[0m, in \u001b[0;36mcalculate_base_cost\u001b[0;34m(reactor_type, n_th, f_22, f_2321, land_cost_per_acre_0, RB_grade_0, BOP_grade_0, num_orders, design_completion_0, ae_exp_0, N_AE, ce_exp_0, N_cons, mod_0, Design_Maturity_0, proc_exp_0, N_proc, standardization_0)\u001b[0m\n\u001b[1;32m 4\u001b[0m direct_cost_updated \u001b[38;5;241m=\u001b[39m update_direct_cost(reactor_type, n_th, f_22, f_2321, land_cost_per_acre_0, RB_grade_0, BOP_grade_0, num_orders, design_completion_0, ae_exp_0, N_AE, ce_exp_0, N_cons)\n\u001b[1;32m 6\u001b[0m act_con_duration \u001b[38;5;241m=\u001b[39m update_cons_dur(reactor_data, direct_cost_updated , mod_0)\n\u001b[0;32m----> 7\u001b[0m cons_duration_plus_delay \u001b[38;5;241m=\u001b[39m \u001b[43mact_cons_duration_plus_delay\u001b[49m\u001b[43m(\u001b[49m\u001b[43mreactor_type\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mn_th\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mDesign_Maturity_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mproc_exp_0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mN_proc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mact_con_duration\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 8\u001b[0m final_con_duration \u001b[38;5;241m=\u001b[39m duration_learning_effect(n_th, standardization_0, cons_duration_plus_delay)\n\u001b[1;32m 10\u001b[0m direct_cost_updated_plus_learning \u001b[38;5;241m=\u001b[39m learning_effect(direct_cost_updated , n_th, standardization_0, reactor_power)\n", - "File \u001b[0;32m/var/folders/fn/9991pz_174vgdscjw2zf2tbxtn5546/T/ipykernel_63785/1160249579.py:25\u001b[0m, in \u001b[0;36mact_cons_duration_plus_delay\u001b[0;34m(reactor_type, n_th, Design_Maturity_0, proc_exp_0, N_proc, cons_duration_no_delay)\u001b[0m\n\u001b[1;32m 22\u001b[0m task_length_multiplier \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m100\u001b[39m\u001b[38;5;241m/\u001b[39m\u001b[38;5;241m64\u001b[39m\n\u001b[1;32m 23\u001b[0m ref_construction_duration \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m100\u001b[39m\n\u001b[0;32m---> 25\u001b[0m B_21 \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m42.1\u001b[39m \u001b[38;5;241m*\u001b[39m \u001b[43mtask_length_multiplier\u001b[49m \u001b[38;5;66;03m# months \u001b[39;00m\n\u001b[1;32m 26\u001b[0m B_22 \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m60.2\u001b[39m \u001b[38;5;241m*\u001b[39m task_length_multiplier\n\u001b[1;32m 27\u001b[0m B_23 \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m14.8\u001b[39m \u001b[38;5;241m*\u001b[39m task_length_multiplier\n", - "\u001b[0;31mUnboundLocalError\u001b[0m: cannot access local variable 'task_length_multiplier' where it is not associated with a value" - ] - } - ], + "outputs": [], "source": [ "reactor_type = \"Concept A\"\n", "\n", @@ -193,7 +178,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "8187e003", "metadata": {}, "outputs": [], @@ -219,7 +204,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.8" + "version": "3.10.18" } }, "nbformat": 4, diff --git a/Cost_Reduction/sensitivity_analysis.ipynb b/Cost_Reduction/sensitivity_analysis.ipynb index 7c9c070..22b49e4 100644 --- a/Cost_Reduction/sensitivity_analysis.ipynb +++ b/Cost_Reduction/sensitivity_analysis.ipynb @@ -943,7 +943,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.1.undefined" + "version": "3.10.18" } }, "nbformat": 4, diff --git a/requirements.txt b/requirements.txt index 7a996e3..1b979ca 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,4 @@ pytest pandas numpy==1.26.4 matplotlib +scipy diff --git a/src/__init__.py b/src/__init__.py index c229391..f2e8526 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -5,5 +5,8 @@ from .utility_accert import Utility_methods from .Algorithm import Algorithm import necost +from .crf import run_one_scenario, run_sampling_from_excel -__all__ = ['Accert', 'xml2obj', 'Utility_methods', 'Algorithm', 'necost'] + +__all__ = ['Accert', 'xml2obj', 'Utility_methods', 'Algorithm', + 'necost', "run_one_scenario", "run_sampling_from_excel"] diff --git a/src/crf/__init__.py b/src/crf/__init__.py new file mode 100644 index 0000000..38163fc --- /dev/null +++ b/src/crf/__init__.py @@ -0,0 +1,14 @@ +""" +Cost Reduction Framework (CRF) +ACCERT Submodule +""" + +from .api import ( + run_one_scenario, + run_sampling_from_excel, +) + +__all__ = [ + "run_one_scenario", + "run_sampling_from_excel", +] diff --git a/src/crf/api.py b/src/crf/api.py index 5723ea4..0d4a147 100644 --- a/src/crf/api.py +++ b/src/crf/api.py @@ -3,9 +3,14 @@ import numpy as np from .io.excel_inputs import InputStore -from .io.excel_levers import read_lever_sheet +from .io.excel_levers import read_levers_sheet from .sampling.sampler import sample_levers -from .sampling.lever_schema import unpack_sample_column, STATIC_KEYS +from .sampling.lever_schema import ( + STATIC_KEYS, + attach_internal_ids, + sample_column_to_levers, + static_row_from_levers, +) from .sampling.postprocess import apply_itc_rounding from .model.avg_runner import run_avg_all_units from .utils.serialize import write_csv_row, stream_pickle_dump @@ -44,28 +49,7 @@ def run_one_scenario(config: dict, levers: dict) -> dict: inp = normalize_levers(levers) result = run_avg_all_units(config=config, inp=inp, store=store) - - # attach static input columns (raw sampled values, matching your previous CSV meaning) - # IMPORTANT: keep the same static key ordering as before - static_vals = { - "Num_orders": levers["num_orders"], - "ITC": levers["itc_percent"], - "n_ITC": levers["n_itc"], - "interest rate": levers["interest_percent"], - "Design completion": levers["design_completion_percent"], - "Design_Maturity_0": levers["design_maturity"], - "supply chain exp_0": levers["proc_exp"], - "N supply chain": levers["N_proc"], - "Const Proficiency": levers["ce_exp"], - "N const prof": levers["N_cons"], - "AE": levers["ae_exp"], - "N AE prof": levers["N_AE"], - "standardization": levers["standardization_percent"], - "modularity": levers["modularity_code"], - "BOP commercial": levers["bop_grade_code"], - "RB Safety Related": levers["rb_grade_code"], - } - + static_vals = static_row_from_levers(levers) return {**static_vals, **result} @@ -77,7 +61,9 @@ def run_sampling_from_excel( out_pkl: str, seed: int | None = None ): - levers_df = read_lever_sheet(levers_xlsx, sheet_name="Levers") + levers_df = read_levers_sheet(levers_xlsx, sheet_name="Levers") + levers_df = attach_internal_ids(levers_df) + samples = sample_levers(n_samples, levers_df, seed=seed) # shape (n_levers, n_samples) # headers: build from max num_orders in sampled set @@ -100,8 +86,12 @@ def run_sampling_from_excel( writer.writeheader() for i in range(n_samples): - levers = unpack_sample_column(samples[:, i]) - row = run_one_scenario(config, levers) + # levers = unpack_sample_column(samples[:, i]) + # row = run_one_scenario(config, levers) + + + levers_raw = sample_column_to_levers(samples, levers_df, i) + row = run_one_scenario(config, levers_raw) write_csv_row(writer, row, headers) stream_pickle_dump(row, pkl_f) diff --git a/src/crf/io/__init__.py b/src/crf/io/__init__.py new file mode 100644 index 0000000..06cfa5f --- /dev/null +++ b/src/crf/io/__init__.py @@ -0,0 +1,12 @@ +""" +CRF Excel I/O +""" + +from .excel_inputs import InputStore +from .excel_levers import read_levers_sheet, read_baseline_levers + +__all__ = [ + "InputStore", + "read_levers_sheet", + "read_baseline_levers", +] diff --git a/src/crf/io/excel_levers.py b/src/crf/io/excel_levers.py index d17d281..53b97f0 100644 --- a/src/crf/io/excel_levers.py +++ b/src/crf/io/excel_levers.py @@ -1,13 +1,25 @@ import pandas as pd REQUIRED_COLS = { - "Distribution", "Min", "Max", "Median", "Low", "High", - "Set", "Probabilities", "Type" + "Levers", "Min", "Low", "Median", "High", "Max", + "Distribution", "Type", "Set", "Probabilities" } -def read_lever_sheet(levers_xlsx: str, sheet_name="Levers") -> pd.DataFrame: +def read_levers_sheet(levers_xlsx: str, sheet_name="Levers") -> pd.DataFrame: df = pd.read_excel(levers_xlsx, sheet_name=sheet_name) + missing = REQUIRED_COLS - set(df.columns) if missing: raise ValueError(f"Levers sheet missing columns: {sorted(missing)}") + + return df + +def read_baseline_levers(levers_xlsx: str, sheet_name="baseline levers") -> pd.DataFrame: + """ + Returns the baseline levers sheet as a dataframe with columns: Levers, Baseline + Keep it as df for now (you can convert to dict later). + """ + df = pd.read_excel(levers_xlsx, sheet_name=sheet_name) + if "Levers" not in df.columns or "Baseline" not in df.columns: + raise ValueError("Baseline levers sheet must have columns: 'Levers' and 'Baseline'") return df diff --git a/src/crf/model/__init__.py b/src/crf/model/__init__.py new file mode 100644 index 0000000..3d91ef3 --- /dev/null +++ b/src/crf/model/__init__.py @@ -0,0 +1,7 @@ +""" +CRF Model Core +""" + +from .pipeline import calculate_final_result + +__all__ = ["calculate_final_result"] diff --git a/src/crf/sampling/__init__.py b/src/crf/sampling/__init__.py new file mode 100644 index 0000000..7c03971 --- /dev/null +++ b/src/crf/sampling/__init__.py @@ -0,0 +1,17 @@ +""" +CRF Sampling Module +""" + +from .sampler import sample_levers +from .lever_schema import ( + attach_internal_ids, + sample_column_to_levers, + static_row_from_levers, +) + +__all__ = [ + "sample_levers", + "attach_internal_ids", + "sample_column_to_levers", + "static_row_from_levers", +] diff --git a/src/crf/sampling/lever_schema.py b/src/crf/sampling/lever_schema.py index 81b7df2..e82630f 100644 --- a/src/crf/sampling/lever_schema.py +++ b/src/crf/sampling/lever_schema.py @@ -1,28 +1,121 @@ -LEVER_ORDER = [ - "num_orders", - "itc_percent", - "n_itc", - "interest_percent", - "design_completion_percent", - "design_maturity", - "proc_exp", - "N_proc", - "ce_exp", - "N_cons", - "ae_exp", - "N_AE", - "standardization_percent", - "modularity_code", - "bop_grade_code", - "rb_grade_code", +import pandas as pd +# --- Unique internal IDs (these are the keys everywhere else) --- +LEVER_IDS = [ + "num_orders", + "itc_percent", + "n_itc", + "interest_percent", + "design_completion_percent", + "design_maturity", + "proc_exp", + "N_proc", + "ce_exp", + "N_cons", + "ae_exp", + "N_AE", + "standardization_percent", + "modularity_code", + "bop_grade_code", + "rb_grade_code", +] + +# --- Map Excel lever names to internal IDs --- +EXCEL_NAME_TO_ID_ORDERED = [ + ("Number of firm orders", "num_orders"), + ("ITC amount", "itc_percent"), + ("Number of plants claiming ITC", "n_itc"), + ("Interest rate", "interest_percent"), + ("Design completion", "design_completion_percent"), + ("Design maturity (technology maturity)", "design_maturity"), + ("Supply chain proficiency", "proc_exp"), + ("Number of plants to achieve best proficiency", "N_proc"), # first occurrence + ("Construction proficiency", "ce_exp"), + ("Number of plants to achieve best proficiency", "N_cons"), # second occurrence + ("A/E proficiency", "ae_exp"), + ("Number of plants to achieve best proficiency", "N_AE"), # third occurrence + ("Cross-site standardization", "standardization_percent"), + ("Modular civil construction", "modularity_code"), + ("Commercial BOP", "bop_grade_code"), + ("Non-safety-related RB", "rb_grade_code"), ] STATIC_KEYS = [ - "Num_orders", "ITC", "n_ITC", "interest rate", "Design completion", "Design_Maturity_0", - "supply chain exp_0", "N supply chain", "Const Proficiency", "N const prof", "AE", - "N AE prof", "standardization", "modularity", "BOP commercial", "RB Safety Related", + "Num_orders", "ITC", "n_ITC", "interest rate", "Design completion", "Design_Maturity_0", + "supply chain exp_0", "N supply chain", "Const Proficiency", "N const prof", "AE", + "N AE prof", "standardization", "modularity", "BOP commercial", "RB Safety Related", ] -def unpack_sample_column(col_vec): - # col_vec length must match LEVER_ORDER - return dict(zip(LEVER_ORDER, col_vec)) + +def _normalize_excel_name(s: str) -> str: + # make matching robust to extra spaces + return " ".join(str(s).strip().split()) + + +def attach_internal_ids(levers_df: pd.DataFrame) -> pd.DataFrame: + """ + Adds a 'lever_id' column to the Levers dataframe using ordered matching. + This is required because Excel contains repeated lever names. + """ + df = levers_df.copy() + df["Levers_norm"] = df["Levers"].apply(_normalize_excel_name) + + ordered = [( _normalize_excel_name(n), i ) for n, i in EXCEL_NAME_TO_ID_ORDERED] + + ids = [] + cursor = 0 + for row_name in df["Levers_norm"].tolist(): + if cursor >= len(ordered): + raise ValueError("Levers sheet has more rows than expected mapping.") + + expected_name, lever_id = ordered[cursor] + if row_name != expected_name: + raise ValueError( + f"Levers sheet row mismatch at position {cursor}:\n" + f" expected: '{expected_name}'\n" + f" found: '{row_name}'\n" + f"Fix the Excel row order OR update EXCEL_NAME_TO_ID_ORDERED." + ) + ids.append(lever_id) + cursor += 1 + + df["lever_id"] = ids + df.drop(columns=["Levers_norm"], inplace=True) + + # final validation + if df["lever_id"].tolist() != LEVER_IDS: + raise ValueError("Internal lever_id order does not match LEVER_IDS. Check mapping.") + return df + + +def sample_column_to_levers(samples_matrix, levers_df_with_ids: pd.DataFrame, sample_idx: int) -> dict: + """ + samples_matrix shape: (n_levers, n_samples) + returns levers_raw dict keyed by internal lever ids. + """ + vec = samples_matrix[:, sample_idx] + ids = levers_df_with_ids["lever_id"].tolist() + return {ids[i]: vec[i] for i in range(len(ids))} + + +def static_row_from_levers(levers_raw: dict) -> dict: + """ + Convert internal lever dict into your legacy CSV static columns. + """ + return { + "Num_orders": levers_raw["num_orders"], + "ITC": levers_raw["itc_percent"], + "n_ITC": levers_raw["n_itc"], + "interest rate": levers_raw["interest_percent"], + "Design completion": levers_raw["design_completion_percent"], + "Design_Maturity_0": levers_raw["design_maturity"], + "supply chain exp_0": levers_raw["proc_exp"], + "N supply chain": levers_raw["N_proc"], + "Const Proficiency": levers_raw["ce_exp"], + "N const prof": levers_raw["N_cons"], + "AE": levers_raw["ae_exp"], + "N AE prof": levers_raw["N_AE"], + "standardization": levers_raw["standardization_percent"], + "modularity": levers_raw["modularity_code"], + "BOP commercial": levers_raw["bop_grade_code"], + "RB Safety Related": levers_raw["rb_grade_code"], + } diff --git a/src/crf/types.py b/src/crf/types.py index 752f253..00f9f7a 100644 --- a/src/crf/types.py +++ b/src/crf/types.py @@ -1,15 +1,13 @@ -# src/crf/types.py - STATIC_KEYS = [ - "Num_orders", "ITC", "n_ITC", "interest rate", "Design completion", "Design_Maturity_0", - "supply chain exp_0", "N supply chain", "Const Proficiency", "N const prof", "AE", - "N AE prof", "standardization", "modularity", "BOP commercial", "RB Safety Related" + "Num_orders", "ITC", "n_ITC", "interest rate", "Design completion", "Design_Maturity_0", + "supply chain exp_0", "N supply chain", "Const Proficiency", "N const prof", "AE", + "N AE prof", "standardization", "modularity", "BOP commercial", "RB Safety Related", ] SUMMARY_KEYS = [ - "cons_duration_cumulative_wz_startup", - "occLastUnit", "TCILastUnit", "durationsLastUnit", - "avg_OCC", "avg_TCI", "avg_duration", + "cons_duration_cumulative_wz_startup", + "occLastUnit", "TCILastUnit", "durationsLastUnit", + "avg_OCC", "avg_TCI", "avg_duration", ] def build_headers(max_orders: int) -> list[str]: @@ -23,20 +21,12 @@ def build_headers(max_orders: int) -> list[str]: def validate_config(config: dict): required = { - "inputs_xlsx", "reactor_type", "f_22", "f_2321", - "land_cost_per_acre_0", "startup_0", "staggering_ratio", + "inputs_xlsx", "reactor_type", + "f_22", "f_2321", + "land_cost_per_acre_0", + "startup_0", + "staggering_ratio", } missing = required - set(config) if missing: - raise ValueError(f"Missing config keys: {sorted(missing)}") - -def validate_levers(levers: dict): - required = { - "num_orders", "itc_percent", "n_itc", "interest_percent", - "design_completion_percent", "design_maturity", "proc_exp", "N_proc", - "ce_exp", "N_cons", "ae_exp", "N_AE", - "standardization_percent", "modularity_code", "bop_grade_code", "rb_grade_code", - } - missing = required - set(levers) - if missing: - raise ValueError(f"Missing lever keys: {sorted(missing)}") + raise ValueError(f"Missing config keys: {sorted(missing)}") \ No newline at end of file diff --git a/src/crf/utils/__init__.py b/src/crf/utils/__init__.py new file mode 100644 index 0000000..d4ea010 --- /dev/null +++ b/src/crf/utils/__init__.py @@ -0,0 +1,9 @@ +""" +CRF Utilities +""" + +from .df_ops import getv, setv, mulv, resetv + +__all__ = ["getv", "setv", "mulv", "resetv"] + + diff --git a/test/test_crf_smoke.py b/test/test_crf_smoke.py new file mode 100644 index 0000000..4306003 --- /dev/null +++ b/test/test_crf_smoke.py @@ -0,0 +1,52 @@ + + +import os +import sys + +src_path = os.path.abspath(os.path.join(os.pardir, 'src')) +sys.path.insert(0, src_path) +from crf import run_one_scenario + + +def main(): + + config = { + "inputs_xlsx": "Inputs.xlsx", + "reactor_type": "Concept A", + "f_22": 250_000_000, + "f_2321": 150_000_000, + "land_cost_per_acre_0": 22000, + "startup_0": 16, + "staggering_ratio": 0.75, + } + + # baseline-style levers (raw form, like from Excel) + levers = { + "num_orders": 3, + "itc_percent": 30, + "n_itc": 1, + "interest_percent": 6.5, + "design_completion_percent": 90, + "design_maturity": 2, + "proc_exp": 0.6, + "N_proc": 3, + "ce_exp": 0.6, + "N_cons": 5, + "ae_exp": 0.6, + "N_AE": 4, + "standardization_percent": 70, + "modularity_code": 1, + "bop_grade_code": 1, + "rb_grade_code": 0, + } + + result = run_one_scenario(config, levers) + + print("\nTest Successful") + print("avg_OCC:", result["avg_OCC"]) + print("avg_TCI:", result["avg_TCI"]) + print("avg_duration:", result["avg_duration"]) + + +if __name__ == "__main__": + main() From 0e7239434ab9eb5068d3c6a94eb3600dd2dbf5fe Mon Sep 17 00:00:00 2001 From: "jia.zhou" <48254033+JiaZhou-PU@users.noreply.github.com> Date: Tue, 17 Feb 2026 14:58:28 -0600 Subject: [PATCH 03/25] simplified input --- src/crf/api.py | 98 ++++++++++++++- src/crf/data/HTGR_baseline.csv | 34 +++++ src/crf/data/SFR_baseline.csv | 70 +++++++++++ src/crf/data/ref_spending_curve.csv | 105 ++++++++++++++++ src/crf/io/excel_inputs.py | 37 ++++-- src/crf/model/core_accounts.py | 187 +++++++++++++++++++--------- src/crf/model/direct_cost.py | 2 +- src/crf/model/finance.py | 30 +++-- src/crf/model/indirect_cost.py | 10 +- src/crf/utils/df_ops.py | 43 +++++-- test/test_crf_smoke.py | 24 ++-- 11 files changed, 545 insertions(+), 95 deletions(-) create mode 100644 src/crf/data/HTGR_baseline.csv create mode 100644 src/crf/data/SFR_baseline.csv create mode 100644 src/crf/data/ref_spending_curve.csv diff --git a/src/crf/api.py b/src/crf/api.py index 0d4a147..db91a33 100644 --- a/src/crf/api.py +++ b/src/crf/api.py @@ -16,6 +16,51 @@ from .utils.serialize import write_csv_row, stream_pickle_dump +def normalize_levers(levers: dict) -> dict: + """Convert raw lever dict to model-ready normalized values.""" + def label01(x, zero_label, one_label): + if x == 0: return zero_label + if x == 1: return one_label + return x + + return { + "num_orders": int(levers["num_orders"]), + "ITC_0": float(levers["itc_percent"]) / 100.0, + "n_ITC": levers["n_itc"], + "interest_rate_0": float(levers["interest_percent"]) / 100.0, + "design_completion_0": float(levers["design_completion_percent"]) / 100.0, + "Design_Maturity_0": levers["design_maturity"], + "proc_exp_0": levers["proc_exp"], + "N_proc": levers["N_proc"], + "ce_exp_0": levers["ce_exp"], + "N_cons": levers["N_cons"], + "ae_exp_0": levers["ae_exp"], + "N_AE": levers["N_AE"], + "standardization_0": float(levers["standardization_percent"]) / 100.0, + "mod_0": label01(levers["modularity_code"], "stick_built", "modularized"), + "BOP_grade_0": label01(levers["bop_grade_code"], "nuclear", "non_nuclear"), + "RB_grade_0": label01(levers["rb_grade_code"], "nuclear", "non_nuclear"), + } + + +import csv +import pickle +import numpy as np + +from .io.excel_inputs import InputStore +from .io.excel_levers import read_levers_sheet +from .sampling.sampler import sample_levers +from .sampling.lever_schema import ( + STATIC_KEYS, + attach_internal_ids, + sample_column_to_levers, + static_row_from_levers, +) +from .sampling.postprocess import apply_itc_rounding +from .model.avg_runner import run_avg_all_units +from .utils.serialize import write_csv_row, stream_pickle_dump + + def normalize_levers(levers: dict) -> dict: """Convert raw lever dict to model-ready normalized values.""" def label01(x, zero_label, one_label): @@ -44,7 +89,58 @@ def label01(x, zero_label, one_label): def run_one_scenario(config: dict, levers: dict) -> dict: - store = InputStore(config["inputs_xlsx"]) + store = InputStore(config.get("data_dir")) + levers = apply_itc_rounding(levers) + inp = normalize_levers(levers) + + result = run_avg_all_units(config=config, inp=inp, store=store) + static_vals = static_row_from_levers(levers) + return {**static_vals, **result} + + +def run_sampling_from_excel( + config: dict, + levers_xlsx: str, + n_samples: int, + out_csv: str, + out_pkl: str, + seed: int | None = None +): + levers_df = read_levers_sheet(levers_xlsx, sheet_name="Levers") + levers_df = attach_internal_ids(levers_df) + + samples = sample_levers(n_samples, levers_df, seed=seed) # shape (n_levers, n_samples) + + # headers: build from max num_orders in sampled set + max_orders = int(np.max(samples[0, :])) + + headers = ( + STATIC_KEYS + + [f"OCC_{i}" for i in range(max_orders)] + + [f"TCI_{i}" for i in range(max_orders)] + + [f"duration_{i}" for i in range(max_orders)] + + [ + "cons_duration_cumulative_wz_startup", + "occLastUnit", "TCILastUnit", "durationsLastUnit", + "avg_OCC", "avg_TCI", "avg_duration", + ] + ) + + with open(out_csv, "w", newline="", encoding="utf-8") as csv_f, open(out_pkl, "wb") as pkl_f: + writer = csv.DictWriter(csv_f, fieldnames=headers) + writer.writeheader() + + for i in range(n_samples): + # levers = unpack_sample_column(samples[:, i]) + # row = run_one_scenario(config, levers) + + + levers_raw = sample_column_to_levers(samples, levers_df, i) + row = run_one_scenario(config, levers_raw) + + write_csv_row(writer, row, headers) + stream_pickle_dump(row, pkl_f) + levers = apply_itc_rounding(levers) inp = normalize_levers(levers) diff --git a/src/crf/data/HTGR_baseline.csv b/src/crf/data/HTGR_baseline.csv new file mode 100644 index 0000000..08bcf24 --- /dev/null +++ b/src/crf/data/HTGR_baseline.csv @@ -0,0 +1,34 @@ +Account,Title,Total Cost (USD),Factory Equipment Cost,Site Labor Hours,Site Labor Cost,Site Material Cost +10,Capitalized Pre-Construction Costs,,,,, +11,Land & Land Rights,15000000,,,, +12,Site Permits,0,,,, +13,Plant Licensing,107009771.98697068,,,, +14,Plant Permits,4721019.352366353,,,, +15,Plant Studies,0,,,, +16,Plant Reports,0,,,, +18,Other Pre-Construction Costs,36194481.701475374,,,, +20,Capitalized Direct Costs,,,,, +21,Structures & Improvements,914902741.8258162,90263521.11,11824706.17,591583080.9,233056139.8 +212,Reactor Containment Building,413396747.7312481,65728880.25,5420558.48,264358228.1,83309639.36 +213,Turbine Room and Heater Bay,15257054.820128009,1136504.843,163947.4218,8458991.226,5661558.752 +211 plus 214 to 219,Othe Structures & Improvements,486248939.27444005,23398136.024113387,6240200.264892557,318765861.5921357,144084941.65819097 +22,Reactor System,1813536678,1530752484,4903887.695,261886915.4,20897278.33 +23,Energy Conversion System,665574715.5139421,534245006.41216296,2364645.320437796,125502354.2820378,5827354.82 +232.1,Electricity Generation Systems,576019409.4786329,486396295.4,1682189.488,89623114.06,0 +233,Ultimate Heat Sink,89555306.03530921,47848710.99,682455.832,35879240.23,5827354.82 +24,Electrical Equipment,140508400.4910087,28461247.42,1597897.041,86126547.25,25920605.82 +25,Initial fuel inventory,451686470.5748004,,0,0,0 +26,Miscellaneous Equipment,214388490.9241457,65401342.47,2408453.335,129937709.9,19049438.52 +28,Simulator,0,,,, +30,Capitalized Indirect Services Costs,,,,, +31,Factory & Field Indirect Costs,691371059.4821017,,,, +32,Factory and construction supervision,2734393138.1498117,,,, +33,Start-Up Costs,115036083.38555087,,,, +34,Shipping & Transportation Costs,9686167.142,,,, +35,Engineering Services,738767482.8,,,, +50,Capitalized Supplementary Costs,,,,, +51,Taxes,187500,,,, +52,Insurance,38204331.42647941,,,, +54,Decommissioning ,8141760,,,, +60,Capitalized Financial Costs,,,,, +62,Interest ,3202161760.899656,,,, \ No newline at end of file diff --git a/src/crf/data/SFR_baseline.csv b/src/crf/data/SFR_baseline.csv new file mode 100644 index 0000000..e8e39b4 --- /dev/null +++ b/src/crf/data/SFR_baseline.csv @@ -0,0 +1,70 @@ +Account,Title,Total Cost (USD),Factory Equipment Cost,Site Labor Hours,Site Labor Cost,Site Material Cost +10,Capitalized Pre-Construction Costs,,,,, +11,Land & Land Rights,11000000.0,,,, +12,Site Permits,1598890.7521620637,,,, +13,Plant Licensing,24382988.437500004,,,, +14,Plant Permits,12679166.666666668,,,, +15,Plant Studies,12679166.666666668,,,, +16,Plant Reports,3972185.8507187515,,,, +18,Other Pre-Construction Costs,12679166.666666668,,,, +,,,,,, +,10s - Subtotal,78991565.04038084,,,, +,10s - $/kWe,254.15561467304,,,, +,,,,,, +20,Capitalized Direct Costs,,,,, +21,Structures & Improvements,244678343.4084428,64350800.8593666,2899406.53716477,145691461.90599,34636080.6430862 +212,Reactor Containment Building,145108927.86932483,57583605.3695275,1605670.3085077,81082169.3410215,6443153.15877585 +213,Turbine Room and Heater Bay,9518417.644642511,620963.974754291,102399.473629672,5280509.16508102,3616944.5048072 +211 plus 214 to 219,Othe Structures & Improvements,90050997.89447546,6146231.515084816,1191336.755027398,59328783.39988749,24575982.97950315 +22,Reactor System,661334796.7533815,559862843.635677,1809703.51462689,97213738.5700561,4258214.54764847 +23,Energy Conversion System,238844021.25290728,189802947.4112764,874095.8194883969,46348086.0900651,2692987.75156579 +232.1,Electricity Generation Systems,197558120.73250762,167738159.268431,559708.577958467,29819961.4640766,0.0 +233,Ultimate Heat Sink,41285900.52039969,22064788.1428454,314387.24152993,16528124.6259885,2692987.75156579 +24,Electrical Equipment,67451644.91245879,13662940.7771874,767077.153200817,41345408.9752344,12443295.160037 +25,Initial fuel inventory,279724434.0,,,, +26,Miscellaneous Equipment,25655315.754125603,8372438.25664057,278805.251378048,15110212.4141026,2172665.08338243 +28,Simulator,78200.0,,,, +,,,,,, +,20s - Subtotal,1517766756.081316,,,, +,20s - $/kWe,4883.419421111054,,,, +,,,,,, +30,Capitalized Indirect Services Costs,,,,, +31,Factory & Field Indirect Costs,146051996.53692532,,,, +32,Factory and construction supervision,506256124.80995834,,,, +33,Start-Up Costs,21298225.546122435,,,, +34,Shipping & Transportation Costs,1793334.4599472817,,,, +35,Engineering Services,136778270.01496467,,,, +,,,,,, +,30s - Subtotal,812177951.367918,,,, +,30s - $/kWe,2613.1851717114478,,,, +,,,,,, +50,Capitalized Supplementary Costs,,,,, +51,Taxes,3510602.4683867483,,,, +52,Insurance,10484751.183521552,,,, +54,Decommissioning ,14606673.443824586,,,, +,,,,,, +,50s - Subtotal,28602027.095732886,,,, +,50s - $/kWe,92.02711420763477,,,, +,,,,,, +60,Capitalized Financial Costs,,,,, +62,Interest ,592300799.7656411,,,, +-,60s - Subtotal,592300799.7656411,,,, +,60s - $/kWe,1905.729728975679,,,, +,,,,,, +,Total Direct Capital Cost (Accounts 10 to 20),1596758321.121697,,,, +,(Accounts 10 to 20) US$/kWe,5137.575035784095,,,, +,,,,,, +,Base Construction Cost (Accounts 10 to 30),2408936272.489615,,,, +,(Accounts 10 to 30) US$/kWe,7750.760207495543,,,, +,,,,,, +,Total Overnight Cost (Accounts 10 to 50),2437538299.5853477,,,, +,(Accounts 10 to 50) US$/kWe,7842.787321703177,,,, +,,,,,, +,Total Capital Investment Cost (All Accounts),3029839099.350989,,,, +,(Accounts 10 to 60) US$/kWe,9748.517050678856,,,, +,,,,,, +,Total Overnight Cost - ITC reduced,2437538299.5853477,,,, +,Total Overnight Cost -ITC reduced (US$/kWe),7842.787321703177,,,, +,,,,,, +,Total Capital Investment Cost - ITC reduced,3029839099.350989,,,, +,Total Capital Investment Cost - ITC reduced (US$/kWe),9748.517050678856,,,, diff --git a/src/crf/data/ref_spending_curve.csv b/src/crf/data/ref_spending_curve.csv new file mode 100644 index 0000000..6ac193d --- /dev/null +++ b/src/crf/data/ref_spending_curve.csv @@ -0,0 +1,105 @@ +Month,CDF +0,0.0032 +1,0.0064 +2,0.009600000000000001 +3,0.0128 +4,0.016 +5,0.019200000000000002 +6,0.022400000000000003 +7,0.029100000000000004 +8,0.035800000000000005 +9,0.0425 +10,0.0492 +11,0.0559 +12,0.0681 +13,0.08349999999999999 +14,0.10289999999999999 +15,0.11699999999999999 +16,0.1309 +17,0.1404 +18,0.1499 +19,0.1602 +20,0.1711 +21,0.1838 +22,0.19799999999999998 +23,0.21209999999999998 +24,0.22709999999999997 +25,0.24129999999999996 +26,0.25479999999999997 +27,0.2689 +28,0.282 +29,0.2934 +30,0.3039 +31,0.3147 +32,0.32539999999999997 +33,0.33519999999999994 +34,0.34589999999999993 +35,0.3616999999999999 +36,0.3773999999999999 +37,0.3928999999999999 +38,0.4094999999999999 +39,0.4265999999999999 +40,0.4444999999999999 +41,0.4596999999999999 +42,0.4701999999999999 +43,0.48049999999999987 +44,0.49119999999999986 +45,0.5020999999999999 +46,0.5130999999999999 +47,0.5240999999999999 +48,0.5370999999999999 +49,0.5481999999999999 +50,0.5592999999999999 +51,0.5703999999999999 +52,0.5813999999999999 +53,0.5923999999999999 +54,0.6006999999999999 +55,0.6089999999999999 +56,0.6185999999999999 +57,0.6268999999999999 +58,0.6350999999999999 +59,0.6432999999999999 +60,0.6528999999999999 +61,0.6621999999999999 +62,0.6713999999999999 +63,0.6808999999999998 +64,0.6904999999999999 +65,0.6989999999999998 +66,0.7073999999999998 +67,0.7174999999999998 +68,0.7263999999999998 +69,0.7352999999999998 +70,0.7441999999999999 +71,0.7528999999999999 +72,0.7613999999999999 +73,0.7697999999999998 +74,0.7780999999999998 +75,0.7866999999999998 +76,0.7952999999999999 +77,0.8039 +78,0.8123999999999999 +79,0.8204999999999999 +80,0.8285999999999999 +81,0.8366999999999999 +82,0.8447999999999999 +83,0.8528999999999999 +84,0.8600999999999999 +85,0.8674999999999998 +86,0.8748999999999998 +87,0.8822999999999998 +88,0.8896999999999997 +89,0.8973999999999998 +90,0.9050999999999998 +91,0.9127999999999998 +92,0.9203999999999999 +93,0.9279999999999999 +94,0.9356 +95,0.9432 +96,0.9507 +97,0.9581999999999999 +98,0.9656999999999999 +99,0.9731999999999998 +100,0.9806999999999998 +101,0.9880999999999998 +102,0.9943999999999997 +103,0.9998999999999997 diff --git a/src/crf/io/excel_inputs.py b/src/crf/io/excel_inputs.py index 7c39f59..7ba59db 100644 --- a/src/crf/io/excel_inputs.py +++ b/src/crf/io/excel_inputs.py @@ -1,3 +1,4 @@ +from pathlib import Path import pandas as pd COLS = [ @@ -7,33 +8,53 @@ ] class InputStore: - def __init__(self, inputs_xlsx: str): - self.inputs_xlsx = inputs_xlsx + """ + Minimal store: + - baseline csv per reactor_type + - one spending curve csv shared + """ + def __init__(self, data_dir: str = None): + if data_dir is None: + self.data_dir = Path(__file__).resolve().parents[1] / "data" + else: + self.data_dir = Path(data_dir) self._baseline = {} self._spending = None def get_baseline(self, reactor_type: str): + if reactor_type in self._baseline: df, power = self._baseline[reactor_type] return df.copy(), power - - if reactor_type == "Concept A": - df = pd.read_excel(self.inputs_xlsx, sheet_name="Concept_A", nrows=69)[COLS].copy() + if reactor_type == "HTGR": + # path = f"{self.data_dir}/HTGR_baseline.csv" + path = self.data_dir / "HTGR_baseline.csv" power = 1056 * 1000 - elif reactor_type == "Concept B": - df = pd.read_excel(self.inputs_xlsx, sheet_name="Concept_B", nrows=69)[COLS].copy() + elif reactor_type == "SFR": + path = self.data_dir / "SFR_baseline.csv" power = 310.8 * 1000 else: raise ValueError(f"Unknown reactor_type: {reactor_type}") + # print current running path for debugging + print(f"Loading baseline from: {path}") + df = pd.read_csv(path) + missing = set(COLS) - set(df.columns) + if missing: + raise ValueError(f"{path} missing columns: {sorted(missing)}") + df = df[COLS].copy() self._baseline[reactor_type] = (df, power) return df.copy(), power def get_spending_curve(self): if self._spending is not None: return self._spending - sp = pd.read_excel(self.inputs_xlsx, sheet_name="Ref Spending Curve", nrows=104, usecols="A:D") + sp_path = path = self.data_dir / "ref_spending_curve.csv" + sp = pd.read_csv(sp_path) + if "Month" not in sp.columns or "CDF" not in sp.columns: + raise ValueError(f"{sp_path} must contain Month and CDF") months = sp["Month"].to_numpy(dtype=float) cdfs = sp["CDF"].to_numpy(dtype=float) self._spending = (months, cdfs) return self._spending + diff --git a/src/crf/model/core_accounts.py b/src/crf/model/core_accounts.py index 4b89d31..790a8cd 100644 --- a/src/crf/model/core_accounts.py +++ b/src/crf/model/core_accounts.py @@ -1,7 +1,79 @@ -import numpy as np import pandas as pd +import numpy as np + +REQUIRED_DERIVED_ROWS = [ + # Subtotals + $/kWe rows + ("10s - Subtotal", None), + ("10s - $/kWe", None), + ("20s - Subtotal", None), + ("20s - $/kWe", None), + ("30s - Subtotal", None), + ("30s - $/kWe", None), + ("50s - Subtotal", None), + ("50s - $/kWe", None), + ("60s - Subtotal", None), + ("60s - $/kWe", None), + + # Final results + ("Total Direct Capital Cost (Accounts 10 to 20)", None), + ("(Accounts 10 to 20) US$/kWe", None), + ("Base Construction Cost (Accounts 10 to 30)", None), + ("(Accounts 10 to 30) US$/kWe", None), + ("Total Overnight Cost (Accounts 10 to 50)", None), + ("(Accounts 10 to 50) US$/kWe", None), + ("Total Capital Investment Cost (All Accounts)", None), + ("(Accounts 10 to 60) US$/kWe", None), + + # ITC reduced outputs (updated later, but create placeholders now) + ("Total Overnight Cost - ITC reduced", None), + ("Total Overnight Cost -ITC reduced (US$/kWe)", None), + ("Total Capital Investment Cost - ITC reduced", None), + ("Total Capital Investment Cost - ITC reduced (US$/kWe)", None), +] + +COST_COLS = [ + "Total Cost (USD)", + "Factory Equipment Cost", + "Site Labor Hours", + "Site Labor Cost", + "Site Material Cost", +] + +ALL_COLS = ["Account", "Title"] + COST_COLS + + +def _blank_row(title: str, account=None) -> dict: + row = {c: np.nan for c in ALL_COLS} + row["Title"] = title + row["Account"] = account + return row + + +def ensure_rows_exist(db: pd.DataFrame) -> pd.DataFrame: + """ + Ensure all derived rows exist (by Title). If missing, append them. + """ + if not set(ALL_COLS).issubset(db.columns): + missing = set(ALL_COLS) - set(db.columns) + raise ValueError(f"DB missing columns: {sorted(missing)}") + + existing_titles = set(db["Title"].astype(str).tolist()) + rows_to_add = [] + for title, acct in REQUIRED_DERIVED_ROWS: + if title not in existing_titles: + rows_to_add.append(_blank_row(title, acct)) + + if rows_to_add: + db = pd.concat([db, pd.DataFrame(rows_to_add)], ignore_index=True) + + return db + def update_high_level_costs(db: pd.DataFrame, reactor_power: float) -> pd.DataFrame: + db = ensure_rows_exist(db) + # change all nan to 0 for cost calculations (but keep original db unchanged for later use) + db = db.copy() + db[COST_COLS] = db[COST_COLS].fillna(0.0) # account 21 db.loc[db.Account == 21, "Factory Equipment Cost"] = ( db.loc[db.Account == 212, "Factory Equipment Cost"].values @@ -23,7 +95,6 @@ def update_high_level_costs(db: pd.DataFrame, reactor_power: float) -> pd.DataFr + db.loc[db.Account == 213, "Site Labor Hours"].values + db.loc[db.Account == "211 plus 214 to 219", "Site Labor Hours"].values ) - # account 23 db.loc[db.Account == 23, "Factory Equipment Cost"] = ( db.loc[db.Account == "232.1", "Factory Equipment Cost"].values @@ -43,36 +114,37 @@ def update_high_level_costs(db: pd.DataFrame, reactor_power: float) -> pd.DataFr ) # total costs for 21..26 components - for x in [21, 212, 213, "211 plus 214 to 219", 22, 23, "232.1", 233, 24, 26]: + for x in [21, 22, 23, 24, 25, 26]: db.loc[db["Account"] == x, "Total Cost (USD)"] = ( db.loc[db["Account"] == x, "Factory Equipment Cost"] + db.loc[db["Account"] == x, "Site Labor Cost"] + db.loc[db["Account"] == x, "Site Material Cost"] ) + # subtotals db.loc[db["Title"] == "10s - Subtotal", "Total Cost (USD)"] = db.loc[ - db["Account"].isin([11, 12, 13, 14, 15, 16, 18]), "Total Cost (USD)" - ].sum() + db["Account"].isin(["11", "12", "13", "14", "15", "16", "18"]), "Total Cost (USD)" + ].fillna(0.0).sum() db.loc[db["Title"] == "20s - Subtotal", "Total Cost (USD)"] = db.loc[ - db["Account"].isin([21, 22, 23, 24, 25, 26, 28]), "Total Cost (USD)" - ].sum() + db["Account"].isin(["21", "22", "23", "24", "25", "26", "28"]), "Total Cost (USD)" + ].fillna(0.0).sum() db.loc[db["Title"] == "30s - Subtotal", "Total Cost (USD)"] = db.loc[ - db["Account"].isin([31, 32, 33, 34, 35]), "Total Cost (USD)" - ].sum() + db["Account"].isin(["31", "32", "33", "34", "35"]), "Total Cost (USD)" + ].fillna(0.0).sum() db.loc[db["Title"] == "50s - Subtotal", "Total Cost (USD)"] = db.loc[ - db["Account"].isin([51, 52, 54]), "Total Cost (USD)" - ].sum() + db["Account"].isin(["51", "52", "54"]), "Total Cost (USD)" + ].fillna(0.0).sum() db.loc[db["Title"] == "60s - Subtotal", "Total Cost (USD)"] = db.loc[ - db["Account"].isin([62]), "Total Cost (USD)" - ].sum() + db["Account"].isin(["62"]), "Total Cost (USD)" + ].fillna(0.0).sum() # $/kWe lines - for t in ["10s", "20s", "30s", "40s", "50s", "60s"]: + for t in ["10s", "20s", "30s", "50s", "60s"]: db.loc[db["Title"] == f"{t} - $/kWe", "Total Cost (USD)"] = ( db.loc[db["Title"] == f"{t} - Subtotal", "Total Cost (USD)"].values / reactor_power ) @@ -118,47 +190,50 @@ def ITC_reduction_factor(itc_level: float) -> float: factors = [1, 0.95, 0.73, 0.63, 0.53] return float(np.interp(itc_level, itc_values, factors)) -def sum_lab_hrs(db: pd.DataFrame): - return ( - db.loc[db.Account == 21, "Site Labor Hours"].values - + db.loc[db.Account == 22, "Site Labor Hours"].values - + db.loc[db.Account == 23, "Site Labor Hours"].values - + db.loc[db.Account == 24, "Site Labor Hours"].values - + db.loc[db.Account == 26, "Site Labor Hours"].values - ) - -def update_cons_duration(db0: pd.DataFrame, db1: pd.DataFrame, ref_duration: float): - sum_old = ( - db0.loc[db0.Account == 21, "Site Labor Hours"].values - + db0.loc[db0.Account == 22, "Site Labor Hours"].values - + db0.loc[db0.Account == 23, "Site Labor Hours"].values - + db0.loc[db0.Account == 24, "Site Labor Hours"].values - + db0.loc[db0.Account == 26, "Site Labor Hours"].values - ) - sum_new = ( - db1.loc[db1.Account == 21, "Site Labor Hours"].values - + db1.loc[db1.Account == 22, "Site Labor Hours"].values - + db1.loc[db1.Account == 23, "Site Labor Hours"].values - + db1.loc[db1.Account == 24, "Site Labor Hours"].values - + db1.loc[db1.Account == 26, "Site Labor Hours"].values +def sum_lab_hrs(db: pd.DataFrame) -> float: + return float( + db.loc[db["Account"].astype(str).str.strip().isin(["21","22","23","24","26"]), "Site Labor Hours"] + .fillna(0.0) + .sum() ) + +def update_cons_duration(db0: pd.DataFrame, db1: pd.DataFrame, ref_duration: float) -> float: + def _sum_hours(db): + return float( + db.loc[db["Account"].astype(str).str.strip().isin(["21","22","23","24","26"]), "Site Labor Hours"] + .fillna(0.0) + .sum() + ) + + sum_old = _sum_hours(db0) + sum_new = _sum_hours(db1) + + if sum_old == 0: + return float(ref_duration) + lab_delta = (sum_new - sum_old) / sum_old - return 0.3 * lab_delta * ref_duration + ref_duration - -def update_cons_duration_2(db0, db1, ref_duration, prev_cons_duration, baseline_lab_hours): - sum_old = ( - db0.loc[db0.Account == 21, "Site Labor Hours"].values - + db0.loc[db0.Account == 22, "Site Labor Hours"].values - + db0.loc[db0.Account == 23, "Site Labor Hours"].values - + db0.loc[db0.Account == 24, "Site Labor Hours"].values - + db0.loc[db0.Account == 26, "Site Labor Hours"].values - ) - sum_new = ( - db1.loc[db1.Account == 21, "Site Labor Hours"].values - + db1.loc[db1.Account == 22, "Site Labor Hours"].values - + db1.loc[db1.Account == 23, "Site Labor Hours"].values - + db1.loc[db1.Account == 24, "Site Labor Hours"].values - + db1.loc[db1.Account == 26, "Site Labor Hours"].values - ) - lab_delta = (sum_new - sum_old) / baseline_lab_hours - return 0.3 * lab_delta * ref_duration + prev_cons_duration + return float(0.3 * lab_delta * ref_duration + ref_duration) + +def update_cons_duration_2( + db0: pd.DataFrame, + db1: pd.DataFrame, + ref_duration: float, + prev_cons_duration: float, + baseline_lab_hours: float +) -> float: + def _sum_hours(db): + return float( + db.loc[db["Account"].astype(str).str.strip().isin(["21","22","23","24","26"]), "Site Labor Hours"] + .fillna(0.0) + .sum() + ) + + sum_old = _sum_hours(db0) + sum_new = _sum_hours(db1) + + if baseline_lab_hours == 0: + return float(prev_cons_duration) + + lab_delta = (sum_new - sum_old) / float(baseline_lab_hours) + return float(0.3 * lab_delta * ref_duration + float(prev_cons_duration)) + diff --git a/src/crf/model/direct_cost.py b/src/crf/model/direct_cost.py index 1e68631..c23eaa3 100644 --- a/src/crf/model/direct_cost.py +++ b/src/crf/model/direct_cost.py @@ -86,7 +86,7 @@ def add_BOP_RP_grades( # duration update from grade change duration_ref = 125 if reactor_type == "Concept A" else 80 - new_dur = float(update_cons_duration(df, db2, duration_ref)) + new_dur = update_cons_duration(df, db2, duration_ref) # modularity factor on duration; for n>=2 assume modularized mod = mod_0 if n_th == 1 else "modularized" diff --git a/src/crf/model/finance.py b/src/crf/model/finance.py index 6191352..7848e68 100644 --- a/src/crf/model/finance.py +++ b/src/crf/model/finance.py @@ -13,21 +13,31 @@ ] -def insurance_cost_update(base_df: pd.DataFrame, df: pd.DataFrame, power: float): - db = df.copy() +def insurance_cost_update(base_df, updated_df, power): + # Ensure derived rows (subtotals/totals) exist and are up-to-date + base_df = update_high_level_costs(base_df.copy(), power) + updated_df = update_high_level_costs(updated_df.copy(), power) + + ref_20 = float(base_df.loc[base_df["Title"].eq("20s - Subtotal"), "Total Cost (USD)"].iloc[0]) + ref_30 = float(base_df.loc[base_df["Title"].eq("30s - Subtotal"), "Total Cost (USD)"].iloc[0]) - new_tot = float(db.loc[db["Title"].eq("20s - Subtotal"), "Total Cost (USD)"].iloc[0]) + \ - float(db.loc[db["Title"].eq("30s - Subtotal"), "Total Cost (USD)"].iloc[0]) + new_20 = float(updated_df.loc[updated_df["Title"].eq("20s - Subtotal"), "Total Cost (USD)"].iloc[0]) + new_30 = float(updated_df.loc[updated_df["Title"].eq("30s - Subtotal"), "Total Cost (USD)"].iloc[0]) - base_tot = float(base_df.loc[base_df["Title"].eq("20s - Subtotal"), "Total Cost (USD)"].iloc[0]) + \ - float(base_df.loc[base_df["Title"].eq("30s - Subtotal"), "Total Cost (USD)"].iloc[0]) + denom = ref_20 + ref_30 + change_factor = 1.0 if denom == 0 else (new_20 + new_30) / denom - factor = new_tot / base_tot + # Update Account 52 (Insurance) + mask52 = updated_df["Account"].astype(str).str.strip().eq("52") + if not mask52.any(): + raise KeyError("Account 52 (Insurance) not found in dataframe.") + ins0 = float(np.nan_to_num(updated_df.loc[mask52, "Total Cost (USD)"].iloc[0], nan=0.0)) + updated_df.loc[mask52, "Total Cost (USD)"] = ins0 * change_factor - old_52 = float(df.loc[df["Account"].eq(52), "Total Cost (USD)"].iloc[0]) - db.loc[db["Account"].eq(52), "Total Cost (USD)"] = old_52 * factor + # Recompute derived totals after change + updated_df = update_high_level_costs(updated_df, power) + return updated_df - return update_high_level_costs(db, power)[COLS].copy() def update_interest_cost( diff --git a/src/crf/model/indirect_cost.py b/src/crf/model/indirect_cost.py index 939cf5f..9ab9956 100644 --- a/src/crf/model/indirect_cost.py +++ b/src/crf/model/indirect_cost.py @@ -28,10 +28,12 @@ def update_indirect_cost( sum_new_mat_cost = 0.0 sum_new_lab_cost = 0.0 sum_new_lab_hrs = 0.0 - for acct in [21, 22, 23, 24, 26]: - sum_new_mat_cost += float(db.loc[db["Account"].eq(acct), "Site Material Cost"].iloc[0]) - sum_new_lab_cost += float(db.loc[db["Account"].eq(acct), "Site Labor Cost"].iloc[0]) - sum_new_lab_hrs += float(db.loc[db["Account"].eq(acct), "Site Labor Hours"].iloc[0]) + mask = db["Account"].astype(str).str.strip().isin(["21","22","23","24","26"]) + sum_new_mat_cost = float(db.loc[mask, "Site Material Cost"].fillna(0.0).sum()) + sum_new_lab_cost = float(db.loc[mask, "Site Labor Cost"].fillna(0.0).sum()) + sum_new_lab_hrs = float(db.loc[mask, "Site Labor Hours"].fillna(0.0).sum()) + + dur = float(final_construction_duration) diff --git a/src/crf/utils/df_ops.py b/src/crf/utils/df_ops.py index da441ae..b3c0a52 100644 --- a/src/crf/utils/df_ops.py +++ b/src/crf/utils/df_ops.py @@ -1,16 +1,43 @@ import numpy as np import pandas as pd -def getv(df: pd.DataFrame, account, col: str): - return df.loc[df["Account"].eq(account), col].iloc[0] +def _acc_str(x) -> str: + return str(x).strip() -def setv(df: pd.DataFrame, account, col: str, value): - df.loc[df["Account"].eq(account), col] = value +def _mask_accounts(df: pd.DataFrame, accounts) -> pd.Series: + """ + Robust account matcher: compares Account values as stripped strings. + accounts can be a scalar or iterable of scalars. + """ + if isinstance(accounts, (str, int, float)): + accounts = [accounts] + targets = {_acc_str(a) for a in accounts} + return df["Account"].astype(str).str.strip().isin(targets) + +def getv(df: pd.DataFrame, account, col): + a = _acc_str(account) + s = df.loc[df["Account"].astype(str).str.strip().eq(a), col] + if s.empty: + raise KeyError( + f"Account '{account}' not found in dataframe (column='{col}'). " + f"Available sample accounts: {df['Account'].astype(str).str.strip().head(30).tolist()}" + ) + return s.iloc[0] + +def setv(df: pd.DataFrame, account, col, value): + mask = _mask_accounts(df, account) + if not mask.any(): + raise KeyError(f"Account '{account}' not found for setv (column='{col}')") + df.loc[mask, col] = value def mulv(df: pd.DataFrame, accounts, cols, factor: float): - m = df["Account"].isin(accounts) - df.loc[m, cols] = df.loc[m, cols].to_numpy() * factor + mask = _mask_accounts(df, accounts) + if not mask.any(): + raise KeyError(f"None of accounts {accounts} found for mulv") + df.loc[mask, cols] = df.loc[mask, cols].to_numpy() * factor def resetv(df: pd.DataFrame, accounts, cols): - m = df["Account"].isin(accounts) - df.loc[m, cols] = np.nan + mask = _mask_accounts(df, accounts) + if not mask.any(): + raise KeyError(f"None of accounts {accounts} found for resetv") + df.loc[mask, cols] = np.nan diff --git a/test/test_crf_smoke.py b/test/test_crf_smoke.py index 4306003..7eded09 100644 --- a/test/test_crf_smoke.py +++ b/test/test_crf_smoke.py @@ -10,16 +10,26 @@ def main(): + # config = { + # "inputs_xlsx": "Inputs.xlsx", + # "reactor_type": "Concept A", + # "f_22": 250_000_000, + # "f_2321": 150_000_000, + # "land_cost_per_acre_0": 22000, + # "startup_0": 16, + # "staggering_ratio": 0.75, + # } config = { - "inputs_xlsx": "Inputs.xlsx", - "reactor_type": "Concept A", - "f_22": 250_000_000, - "f_2321": 150_000_000, - "land_cost_per_acre_0": 22000, - "startup_0": 16, - "staggering_ratio": 0.75, + "reactor_type": "HTGR", # or "SFR" + # "data_dir": "src/crf/data", # + "f_22": 250_000_000, + "f_2321": 150_000_000, + "land_cost_per_acre_0": 22000, + "startup_0": 16, + "staggering_ratio": 0.75, } + # baseline-style levers (raw form, like from Excel) levers = { "num_orders": 3, From 7ee6e615bb2d5819076b09d8e9bbf16d19453de4 Mon Sep 17 00:00:00 2001 From: "jia.zhou" <48254033+JiaZhou-PU@users.noreply.github.com> Date: Thu, 19 Feb 2026 14:59:34 -0600 Subject: [PATCH 04/25] GNCOA25 exclude --- src/crf/data/HTGR_baseline.csv | 2 +- src/crf/model/core_accounts.py | 68 ++++++++++++++++++++-------------- src/crf/model/direct_cost.py | 22 +++++++++-- 3 files changed, 61 insertions(+), 31 deletions(-) diff --git a/src/crf/data/HTGR_baseline.csv b/src/crf/data/HTGR_baseline.csv index 08bcf24..fb432e8 100644 --- a/src/crf/data/HTGR_baseline.csv +++ b/src/crf/data/HTGR_baseline.csv @@ -17,7 +17,7 @@ Account,Title,Total Cost (USD),Factory Equipment Cost,Site Labor Hours,Site Labo 232.1,Electricity Generation Systems,576019409.4786329,486396295.4,1682189.488,89623114.06,0 233,Ultimate Heat Sink,89555306.03530921,47848710.99,682455.832,35879240.23,5827354.82 24,Electrical Equipment,140508400.4910087,28461247.42,1597897.041,86126547.25,25920605.82 -25,Initial fuel inventory,451686470.5748004,,0,0,0 +25,Initial fuel inventory,451686470.5748004,451686470.5748004,0,0,0 26,Miscellaneous Equipment,214388490.9241457,65401342.47,2408453.335,129937709.9,19049438.52 28,Simulator,0,,,, 30,Capitalized Indirect Services Costs,,,,, diff --git a/src/crf/model/core_accounts.py b/src/crf/model/core_accounts.py index 790a8cd..6979bc8 100644 --- a/src/crf/model/core_accounts.py +++ b/src/crf/model/core_accounts.py @@ -75,51 +75,63 @@ def update_high_level_costs(db: pd.DataFrame, reactor_power: float) -> pd.DataFr db = db.copy() db[COST_COLS] = db[COST_COLS].fillna(0.0) # account 21 - db.loc[db.Account == 21, "Factory Equipment Cost"] = ( - db.loc[db.Account == 212, "Factory Equipment Cost"].values - + db.loc[db.Account == 213, "Factory Equipment Cost"].values + db.loc[db.Account == "21", "Factory Equipment Cost"] = ( + db.loc[db.Account == "212", "Factory Equipment Cost"].values + + db.loc[db.Account == "213", "Factory Equipment Cost"].values + db.loc[db.Account == "211 plus 214 to 219", "Factory Equipment Cost"].values ) - db.loc[db.Account == 21, "Site Material Cost"] = ( - db.loc[db.Account == 212, "Site Material Cost"].values - + db.loc[db.Account == 213, "Site Material Cost"].values + db.loc[db.Account == "21", "Site Material Cost"] = ( + db.loc[db.Account == "212", "Site Material Cost"].values + + db.loc[db.Account == "213", "Site Material Cost"].values + db.loc[db.Account == "211 plus 214 to 219", "Site Material Cost"].values ) - db.loc[db.Account == 21, "Site Labor Cost"] = ( - db.loc[db.Account == 212, "Site Labor Cost"].values - + db.loc[db.Account == 213, "Site Labor Cost"].values + db.loc[db.Account == "21", "Site Labor Cost"] = ( + db.loc[db.Account == "212", "Site Labor Cost"].values + + db.loc[db.Account == "213", "Site Labor Cost"].values + db.loc[db.Account == "211 plus 214 to 219", "Site Labor Cost"].values ) - db.loc[db.Account == 21, "Site Labor Hours"] = ( - db.loc[db.Account == 212, "Site Labor Hours"].values - + db.loc[db.Account == 213, "Site Labor Hours"].values + db.loc[db.Account == "21", "Site Labor Hours"] = ( + db.loc[db.Account == "212", "Site Labor Hours"].values + + db.loc[db.Account == "213", "Site Labor Hours"].values + db.loc[db.Account == "211 plus 214 to 219", "Site Labor Hours"].values ) # account 23 - db.loc[db.Account == 23, "Factory Equipment Cost"] = ( + db.loc[db.Account == "23", "Factory Equipment Cost"] = ( db.loc[db.Account == "232.1", "Factory Equipment Cost"].values - + db.loc[db.Account == 233, "Factory Equipment Cost"].values + + db.loc[db.Account == "233", "Factory Equipment Cost"].values ) - db.loc[db.Account == 23, "Site Material Cost"] = ( + db.loc[db.Account == "23", "Site Material Cost"] = ( db.loc[db.Account == "232.1", "Site Material Cost"].values - + db.loc[db.Account == 233, "Site Material Cost"].values + + db.loc[db.Account == "233", "Site Material Cost"].values ) - db.loc[db.Account == 23, "Site Labor Cost"] = ( + db.loc[db.Account == "23", "Site Labor Cost"] = ( db.loc[db.Account == "232.1", "Site Labor Cost"].values - + db.loc[db.Account == 233, "Site Labor Cost"].values + + db.loc[db.Account == "233", "Site Labor Cost"].values ) - db.loc[db.Account == 23, "Site Labor Hours"] = ( + db.loc[db.Account == "23", "Site Labor Hours"] = ( db.loc[db.Account == "232.1", "Site Labor Hours"].values - + db.loc[db.Account == 233, "Site Labor Hours"].values + + db.loc[db.Account == "233", "Site Labor Hours"].values ) - # total costs for 21..26 components - for x in [21, 22, 23, 24, 25, 26]: - db.loc[db["Account"] == x, "Total Cost (USD)"] = ( - db.loc[db["Account"] == x, "Factory Equipment Cost"] - + db.loc[db["Account"] == x, "Site Labor Cost"] - + db.loc[db["Account"] == x, "Site Material Cost"] - ) + # total costs for all accounts should be updated except the lines + # with no Account (subtotals, $/kWe, and final results) + db.loc[db["Account"].notna(), "Total Cost (USD)"] = ( + db.loc[db["Account"].notna(), "Factory Equipment Cost"].values + + db.loc[db["Account"].notna(), "Site Material Cost"].values + + db.loc[db["Account"].notna(), "Site Labor Cost"].values + ) + + + + + + + # for x in [21, 22, 23, 24, 25, 26]: + # db.loc[db["Account"] == x, "Total Cost (USD)"] = ( + # db.loc[db["Account"] == x, "Factory Equipment Cost"] + # + db.loc[db["Account"] == x, "Site Labor Cost"] + # + db.loc[db["Account"] == x, "Site Material Cost"] + # ) # subtotals @@ -183,6 +195,8 @@ def update_high_level_costs(db: pd.DataFrame, reactor_power: float) -> pd.DataFr db.loc[db["Title"] == "(Accounts 10 to 60) US$/kWe", "Total Cost (USD)"] = ( db.loc[db["Title"] == "Total Capital Investment Cost (All Accounts)", "Total Cost (USD)"].values / reactor_power ) + # print(db[8:21]) + # print(db[35:37]) # debug print return db def ITC_reduction_factor(itc_level: float) -> float: diff --git a/src/crf/model/direct_cost.py b/src/crf/model/direct_cost.py index c23eaa3..d463965 100644 --- a/src/crf/model/direct_cost.py +++ b/src/crf/model/direct_cost.py @@ -19,7 +19,7 @@ "Factory Equipment Cost", "Site Labor Hours", "Site Labor Cost", "Site Material Cost" ] - +# exclude account 25 because it is the initial fuel cost and should not be affected by rework ACCT_DIRECT = [212, 213, "211 plus 214 to 219", 22, "232.1", 233, 24, 26] @@ -185,15 +185,31 @@ def update_direct_cost( mod_0: str ): reactor_df, power = store.get_baseline(reactor_type) - db, baseline_lab_hours = add_factory_cost(reactor_df, power, f_22, f_2321, num_orders) + # all Total Cost (USD) columns should be 0 at this point + db["Total Cost (USD)"] = 0.0 + db = update_high_level_costs(db, power)[COLS].copy() + # print("After factory cost update:") + # print(db[8:21]) + # print(db[35:37]) # debug print db = add_land_cost(db, land_cost_per_acre_0, power) + # print("After land cost update:") + # print(db[8:21]) + # print(db[35:37]) # debug print db, prev_dur = add_BOP_RP_grades(db, RB_grade_0, BOP_grade_0, power, reactor_type, n_th, mod_0) + # print("After BOP/RP grade update:") + # print(db[8:21]) + # print(db[35:37]) # debug print db = add_bulk_ordering(db, num_orders, f_22, f_2321, power) + # print("After bulk ordering update:") + # print(db[8:21]) + # print(db[35:37]) # debug print db, dur_no_delay = add_reworking_productivity( db, reactor_type, n_th, design_completion_0, ae_exp_0, N_AE, ce_exp_0, N_cons, power, prev_dur, baseline_lab_hours ) - + # print("After direct cost updates:") + # print(db[8:21]) + # print(db[35:37]) # debug print return db, dur_no_delay From 1f4ce52fd735b4380627a8b78aa3106dcf9adef1 Mon Sep 17 00:00:00 2001 From: "jia.zhou" <48254033+JiaZhou-PU@users.noreply.github.com> Date: Thu, 19 Feb 2026 22:26:01 -0600 Subject: [PATCH 05/25] update total cost on 20s --- src/crf/model/core_accounts.py | 33 +++++++++++++-------------------- src/crf/model/direct_cost.py | 5 +++-- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/crf/model/core_accounts.py b/src/crf/model/core_accounts.py index 6979bc8..afb1857 100644 --- a/src/crf/model/core_accounts.py +++ b/src/crf/model/core_accounts.py @@ -95,6 +95,8 @@ def update_high_level_costs(db: pd.DataFrame, reactor_power: float) -> pd.DataFr + db.loc[db.Account == "213", "Site Labor Hours"].values + db.loc[db.Account == "211 plus 214 to 219", "Site Labor Hours"].values ) + + # account 23 db.loc[db.Account == "23", "Factory Equipment Cost"] = ( db.loc[db.Account == "232.1", "Factory Equipment Cost"].values @@ -114,24 +116,16 @@ def update_high_level_costs(db: pd.DataFrame, reactor_power: float) -> pd.DataFr ) # total costs for all accounts should be updated except the lines - # with no Account (subtotals, $/kWe, and final results) - db.loc[db["Account"].notna(), "Total Cost (USD)"] = ( - db.loc[db["Account"].notna(), "Factory Equipment Cost"].values - + db.loc[db["Account"].notna(), "Site Material Cost"].values - + db.loc[db["Account"].notna(), "Site Labor Cost"].values - ) - - - - - - - # for x in [21, 22, 23, 24, 25, 26]: - # db.loc[db["Account"] == x, "Total Cost (USD)"] = ( - # db.loc[db["Account"] == x, "Factory Equipment Cost"] - # + db.loc[db["Account"] == x, "Site Labor Cost"] - # + db.loc[db["Account"] == x, "Site Material Cost"] - # ) + # with no Account (subtotals, $/kWe, and final results) but only + # accounts under 20s has factory equipment costs, labor hours, and + # labor costs, so we can skip accounts 10s, 30s 50s and 60s + + for x in ['21', '211 plus 214 to 219', '212', '213', '22', '23', '232.1', '233', '24', '25', '26']: + db.loc[db["Account"] == x, "Total Cost (USD)"] = ( + db.loc[db["Account"] == x, "Factory Equipment Cost"] + + db.loc[db["Account"] == x, "Site Labor Cost"] + + db.loc[db["Account"] == x, "Site Material Cost"] + ) # subtotals @@ -195,8 +189,7 @@ def update_high_level_costs(db: pd.DataFrame, reactor_power: float) -> pd.DataFr db.loc[db["Title"] == "(Accounts 10 to 60) US$/kWe", "Total Cost (USD)"] = ( db.loc[db["Title"] == "Total Capital Investment Cost (All Accounts)", "Total Cost (USD)"].values / reactor_power ) - # print(db[8:21]) - # print(db[35:37]) # debug print + # print(db[["Account", "Title", "Total Cost (USD)"]]) return db def ITC_reduction_factor(itc_level: float) -> float: diff --git a/src/crf/model/direct_cost.py b/src/crf/model/direct_cost.py index d463965..c6fe749 100644 --- a/src/crf/model/direct_cost.py +++ b/src/crf/model/direct_cost.py @@ -186,8 +186,9 @@ def update_direct_cost( ): reactor_df, power = store.get_baseline(reactor_type) db, baseline_lab_hours = add_factory_cost(reactor_df, power, f_22, f_2321, num_orders) - # all Total Cost (USD) columns should be 0 at this point - db["Total Cost (USD)"] = 0.0 + # all Total Cost (USD) columns should be 0 + # when accounts are in 20s + db.loc[db["Account"].isin(['21', '211 plus 214 to 219', '212', '213', '22', '23', '232.1', '233', '24', '25', '26']), "Total Cost (USD)"] = 0.0 db = update_high_level_costs(db, power)[COLS].copy() # print("After factory cost update:") # print(db[8:21]) From 566322724a17048acb8135b3b2e0c4fe1c07497c Mon Sep 17 00:00:00 2001 From: "jia.zhou" <48254033+JiaZhou-PU@users.noreply.github.com> Date: Thu, 26 Feb 2026 18:32:32 -0600 Subject: [PATCH 06/25] construction duration has not fixed also the concept name --- .../CostReduction_exploration_mode.ipynb | 8 +- Cost_Reduction/scenarios.ipynb | 282 +++++++++++++++++- src/crf/data/HTGR_baseline.csv | 2 +- src/crf/model/core_accounts.py | 1 - src/crf/model/direct_cost.py | 26 +- src/crf/model/finance.py | 5 + src/crf/model/indirect_cost.py | 6 +- src/crf/model/learning.py | 4 +- test/test_crf_smoke.py | 43 ++- 9 files changed, 314 insertions(+), 63 deletions(-) diff --git a/Cost_Reduction/CostReduction_exploration_mode.ipynb b/Cost_Reduction/CostReduction_exploration_mode.ipynb index 1c2cb30..70af906 100644 --- a/Cost_Reduction/CostReduction_exploration_mode.ipynb +++ b/Cost_Reduction/CostReduction_exploration_mode.ipynb @@ -3676,7 +3676,7 @@ ], "source": [ "def update_indirect_cost(n_th, standardization_0, Reactor_data_updated_6, final_construction_duration, power ):\n", - " \n", + " print(\"in function update indirect\")\n", " if n_th == 1:\n", " standardization = 0.7\n", " elif n_th >1:\n", @@ -3709,8 +3709,7 @@ " sum_new_mat_cost += (db.loc[db.Account == x, 'Site Material Cost']).values\n", " sum_new_lab_cost += (db.loc[db.Account == x, 'Site Labor Cost']).values\n", " sum_new_lab_hrs += (db.loc[db.Account == x, 'Site Labor Hours']).values\n", - "\n", - "\n", + " \n", " # The new indirect costs \n", " db.loc[db.Account == 31, 'Total Cost (USD)'] = (sum_new_mat_cost*0.785* sum_new_lab_hrs/final_construction_duration/160/1058)\\\n", " + sum_new_lab_cost *0.36\n", @@ -3728,7 +3727,6 @@ " Reactor_data_updated_7_ = pd.DataFrame()\n", " Reactor_data_updated_7_ = Reactor_data_updated_7[['Account', 'Title', 'Total Cost (USD)', 'Factory Equipment Cost', 'Site Labor Hours', 'Site Labor Cost',\\\n", " 'Site Material Cost']].copy()\n", - " \n", " return Reactor_data_updated_7_ \n", "\n", "\n", @@ -5734,7 +5732,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.18" + "version": "3.10.16" } }, "nbformat": 4, diff --git a/Cost_Reduction/scenarios.ipynb b/Cost_Reduction/scenarios.ipynb index 50f3a0e..6d38278 100644 --- a/Cost_Reduction/scenarios.ipynb +++ b/Cost_Reduction/scenarios.ipynb @@ -54,7 +54,30 @@ "execution_count": 2, "id": "7b9a01a8", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "T_end,ref_construction_duration,cons_duration_no_delay,supply_chain_delay,actual_construction_duration_plus_delay\n", + "75.434 64 [80.63803108] 11.433999999999997 [92.07203108]\n", + "T_end,ref_construction_duration,cons_duration_no_delay,supply_chain_delay,actual_construction_duration_plus_delay\n", + "75.434 64 [80.63803108] 11.433999999999997 [92.07203108]\n", + "T_end,ref_construction_duration,cons_duration_no_delay,supply_chain_delay,actual_construction_duration_plus_delay\n", + "75.434 64 [80.63803108] 11.433999999999997 [92.07203108]\n", + "in function update indirect\n", + "T_end,ref_construction_duration,cons_duration_no_delay,supply_chain_delay,actual_construction_duration_plus_delay\n", + "75.434 64 [80.63803108] 11.433999999999997 [92.07203108]\n", + "in function update indirect\n", + "T_end,ref_construction_duration,cons_duration_no_delay,supply_chain_delay,actual_construction_duration_plus_delay\n", + "75.434 64 [80.63803108] 11.433999999999997 [92.07203108]\n", + "in function update indirect\n", + "T_end,ref_construction_duration,cons_duration_no_delay,supply_chain_delay,actual_construction_duration_plus_delay\n", + "75.434 64 [80.63803108] 11.433999999999997 [92.07203108]\n", + "in function update indirect\n" + ] + } + ], "source": [ "# Just running the other jupyter notebook to bring all the functions from there\n", "%run CostReduction_exploration_mode.ipynb" @@ -172,8 +195,8 @@ " ITC_0 = 0.4\n", " n_ITC = 4 \n", " \n", - " avg_results = calculate_final_result_avg(reactor_type, f_22, f_2321, land_cost_per_acre_0, RB_grade_0, BOP_grade_0,\\\n", - " num_orders, design_completion_0, ae_exp_0, N_AE, ce_exp_0, N_cons, mod_0, Design_Maturity_0, proc_exp_0, N_proc, standardization_0, interest_rate_0, startup_0) " + " #avg_results = calculate_final_result_avg(reactor_type, f_22, f_2321, land_cost_per_acre_0, RB_grade_0, BOP_grade_0,\\\n", + " #num_orders, design_completion_0, ae_exp_0, N_AE, ce_exp_0, N_cons, mod_0, Design_Maturity_0, proc_exp_0, N_proc, standardization_0, interest_rate_0, startup_0) " ] }, { @@ -181,11 +204,256 @@ "execution_count": 5, "id": "8187e003", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "4\n", + "T_end,ref_construction_duration,cons_duration_no_delay,supply_chain_delay,actual_construction_duration_plus_delay\n", + "111.4278125 100 [121.63844064] 11.427812500000002 [133.06625314]\n", + "in function update indirect\n" + ] + }, + { + "data": { + "text/plain": [ + "( Account Title \\\n", + " 0 10 Capitalized Pre-Construction Costs \n", + " 1 11 Land & Land Rights \n", + " 2 12 Site Permits \n", + " 3 13 Plant Licensing \n", + " 4 14 Plant Permits \n", + " 5 15 Plant Studies \n", + " 6 16 Plant Reports \n", + " 7 18 Other Pre-Construction Costs \n", + " 8 NaN NaN \n", + " 9 NaN 10s - Subtotal \n", + " 10 NaN 10s - $/kWe \n", + " 11 NaN NaN \n", + " 12 20 Capitalized Direct Costs \n", + " 13 21 Structures & Improvements \n", + " 14 212 Reactor Containment Building \n", + " 15 213 Turbine Room and Heater Bay \n", + " 16 211 plus 214 to 219 Othe Structures & Improvements \n", + " 17 22 Reactor System \n", + " 18 23 Energy Conversion System \n", + " 19 232.1 Electricity Generation Systems \n", + " 20 233 Ultimate Heat Sink \n", + " 21 24 Electrical Equipment \n", + " 22 25 Initial fuel inventory \n", + " 23 26 Miscellaneous Equipment \n", + " 24 28 Simulator \n", + " 25 NaN NaN \n", + " 26 NaN 20s - Subtotal \n", + " 27 NaN 20s - $/kWe \n", + " 28 NaN NaN \n", + " 29 30 Capitalized Indirect Services Costs \n", + " 30 31 Factory & Field Indirect Costs \n", + " 31 32 Factory and construction supervision \n", + " 32 33 Start-Up Costs \n", + " 33 34 Shipping & Transportation Costs \n", + " 34 35 Engineering Services \n", + " 35 NaN NaN \n", + " 36 NaN 30s - Subtotal \n", + " 37 NaN 30s - $/kWe \n", + " 38 NaN NaN \n", + " 39 50 Capitalized Supplementary Costs \n", + " 40 51 Taxes \n", + " 41 52 Insurance \n", + " 42 54 Decommissioning \n", + " 43 NaN NaN \n", + " 44 NaN 50s - Subtotal \n", + " 45 NaN 50s - $/kWe \n", + " 46 NaN NaN \n", + " 47 60 Capitalized Financial Costs \n", + " 48 62 Interest \n", + " 49 - 60s - Subtotal \n", + " 50 NaN 60s - $/kWe \n", + " 51 NaN NaN \n", + " 52 NaN Total Direct Capital Cost (Accounts 10 to 20) \n", + " 53 NaN (Accounts 10 to 20) US$/kWe \n", + " 54 NaN NaN \n", + " 55 NaN Base Construction Cost (Accounts 10 to 30) \n", + " 56 NaN (Accounts 10 to 30) US$/kWe \n", + " 57 NaN NaN \n", + " 58 NaN Total Overnight Cost (Accounts 10 to 50) \n", + " 59 NaN (Accounts 10 to 50) US$/kWe \n", + " 60 NaN NaN \n", + " 61 NaN Total Capital Investment Cost (All Accounts) \n", + " 62 NaN (Accounts 10 to 60) US$/kWe \n", + " 63 NaN NaN \n", + " 64 NaN Total Overnight Cost - ITC reduced \n", + " 65 NaN Total Overnight Cost -ITC reduced (US$/kWe) \n", + " 66 NaN NaN \n", + " 67 NaN Total Capital Investment Cost - ITC reduced \n", + " 68 NaN Total Capital Investment Cost - ITC reduced (U... \n", + " \n", + " Total Cost (USD) Factory Equipment Cost Site Labor Hours \\\n", + " 0 NaN NaN NaN \n", + " 1 1.500000e+07 NaN NaN \n", + " 2 0.000000e+00 NaN NaN \n", + " 3 1.070098e+08 NaN NaN \n", + " 4 4.721019e+06 NaN NaN \n", + " 5 0.000000e+00 NaN NaN \n", + " 6 0.000000e+00 NaN NaN \n", + " 7 3.619448e+07 NaN NaN \n", + " 8 NaN NaN NaN \n", + " 9 1.629253e+08 NaN NaN \n", + " 10 1.542853e+02 NaN NaN \n", + " 11 NaN NaN NaN \n", + " 12 NaN NaN NaN \n", + " 13 1.533300e+09 1.365362e+08 2.090920e+07 \n", + " 14 6.966448e+08 9.992741e+07 9.638431e+06 \n", + " 15 1.522573e+07 1.036695e+06 1.749114e+05 \n", + " 16 8.214295e+08 3.557211e+07 1.109586e+07 \n", + " 17 1.975878e+09 1.478440e+09 8.719725e+06 \n", + " 18 4.755064e+08 3.072326e+08 3.008178e+06 \n", + " 19 3.301050e+08 2.344884e+08 1.794686e+06 \n", + " 20 1.454013e+08 7.274425e+07 1.213492e+06 \n", + " 21 2.358203e+08 4.326955e+07 2.841261e+06 \n", + " 22 4.516865e+08 NaN 0.000000e+00 \n", + " 23 3.594357e+08 9.942946e+07 4.282531e+06 \n", + " 24 0.000000e+00 NaN NaN \n", + " 25 NaN NaN NaN \n", + " 26 5.031627e+09 NaN NaN \n", + " 27 4.764798e+03 NaN NaN \n", + " 28 NaN NaN NaN \n", + " 29 NaN NaN NaN \n", + " 30 1.377074e+09 NaN NaN \n", + " 31 5.005922e+09 NaN NaN \n", + " 32 2.102487e+08 NaN NaN \n", + " 33 1.752073e+07 NaN NaN \n", + " 34 1.351599e+09 NaN NaN \n", + " 35 NaN NaN NaN \n", + " 36 7.962364e+09 NaN NaN \n", + " 37 7.540118e+03 NaN NaN \n", + " 38 NaN NaN NaN \n", + " 39 NaN NaN NaN \n", + " 40 1.875000e+05 NaN NaN \n", + " 41 5.847296e+07 NaN NaN \n", + " 42 8.141760e+06 NaN NaN \n", + " 43 NaN NaN NaN \n", + " 44 6.680222e+07 NaN NaN \n", + " 45 6.325968e+01 NaN NaN \n", + " 46 NaN NaN NaN \n", + " 47 NaN NaN NaN \n", + " 48 6.594528e+09 NaN NaN \n", + " 49 6.594528e+09 NaN NaN \n", + " 50 6.244818e+03 NaN NaN \n", + " 51 NaN NaN NaN \n", + " 52 5.194552e+09 NaN NaN \n", + " 53 4.919084e+03 NaN NaN \n", + " 54 NaN NaN NaN \n", + " 55 1.315692e+10 NaN NaN \n", + " 56 1.245920e+04 NaN NaN \n", + " 57 NaN NaN NaN \n", + " 58 1.322372e+10 NaN NaN \n", + " 59 1.252246e+04 NaN NaN \n", + " 60 NaN NaN NaN \n", + " 61 1.981825e+10 NaN NaN \n", + " 62 1.876728e+04 NaN NaN \n", + " 63 NaN NaN NaN \n", + " 64 8.330943e+09 NaN NaN \n", + " 65 7.889150e+03 NaN NaN \n", + " 66 NaN NaN NaN \n", + " 67 1.492547e+10 NaN NaN \n", + " 68 1.413397e+04 NaN NaN \n", + " \n", + " Site Labor Cost Site Material Cost \n", + " 0 NaN NaN \n", + " 1 NaN NaN \n", + " 2 NaN NaN \n", + " 3 NaN NaN \n", + " 4 NaN NaN \n", + " 5 NaN NaN \n", + " 6 NaN NaN \n", + " 7 NaN NaN \n", + " 8 NaN NaN \n", + " 9 NaN NaN \n", + " 10 NaN NaN \n", + " 11 NaN NaN \n", + " 12 NaN NaN \n", + " 13 1.045892e+09 3.508716e+08 \n", + " 14 4.700620e+08 1.266554e+08 \n", + " 15 9.024686e+06 5.164350e+06 \n", + " 16 5.668055e+08 2.190519e+08 \n", + " 17 4.656677e+08 3.177007e+07 \n", + " 18 1.594144e+08 8.859309e+06 \n", + " 19 9.561666e+07 0.000000e+00 \n", + " 20 6.379777e+07 8.859309e+06 \n", + " 21 1.531438e+08 3.940702e+07 \n", + " 22 0.000000e+00 0.000000e+00 \n", + " 23 2.310455e+08 2.896080e+07 \n", + " 24 NaN NaN \n", + " 25 NaN NaN \n", + " 26 NaN NaN \n", + " 27 NaN NaN \n", + " 28 NaN NaN \n", + " 29 NaN NaN \n", + " 30 NaN NaN \n", + " 31 NaN NaN \n", + " 32 NaN NaN \n", + " 33 NaN NaN \n", + " 34 NaN NaN \n", + " 35 NaN NaN \n", + " 36 NaN NaN \n", + " 37 NaN NaN \n", + " 38 NaN NaN \n", + " 39 NaN NaN \n", + " 40 NaN NaN \n", + " 41 NaN NaN \n", + " 42 NaN NaN \n", + " 43 NaN NaN \n", + " 44 NaN NaN \n", + " 45 NaN NaN \n", + " 46 NaN NaN \n", + " 47 NaN NaN \n", + " 48 NaN NaN \n", + " 49 NaN NaN \n", + " 50 NaN NaN \n", + " 51 NaN NaN \n", + " 52 NaN NaN \n", + " 53 NaN NaN \n", + " 54 NaN NaN \n", + " 55 NaN NaN \n", + " 56 NaN NaN \n", + " 57 NaN NaN \n", + " 58 NaN NaN \n", + " 59 NaN NaN \n", + " 60 NaN NaN \n", + " 61 NaN NaN \n", + " 62 NaN NaN \n", + " 63 NaN NaN \n", + " 64 NaN NaN \n", + " 65 NaN NaN \n", + " 66 NaN NaN \n", + " 67 NaN NaN \n", + " 68 NaN NaN ,\n", + " 7889.150458472184,\n", + " 14133.968613003488,\n", + " array([133.06625314]))" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "# calculate_final_result(reactor_type, n_th, f_22, f_2321, land_cost_per_acre_0, RB_grade_0, BOP_grade_0,\\\n", - "# num_orders, design_completion_0, ae_exp_0, N_AE, ce_exp_0, N_cons, mod_0, Design_Maturity_0, proc_exp_0, N_proc, standardization_0, interest_rate_0, startup_0)" + "print(n_ITC)\n", + "calculate_final_result(reactor_type, n_th, f_22, f_2321, land_cost_per_acre_0, RB_grade_0, BOP_grade_0,\\\n", + " num_orders, design_completion_0, ae_exp_0, N_AE, ce_exp_0, N_cons, mod_0, Design_Maturity_0, proc_exp_0, N_proc, \\\n", + " standardization_0, interest_rate_0, startup_0)\n" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8f62c044-f816-49db-8564-5ef81c3221f8", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { @@ -204,7 +472,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.18" + "version": "3.10.16" } }, "nbformat": 4, diff --git a/src/crf/data/HTGR_baseline.csv b/src/crf/data/HTGR_baseline.csv index fb432e8..501a4d9 100644 --- a/src/crf/data/HTGR_baseline.csv +++ b/src/crf/data/HTGR_baseline.csv @@ -19,7 +19,7 @@ Account,Title,Total Cost (USD),Factory Equipment Cost,Site Labor Hours,Site Labo 24,Electrical Equipment,140508400.4910087,28461247.42,1597897.041,86126547.25,25920605.82 25,Initial fuel inventory,451686470.5748004,451686470.5748004,0,0,0 26,Miscellaneous Equipment,214388490.9241457,65401342.47,2408453.335,129937709.9,19049438.52 -28,Simulator,0,,,, +28,Simulator,0,0,0,0,0 30,Capitalized Indirect Services Costs,,,,, 31,Factory & Field Indirect Costs,691371059.4821017,,,, 32,Factory and construction supervision,2734393138.1498117,,,, diff --git a/src/crf/model/core_accounts.py b/src/crf/model/core_accounts.py index afb1857..d0ced42 100644 --- a/src/crf/model/core_accounts.py +++ b/src/crf/model/core_accounts.py @@ -189,7 +189,6 @@ def update_high_level_costs(db: pd.DataFrame, reactor_power: float) -> pd.DataFr db.loc[db["Title"] == "(Accounts 10 to 60) US$/kWe", "Total Cost (USD)"] = ( db.loc[db["Title"] == "Total Capital Investment Cost (All Accounts)", "Total Cost (USD)"].values / reactor_power ) - # print(db[["Account", "Title", "Total Cost (USD)"]]) return db def ITC_reduction_factor(itc_level: float) -> float: diff --git a/src/crf/model/direct_cost.py b/src/crf/model/direct_cost.py index c6fe749..38eb3bf 100644 --- a/src/crf/model/direct_cost.py +++ b/src/crf/model/direct_cost.py @@ -85,9 +85,11 @@ def add_BOP_RP_grades( db2 = update_high_level_costs(db, power)[COLS].copy() # duration update from grade change - duration_ref = 125 if reactor_type == "Concept A" else 80 + duration_ref = 125 if reactor_type == "HTGR" else 80 + # duration_ref = 100 if reactor_type == "HTGR" else 64 + print(f"DEBUG: reactor_type={reactor_type}, duration_ref={duration_ref}") new_dur = update_cons_duration(df, db2, duration_ref) - + print(f"DEBUG: new_dur before modulized change={new_dur}") # modularity factor on duration; for n>=2 assume modularized mod = mod_0 if n_th == 1 else "modularized" mod_factor = 0.8 if mod == "modularized" else 1.0 @@ -146,7 +148,7 @@ def add_reworking_productivity( productivity = 0.145 * ce_exp + 0.71 - if reactor_type == "Concept B": + if reactor_type == "SFR": rework = (-0.9 * design_completion + 1.9) * (-0.15 * ae_exp + 1.3) * (-0.15 * ce_exp + 1.3) ref_duration = 80 else: @@ -162,8 +164,9 @@ def add_reworking_productivity( setv(db, acct, "Site Labor Cost", float(getv(df, acct, "Site Labor Cost")) * rework / productivity) db2 = update_high_level_costs(db, power)[COLS].copy() - + print(f"DEBUG: ref_duration={ref_duration}, prev_cons_duration={prev_cons_duration}, baseline_lab_hours={baseline_lab_hours}") new_dur = float(update_cons_duration_2(df, db2, ref_duration, prev_cons_duration, baseline_lab_hours)) + print(f"DEBUG: new_dur={new_dur}") return db2, new_dur @@ -190,27 +193,12 @@ def update_direct_cost( # when accounts are in 20s db.loc[db["Account"].isin(['21', '211 plus 214 to 219', '212', '213', '22', '23', '232.1', '233', '24', '25', '26']), "Total Cost (USD)"] = 0.0 db = update_high_level_costs(db, power)[COLS].copy() - # print("After factory cost update:") - # print(db[8:21]) - # print(db[35:37]) # debug print db = add_land_cost(db, land_cost_per_acre_0, power) - # print("After land cost update:") - # print(db[8:21]) - # print(db[35:37]) # debug print db, prev_dur = add_BOP_RP_grades(db, RB_grade_0, BOP_grade_0, power, reactor_type, n_th, mod_0) - # print("After BOP/RP grade update:") - # print(db[8:21]) - # print(db[35:37]) # debug print db = add_bulk_ordering(db, num_orders, f_22, f_2321, power) - # print("After bulk ordering update:") - # print(db[8:21]) - # print(db[35:37]) # debug print db, dur_no_delay = add_reworking_productivity( db, reactor_type, n_th, design_completion_0, ae_exp_0, N_AE, ce_exp_0, N_cons, power, prev_dur, baseline_lab_hours ) - # print("After direct cost updates:") - # print(db[8:21]) - # print(db[35:37]) # debug print return db, dur_no_delay diff --git a/src/crf/model/finance.py b/src/crf/model/finance.py index 7848e68..07056d8 100644 --- a/src/crf/model/finance.py +++ b/src/crf/model/finance.py @@ -119,4 +119,9 @@ def update_itc( db.loc[db["Title"].eq("Total Capital Investment Cost - ITC reduced (US$/kWe)"), "Total Cost (USD)"] = levelized_NCI db2 = update_high_level_costs(db, reactor_power)[COLS].copy() + print('DEBUG db2') + # print line 22 to 26 and line 37 38 + print(db2[22:27]) + # print line 37 and 38 Title Total Cost (USD) + print(db2.iloc[[37, 38]][["Title", "Total Cost (USD)"]]) return db2, itc_reduced_occ / reactor_power, levelized_NCI diff --git a/src/crf/model/indirect_cost.py b/src/crf/model/indirect_cost.py index 9ab9956..c75930f 100644 --- a/src/crf/model/indirect_cost.py +++ b/src/crf/model/indirect_cost.py @@ -32,17 +32,17 @@ def update_indirect_cost( sum_new_mat_cost = float(db.loc[mask, "Site Material Cost"].fillna(0.0).sum()) sum_new_lab_cost = float(db.loc[mask, "Site Labor Cost"].fillna(0.0).sum()) sum_new_lab_hrs = float(db.loc[mask, "Site Labor Hours"].fillna(0.0).sum()) - + print(f"DEBUG: sum_new_mat_cost={sum_new_mat_cost}, sum_new_lab_cost={sum_new_lab_cost}, sum_new_lab_hrs={sum_new_lab_hrs}") dur = float(final_construction_duration) - + print(f"DEBUG: final_construction_duration={dur}, standardization={standardization}, factor_35={factor_35}") val31 = (sum_new_mat_cost * 0.785 * sum_new_lab_hrs / dur / 160 / 1058) + sum_new_lab_cost * 0.36 val32 = sum_new_lab_cost * 0.36 * 3.661 * dur / 72 val33 = 0.04207006 * val32 val34 = 0.00354234616938 * val32 val35 = (0.27017603 * val32) * factor_35 - + print(f"DEBUG: val31={val31}, val32={val32}, val33={val33}, val34={val34}, val35={val35}") setv(db, 31, "Total Cost (USD)", val31) setv(db, 32, "Total Cost (USD)", val32) setv(db, 33, "Total Cost (USD)", val33) diff --git a/src/crf/model/learning.py b/src/crf/model/learning.py index 413f9c8..89541fa 100644 --- a/src/crf/model/learning.py +++ b/src/crf/model/learning.py @@ -62,7 +62,7 @@ def act_cons_duration_plus_delay( Design_Maturity = 2 proc_exp = min(proc_exp_0 + (2 / N_proc) * (n_th - 1), 2) - if reactor_type == "Concept B": + if reactor_type == "SFR": task_length_multiplier = 1.0 ref_construction_duration = 64 else: # Concept A @@ -88,11 +88,13 @@ def act_cons_duration_plus_delay( T_end = max(T_21, T_22, T_23, T_24, T_25, T_26) supply_chain_delay = max(T_end - ref_construction_duration, 0) + print(f"DEBUG: cons_duration_no_delay={cons_duration_no_delay}") return float(cons_duration_no_delay) + float(supply_chain_delay) def duration_learning_effect(n_th: int, standardization_0: float, actual_construction_duration_plus_delay: float): standardization = min(0.7, standardization_0) if n_th == 1 else standardization_0 + # fitted_LR_duration = 0.15 * standardization / 0.7 fitted_LR_duration = 0.103719051 * standardization / 0.7 duration_multiplier = (1 - fitted_LR_duration) ** np.log2(n_th) return float(duration_multiplier) * float(actual_construction_duration_plus_delay) diff --git a/test/test_crf_smoke.py b/test/test_crf_smoke.py index 7eded09..f23e889 100644 --- a/test/test_crf_smoke.py +++ b/test/test_crf_smoke.py @@ -10,18 +10,8 @@ def main(): - # config = { - # "inputs_xlsx": "Inputs.xlsx", - # "reactor_type": "Concept A", - # "f_22": 250_000_000, - # "f_2321": 150_000_000, - # "land_cost_per_acre_0": 22000, - # "startup_0": 16, - # "staggering_ratio": 0.75, - # } config = { "reactor_type": "HTGR", # or "SFR" - # "data_dir": "src/crf/data", # "f_22": 250_000_000, "f_2321": 150_000_000, "land_cost_per_acre_0": 22000, @@ -29,25 +19,24 @@ def main(): "staggering_ratio": 0.75, } - # baseline-style levers (raw form, like from Excel) levers = { - "num_orders": 3, - "itc_percent": 30, - "n_itc": 1, - "interest_percent": 6.5, - "design_completion_percent": 90, - "design_maturity": 2, - "proc_exp": 0.6, + "num_orders": 13, + "itc_percent": 40, + "n_itc": 4, + "interest_percent": 6, + "design_completion_percent": 80, + "design_maturity": 1, + "proc_exp": 0.5, "N_proc": 3, - "ce_exp": 0.6, + "ae_exp": 0.5, + "N_AE": 4, + "ce_exp": 1, "N_cons": 5, - "ae_exp": 0.6, - "N_AE": 4, - "standardization_percent": 70, - "modularity_code": 1, - "bop_grade_code": 1, - "rb_grade_code": 0, + "standardization_percent": 80, # 80% standardized + "modularity_code": 1, # Modularized + "bop_grade_code": 1, # Non-nuclear + "rb_grade_code": 0, # Nuclear } result = run_one_scenario(config, levers) @@ -56,7 +45,9 @@ def main(): print("avg_OCC:", result["avg_OCC"]) print("avg_TCI:", result["avg_TCI"]) print("avg_duration:", result["avg_duration"]) - + for k, v in result.items(): + if k not in ["avg_OCC", "avg_TCI", "avg_duration"]: + print(f"{k}: {v}") if __name__ == "__main__": main() From b3c95f2ef52d856405bf49dcfe17fffe6067c5fa Mon Sep 17 00:00:00 2001 From: "jia.zhou" <48254033+JiaZhou-PU@users.noreply.github.com> Date: Fri, 27 Feb 2026 19:29:17 -0600 Subject: [PATCH 07/25] add OCC NCI pass HTGR scenario 1 and 4 --- .../CostReduction_exploration_mode.ipynb | 10 +- Cost_Reduction/scenarios.ipynb | 902 +++++++++++++++++- src/crf/api.py | 46 - src/crf/model/avg_runner.py | 33 +- src/crf/model/core_accounts.py | 14 +- src/crf/model/direct_cost.py | 9 +- src/crf/model/finance.py | 12 +- src/crf/model/indirect_cost.py | 6 +- src/crf/model/learning.py | 6 +- src/crf/model/pipeline.py | 5 +- test/test_crf_smoke.py | 11 +- 11 files changed, 941 insertions(+), 113 deletions(-) diff --git a/Cost_Reduction/CostReduction_exploration_mode.ipynb b/Cost_Reduction/CostReduction_exploration_mode.ipynb index 70af906..ee05c43 100644 --- a/Cost_Reduction/CostReduction_exploration_mode.ipynb +++ b/Cost_Reduction/CostReduction_exploration_mode.ipynb @@ -2932,7 +2932,8 @@ " (db.loc[db.Account == 24, 'Site Labor Hours']).values+\\\n", " (db.loc[db.Account == 26, 'Site Labor Hours']).values\n", "\n", - "\n", + " print('update_cons_dur')\n", + " print('sum_old_lab_hrs,sum_new_lab_hrs',sum_old_lab_hrs,sum_new_lab_hrs)\n", "\n", " # # # change in labor hours for account 20\n", " labor_hour_ratio = (sum_new_lab_hrs)/sum_old_lab_hrs # note that this number can be positive or negative\n", @@ -2961,7 +2962,8 @@ " baseline_construction_duration = 64/mod_factor # months\n", " elif reactor_type == \"Concept A\": \n", " baseline_construction_duration = 100/mod_factor # months\n", - "\n", + " print('baseline_construction_duration',baseline_construction_duration)\n", + " print('labor_hour_ratio',labor_hour_ratio)\n", " actual_construction_duration = baseline_construction_duration*(0.3*labor_hour_ratio+0.7)\n", " return actual_construction_duration \n", "\n", @@ -3770,9 +3772,9 @@ "def calculate_base_cost(reactor_type, n_th, f_22, f_2321, land_cost_per_acre_0, RB_grade_0, BOP_grade_0, num_orders, design_completion_0, ae_exp_0, N_AE, ce_exp_0, N_cons, mod_0, Design_Maturity_0, proc_exp_0, N_proc, standardization_0):\n", " reactor_data = (reactor_data_read(reactor_type ))[0]\n", " reactor_power = (reactor_data_read(reactor_type ))[1]\n", - " direct_cost_updated = update_direct_cost(reactor_type, n_th, f_22, f_2321, land_cost_per_acre_0, RB_grade_0, BOP_grade_0, num_orders, design_completion_0, ae_exp_0, N_AE, ce_exp_0, N_cons)\n", - "\n", + " direct_cost_updated = update_direct_cost(reactor_type, n_th, f_22, f_2321, land_cost_per_acre_0, RB_grade_0, BOP_grade_0, num_orders, design_completion_0, ae_exp_0, N_AE, ce_exp_0, N_cons) \n", " act_con_duration = update_cons_dur(reactor_data, direct_cost_updated , mod_0)\n", + " \n", " cons_duration_plus_delay = act_cons_duration_plus_delay(reactor_type, n_th, Design_Maturity_0, proc_exp_0, N_proc, act_con_duration)\n", " final_con_duration = duration_learning_effect(n_th, standardization_0, cons_duration_plus_delay)\n", "\n", diff --git a/Cost_Reduction/scenarios.ipynb b/Cost_Reduction/scenarios.ipynb index 6d38278..d594d17 100644 --- a/Cost_Reduction/scenarios.ipynb +++ b/Cost_Reduction/scenarios.ipynb @@ -59,21 +59,673 @@ "name": "stdout", "output_type": "stream", "text": [ - "T_end,ref_construction_duration,cons_duration_no_delay,supply_chain_delay,actual_construction_duration_plus_delay\n", - "75.434 64 [80.63803108] 11.433999999999997 [92.07203108]\n", - "T_end,ref_construction_duration,cons_duration_no_delay,supply_chain_delay,actual_construction_duration_plus_delay\n", - "75.434 64 [80.63803108] 11.433999999999997 [92.07203108]\n", - "T_end,ref_construction_duration,cons_duration_no_delay,supply_chain_delay,actual_construction_duration_plus_delay\n", - "75.434 64 [80.63803108] 11.433999999999997 [92.07203108]\n", + "update_cons_dur\n", + "sum_old_lab_hrs,sum_new_lab_hrs [6629088.27585892] [12373618.31745577]\n", + "baseline_construction_duration 64.0\n", + "labor_hour_ratio [1.86656412]\n", + "update_cons_dur\n", + "sum_old_lab_hrs,sum_new_lab_hrs [6629088.27585892] [12373618.31745577]\n", + "baseline_construction_duration 64.0\n", + "labor_hour_ratio [1.86656412]\n", + "update_cons_dur\n", + "sum_old_lab_hrs,sum_new_lab_hrs [6629088.27585892] [12373618.31745577]\n", + "baseline_construction_duration 64.0\n", + "labor_hour_ratio [1.86656412]\n", + "update_cons_dur\n", + "sum_old_lab_hrs,sum_new_lab_hrs [6629088.27585892] [12373618.31745577]\n", + "baseline_construction_duration 64.0\n", + "labor_hour_ratio [1.86656412]\n", "in function update indirect\n", - "T_end,ref_construction_duration,cons_duration_no_delay,supply_chain_delay,actual_construction_duration_plus_delay\n", - "75.434 64 [80.63803108] 11.433999999999997 [92.07203108]\n", + " Account Title \\\n", + "0 10 Capitalized Pre-Construction Costs \n", + "1 11 Land & Land Rights \n", + "2 12 Site Permits \n", + "3 13 Plant Licensing \n", + "4 14 Plant Permits \n", + "5 15 Plant Studies \n", + "6 16 Plant Reports \n", + "7 18 Other Pre-Construction Costs \n", + "8 NaN NaN \n", + "9 NaN 10s - Subtotal \n", + "10 NaN 10s - $/kWe \n", + "11 NaN NaN \n", + "12 20 Capitalized Direct Costs \n", + "13 21 Structures & Improvements \n", + "14 212 Reactor Containment Building \n", + "15 213 Turbine Room and Heater Bay \n", + "16 211 plus 214 to 219 Othe Structures & Improvements \n", + "17 22 Reactor System \n", + "18 23 Energy Conversion System \n", + "19 232.1 Electricity Generation Systems \n", + "20 233 Ultimate Heat Sink \n", + "21 24 Electrical Equipment \n", + "22 25 Initial fuel inventory \n", + "23 26 Miscellaneous Equipment \n", + "24 28 Simulator \n", + "25 NaN NaN \n", + "26 NaN 20s - Subtotal \n", + "27 NaN 20s - $/kWe \n", + "28 NaN NaN \n", + "29 30 Capitalized Indirect Services Costs \n", + "30 31 Factory & Field Indirect Costs \n", + "31 32 Factory and construction supervision \n", + "32 33 Start-Up Costs \n", + "33 34 Shipping & Transportation Costs \n", + "34 35 Engineering Services \n", + "35 NaN NaN \n", + "36 NaN 30s - Subtotal \n", + "37 NaN 30s - $/kWe \n", + "38 NaN NaN \n", + "39 50 Capitalized Supplementary Costs \n", + "40 51 Taxes \n", + "41 52 Insurance \n", + "42 54 Decommissioning \n", + "43 NaN NaN \n", + "44 NaN 50s - Subtotal \n", + "45 NaN 50s - $/kWe \n", + "46 NaN NaN \n", + "47 60 Capitalized Financial Costs \n", + "48 62 Interest \n", + "49 - 60s - Subtotal \n", + "50 NaN 60s - $/kWe \n", + "51 NaN NaN \n", + "52 NaN Total Direct Capital Cost (Accounts 10 to 20) \n", + "53 NaN (Accounts 10 to 20) US$/kWe \n", + "54 NaN NaN \n", + "55 NaN Base Construction Cost (Accounts 10 to 30) \n", + "56 NaN (Accounts 10 to 30) US$/kWe \n", + "57 NaN NaN \n", + "58 NaN Total Overnight Cost (Accounts 10 to 50) \n", + "59 NaN (Accounts 10 to 50) US$/kWe \n", + "60 NaN NaN \n", + "61 NaN Total Capital Investment Cost (All Accounts) \n", + "62 NaN (Accounts 10 to 60) US$/kWe \n", + "63 NaN NaN \n", + "64 NaN Total Overnight Cost - ITC reduced \n", + "65 NaN Total Overnight Cost -ITC reduced (US$/kWe) \n", + "66 NaN NaN \n", + "67 NaN Total Capital Investment Cost - ITC reduced \n", + "68 NaN Total Capital Investment Cost - ITC reduced (U... \n", + "\n", + " Total Cost (USD) Factory Equipment Cost Site Labor Hours \\\n", + "0 NaN NaN NaN \n", + "1 1.100000e+07 NaN NaN \n", + "2 1.598891e+06 NaN NaN \n", + "3 2.438299e+07 NaN NaN \n", + "4 1.267917e+07 NaN NaN \n", + "5 1.267917e+07 NaN NaN \n", + "6 3.972186e+06 NaN NaN \n", + "7 1.267917e+07 NaN NaN \n", + "8 NaN NaN NaN \n", + "9 7.899157e+07 NaN NaN \n", + "10 2.541556e+02 NaN NaN \n", + "11 NaN NaN NaN \n", + "12 NaN NaN NaN \n", + "13 4.408830e+08 1.065590e+08 5.557506e+06 \n", + "14 2.640765e+08 9.572267e+07 3.121808e+06 \n", + "15 1.038681e+07 6.193464e+05 1.194535e+05 \n", + "16 1.664197e+08 1.021703e+07 2.316244e+06 \n", + "17 8.076069e+08 6.115216e+08 3.518498e+06 \n", + "18 2.065652e+08 1.351676e+08 1.264169e+06 \n", + "19 1.332751e+08 9.848878e+07 6.529246e+05 \n", + "20 7.329011e+07 3.667885e+07 6.112442e+05 \n", + "21 1.237824e+08 2.271225e+07 1.491382e+06 \n", + "22 2.797244e+08 NaN NaN \n", + "23 4.690726e+07 1.391771e+07 5.420643e+05 \n", + "24 7.820000e+04 NaN NaN \n", + "25 NaN NaN NaN \n", + "26 1.905547e+09 NaN NaN \n", + "27 6.131105e+03 NaN NaN \n", + "28 NaN NaN NaN \n", + "29 NaN NaN NaN \n", + "30 1.460520e+08 NaN NaN \n", + "31 5.062561e+08 NaN NaN \n", + "32 2.129823e+07 NaN NaN \n", + "33 1.793334e+06 NaN NaN \n", + "34 1.367783e+08 NaN NaN \n", + "35 NaN NaN NaN \n", + "36 8.121780e+08 NaN NaN \n", + "37 2.613185e+03 NaN NaN \n", + "38 NaN NaN NaN \n", + "39 NaN NaN NaN \n", + "40 3.510602e+06 NaN NaN \n", + "41 1.048475e+07 NaN NaN \n", + "42 1.460667e+07 NaN NaN \n", + "43 NaN NaN NaN \n", + "44 2.860203e+07 NaN NaN \n", + "45 9.202711e+01 NaN NaN \n", + "46 NaN NaN NaN \n", + "47 NaN NaN NaN \n", + "48 5.923008e+08 NaN NaN \n", + "49 5.923008e+08 NaN NaN \n", + "50 1.905730e+03 NaN NaN \n", + "51 NaN NaN NaN \n", + "52 1.984539e+09 NaN NaN \n", + "53 6.385260e+03 NaN NaN \n", + "54 NaN NaN NaN \n", + "55 2.796717e+09 NaN NaN \n", + "56 8.998446e+03 NaN NaN \n", + "57 NaN NaN NaN \n", + "58 2.825319e+09 NaN NaN \n", + "59 9.090473e+03 NaN NaN \n", + "60 NaN NaN NaN \n", + "61 3.417620e+09 NaN NaN \n", + "62 1.099620e+04 NaN NaN \n", + "63 NaN NaN NaN \n", + "64 2.437538e+09 NaN NaN \n", + "65 7.842787e+03 NaN NaN \n", + "66 NaN NaN NaN \n", + "67 3.029839e+09 NaN NaN \n", + "68 9.748517e+03 NaN NaN \n", + "\n", + " Site Labor Cost Site Material Cost \n", + "0 NaN NaN \n", + "1 NaN NaN \n", + "2 NaN NaN \n", + "3 NaN NaN \n", + "4 NaN NaN \n", + "5 NaN NaN \n", + "6 NaN NaN \n", + "7 NaN NaN \n", + "8 NaN NaN \n", + "9 NaN NaN \n", + "10 NaN NaN \n", + "11 NaN NaN \n", + "12 NaN NaN \n", + "13 2.791525e+08 5.517141e+07 \n", + "14 1.576432e+08 1.071061e+07 \n", + "15 6.159946e+06 3.607522e+06 \n", + "16 1.153494e+08 4.085327e+07 \n", + "17 1.890068e+08 7.078536e+06 \n", + "18 6.692093e+07 4.476621e+06 \n", + "19 3.478629e+07 0.000000e+00 \n", + "20 3.213464e+07 4.476621e+06 \n", + "21 8.038539e+07 2.068480e+07 \n", + "22 NaN NaN \n", + "23 2.937788e+07 3.611675e+06 \n", + "24 NaN NaN \n", + "25 NaN NaN \n", + "26 NaN NaN \n", + "27 NaN NaN \n", + "28 NaN NaN \n", + "29 NaN NaN \n", + "30 NaN NaN \n", + "31 NaN NaN \n", + "32 NaN NaN \n", + "33 NaN NaN \n", + "34 NaN NaN \n", + "35 NaN NaN \n", + "36 NaN NaN \n", + "37 NaN NaN \n", + "38 NaN NaN \n", + "39 NaN NaN \n", + "40 NaN NaN \n", + "41 NaN NaN \n", + "42 NaN NaN \n", + "43 NaN NaN \n", + "44 NaN NaN \n", + "45 NaN NaN \n", + "46 NaN NaN \n", + "47 NaN NaN \n", + "48 NaN NaN \n", + "49 NaN NaN \n", + "50 NaN NaN \n", + "51 NaN NaN \n", + "52 NaN NaN \n", + "53 NaN NaN \n", + "54 NaN NaN \n", + "55 NaN NaN \n", + "56 NaN NaN \n", + "57 NaN NaN \n", + "58 NaN NaN \n", + "59 NaN NaN \n", + "60 NaN NaN \n", + "61 NaN NaN \n", + "62 NaN NaN \n", + "63 NaN NaN \n", + "64 NaN NaN \n", + "65 NaN NaN \n", + "66 NaN NaN \n", + "67 NaN NaN \n", + "68 NaN NaN \n", + "update_cons_dur\n", + "sum_old_lab_hrs,sum_new_lab_hrs [6629088.27585892] [12373618.31745577]\n", + "baseline_construction_duration 64.0\n", + "labor_hour_ratio [1.86656412]\n", "in function update indirect\n", - "T_end,ref_construction_duration,cons_duration_no_delay,supply_chain_delay,actual_construction_duration_plus_delay\n", - "75.434 64 [80.63803108] 11.433999999999997 [92.07203108]\n", + " Account Title \\\n", + "0 10 Capitalized Pre-Construction Costs \n", + "1 11 Land & Land Rights \n", + "2 12 Site Permits \n", + "3 13 Plant Licensing \n", + "4 14 Plant Permits \n", + "5 15 Plant Studies \n", + "6 16 Plant Reports \n", + "7 18 Other Pre-Construction Costs \n", + "8 NaN NaN \n", + "9 NaN 10s - Subtotal \n", + "10 NaN 10s - $/kWe \n", + "11 NaN NaN \n", + "12 20 Capitalized Direct Costs \n", + "13 21 Structures & Improvements \n", + "14 212 Reactor Containment Building \n", + "15 213 Turbine Room and Heater Bay \n", + "16 211 plus 214 to 219 Othe Structures & Improvements \n", + "17 22 Reactor System \n", + "18 23 Energy Conversion System \n", + "19 232.1 Electricity Generation Systems \n", + "20 233 Ultimate Heat Sink \n", + "21 24 Electrical Equipment \n", + "22 25 Initial fuel inventory \n", + "23 26 Miscellaneous Equipment \n", + "24 28 Simulator \n", + "25 NaN NaN \n", + "26 NaN 20s - Subtotal \n", + "27 NaN 20s - $/kWe \n", + "28 NaN NaN \n", + "29 30 Capitalized Indirect Services Costs \n", + "30 31 Factory & Field Indirect Costs \n", + "31 32 Factory and construction supervision \n", + "32 33 Start-Up Costs \n", + "33 34 Shipping & Transportation Costs \n", + "34 35 Engineering Services \n", + "35 NaN NaN \n", + "36 NaN 30s - Subtotal \n", + "37 NaN 30s - $/kWe \n", + "38 NaN NaN \n", + "39 50 Capitalized Supplementary Costs \n", + "40 51 Taxes \n", + "41 52 Insurance \n", + "42 54 Decommissioning \n", + "43 NaN NaN \n", + "44 NaN 50s - Subtotal \n", + "45 NaN 50s - $/kWe \n", + "46 NaN NaN \n", + "47 60 Capitalized Financial Costs \n", + "48 62 Interest \n", + "49 - 60s - Subtotal \n", + "50 NaN 60s - $/kWe \n", + "51 NaN NaN \n", + "52 NaN Total Direct Capital Cost (Accounts 10 to 20) \n", + "53 NaN (Accounts 10 to 20) US$/kWe \n", + "54 NaN NaN \n", + "55 NaN Base Construction Cost (Accounts 10 to 30) \n", + "56 NaN (Accounts 10 to 30) US$/kWe \n", + "57 NaN NaN \n", + "58 NaN Total Overnight Cost (Accounts 10 to 50) \n", + "59 NaN (Accounts 10 to 50) US$/kWe \n", + "60 NaN NaN \n", + "61 NaN Total Capital Investment Cost (All Accounts) \n", + "62 NaN (Accounts 10 to 60) US$/kWe \n", + "63 NaN NaN \n", + "64 NaN Total Overnight Cost - ITC reduced \n", + "65 NaN Total Overnight Cost -ITC reduced (US$/kWe) \n", + "66 NaN NaN \n", + "67 NaN Total Capital Investment Cost - ITC reduced \n", + "68 NaN Total Capital Investment Cost - ITC reduced (U... \n", + "\n", + " Total Cost (USD) Factory Equipment Cost Site Labor Hours \\\n", + "0 NaN NaN NaN \n", + "1 1.100000e+07 NaN NaN \n", + "2 1.598891e+06 NaN NaN \n", + "3 2.438299e+07 NaN NaN \n", + "4 1.267917e+07 NaN NaN \n", + "5 1.267917e+07 NaN NaN \n", + "6 3.972186e+06 NaN NaN \n", + "7 1.267917e+07 NaN NaN \n", + "8 NaN NaN NaN \n", + "9 7.899157e+07 NaN NaN \n", + "10 2.541556e+02 NaN NaN \n", + "11 NaN NaN NaN \n", + "12 NaN NaN NaN \n", + "13 4.408830e+08 1.065590e+08 5.557506e+06 \n", + "14 2.640765e+08 9.572267e+07 3.121808e+06 \n", + "15 1.038681e+07 6.193464e+05 1.194535e+05 \n", + "16 1.664197e+08 1.021703e+07 2.316244e+06 \n", + "17 8.076069e+08 6.115216e+08 3.518498e+06 \n", + "18 2.065652e+08 1.351676e+08 1.264169e+06 \n", + "19 1.332751e+08 9.848878e+07 6.529246e+05 \n", + "20 7.329011e+07 3.667885e+07 6.112442e+05 \n", + "21 1.237824e+08 2.271225e+07 1.491382e+06 \n", + "22 2.797244e+08 NaN NaN \n", + "23 4.690726e+07 1.391771e+07 5.420643e+05 \n", + "24 7.820000e+04 NaN NaN \n", + "25 NaN NaN NaN \n", + "26 1.905547e+09 NaN NaN \n", + "27 6.131105e+03 NaN NaN \n", + "28 NaN NaN NaN \n", + "29 NaN NaN NaN \n", + "30 1.460520e+08 NaN NaN \n", + "31 5.062561e+08 NaN NaN \n", + "32 2.129823e+07 NaN NaN \n", + "33 1.793334e+06 NaN NaN \n", + "34 1.367783e+08 NaN NaN \n", + "35 NaN NaN NaN \n", + "36 8.121780e+08 NaN NaN \n", + "37 2.613185e+03 NaN NaN \n", + "38 NaN NaN NaN \n", + "39 NaN NaN NaN \n", + "40 3.510602e+06 NaN NaN \n", + "41 1.048475e+07 NaN NaN \n", + "42 1.460667e+07 NaN NaN \n", + "43 NaN NaN NaN \n", + "44 2.860203e+07 NaN NaN \n", + "45 9.202711e+01 NaN NaN \n", + "46 NaN NaN NaN \n", + "47 NaN NaN NaN \n", + "48 5.923008e+08 NaN NaN \n", + "49 5.923008e+08 NaN NaN \n", + "50 1.905730e+03 NaN NaN \n", + "51 NaN NaN NaN \n", + "52 1.984539e+09 NaN NaN \n", + "53 6.385260e+03 NaN NaN \n", + "54 NaN NaN NaN \n", + "55 2.796717e+09 NaN NaN \n", + "56 8.998446e+03 NaN NaN \n", + "57 NaN NaN NaN \n", + "58 2.825319e+09 NaN NaN \n", + "59 9.090473e+03 NaN NaN \n", + "60 NaN NaN NaN \n", + "61 3.417620e+09 NaN NaN \n", + "62 1.099620e+04 NaN NaN \n", + "63 NaN NaN NaN \n", + "64 2.437538e+09 NaN NaN \n", + "65 7.842787e+03 NaN NaN \n", + "66 NaN NaN NaN \n", + "67 3.029839e+09 NaN NaN \n", + "68 9.748517e+03 NaN NaN \n", + "\n", + " Site Labor Cost Site Material Cost \n", + "0 NaN NaN \n", + "1 NaN NaN \n", + "2 NaN NaN \n", + "3 NaN NaN \n", + "4 NaN NaN \n", + "5 NaN NaN \n", + "6 NaN NaN \n", + "7 NaN NaN \n", + "8 NaN NaN \n", + "9 NaN NaN \n", + "10 NaN NaN \n", + "11 NaN NaN \n", + "12 NaN NaN \n", + "13 2.791525e+08 5.517141e+07 \n", + "14 1.576432e+08 1.071061e+07 \n", + "15 6.159946e+06 3.607522e+06 \n", + "16 1.153494e+08 4.085327e+07 \n", + "17 1.890068e+08 7.078536e+06 \n", + "18 6.692093e+07 4.476621e+06 \n", + "19 3.478629e+07 0.000000e+00 \n", + "20 3.213464e+07 4.476621e+06 \n", + "21 8.038539e+07 2.068480e+07 \n", + "22 NaN NaN \n", + "23 2.937788e+07 3.611675e+06 \n", + "24 NaN NaN \n", + "25 NaN NaN \n", + "26 NaN NaN \n", + "27 NaN NaN \n", + "28 NaN NaN \n", + "29 NaN NaN \n", + "30 NaN NaN \n", + "31 NaN NaN \n", + "32 NaN NaN \n", + "33 NaN NaN \n", + "34 NaN NaN \n", + "35 NaN NaN \n", + "36 NaN NaN \n", + "37 NaN NaN \n", + "38 NaN NaN \n", + "39 NaN NaN \n", + "40 NaN NaN \n", + "41 NaN NaN \n", + "42 NaN NaN \n", + "43 NaN NaN \n", + "44 NaN NaN \n", + "45 NaN NaN \n", + "46 NaN NaN \n", + "47 NaN NaN \n", + "48 NaN NaN \n", + "49 NaN NaN \n", + "50 NaN NaN \n", + "51 NaN NaN \n", + "52 NaN NaN \n", + "53 NaN NaN \n", + "54 NaN NaN \n", + "55 NaN NaN \n", + "56 NaN NaN \n", + "57 NaN NaN \n", + "58 NaN NaN \n", + "59 NaN NaN \n", + "60 NaN NaN \n", + "61 NaN NaN \n", + "62 NaN NaN \n", + "63 NaN NaN \n", + "64 NaN NaN \n", + "65 NaN NaN \n", + "66 NaN NaN \n", + "67 NaN NaN \n", + "68 NaN NaN \n", + "update_cons_dur\n", + "sum_old_lab_hrs,sum_new_lab_hrs [6629088.27585892] [12373618.31745577]\n", + "baseline_construction_duration 64.0\n", + "labor_hour_ratio [1.86656412]\n", "in function update indirect\n", - "T_end,ref_construction_duration,cons_duration_no_delay,supply_chain_delay,actual_construction_duration_plus_delay\n", - "75.434 64 [80.63803108] 11.433999999999997 [92.07203108]\n", + " Account Title \\\n", + "0 10 Capitalized Pre-Construction Costs \n", + "1 11 Land & Land Rights \n", + "2 12 Site Permits \n", + "3 13 Plant Licensing \n", + "4 14 Plant Permits \n", + "5 15 Plant Studies \n", + "6 16 Plant Reports \n", + "7 18 Other Pre-Construction Costs \n", + "8 NaN NaN \n", + "9 NaN 10s - Subtotal \n", + "10 NaN 10s - $/kWe \n", + "11 NaN NaN \n", + "12 20 Capitalized Direct Costs \n", + "13 21 Structures & Improvements \n", + "14 212 Reactor Containment Building \n", + "15 213 Turbine Room and Heater Bay \n", + "16 211 plus 214 to 219 Othe Structures & Improvements \n", + "17 22 Reactor System \n", + "18 23 Energy Conversion System \n", + "19 232.1 Electricity Generation Systems \n", + "20 233 Ultimate Heat Sink \n", + "21 24 Electrical Equipment \n", + "22 25 Initial fuel inventory \n", + "23 26 Miscellaneous Equipment \n", + "24 28 Simulator \n", + "25 NaN NaN \n", + "26 NaN 20s - Subtotal \n", + "27 NaN 20s - $/kWe \n", + "28 NaN NaN \n", + "29 30 Capitalized Indirect Services Costs \n", + "30 31 Factory & Field Indirect Costs \n", + "31 32 Factory and construction supervision \n", + "32 33 Start-Up Costs \n", + "33 34 Shipping & Transportation Costs \n", + "34 35 Engineering Services \n", + "35 NaN NaN \n", + "36 NaN 30s - Subtotal \n", + "37 NaN 30s - $/kWe \n", + "38 NaN NaN \n", + "39 50 Capitalized Supplementary Costs \n", + "40 51 Taxes \n", + "41 52 Insurance \n", + "42 54 Decommissioning \n", + "43 NaN NaN \n", + "44 NaN 50s - Subtotal \n", + "45 NaN 50s - $/kWe \n", + "46 NaN NaN \n", + "47 60 Capitalized Financial Costs \n", + "48 62 Interest \n", + "49 - 60s - Subtotal \n", + "50 NaN 60s - $/kWe \n", + "51 NaN NaN \n", + "52 NaN Total Direct Capital Cost (Accounts 10 to 20) \n", + "53 NaN (Accounts 10 to 20) US$/kWe \n", + "54 NaN NaN \n", + "55 NaN Base Construction Cost (Accounts 10 to 30) \n", + "56 NaN (Accounts 10 to 30) US$/kWe \n", + "57 NaN NaN \n", + "58 NaN Total Overnight Cost (Accounts 10 to 50) \n", + "59 NaN (Accounts 10 to 50) US$/kWe \n", + "60 NaN NaN \n", + "61 NaN Total Capital Investment Cost (All Accounts) \n", + "62 NaN (Accounts 10 to 60) US$/kWe \n", + "63 NaN NaN \n", + "64 NaN Total Overnight Cost - ITC reduced \n", + "65 NaN Total Overnight Cost -ITC reduced (US$/kWe) \n", + "66 NaN NaN \n", + "67 NaN Total Capital Investment Cost - ITC reduced \n", + "68 NaN Total Capital Investment Cost - ITC reduced (U... \n", + "\n", + " Total Cost (USD) Factory Equipment Cost Site Labor Hours \\\n", + "0 NaN NaN NaN \n", + "1 1.100000e+07 NaN NaN \n", + "2 1.598891e+06 NaN NaN \n", + "3 2.438299e+07 NaN NaN \n", + "4 1.267917e+07 NaN NaN \n", + "5 1.267917e+07 NaN NaN \n", + "6 3.972186e+06 NaN NaN \n", + "7 1.267917e+07 NaN NaN \n", + "8 NaN NaN NaN \n", + "9 7.899157e+07 NaN NaN \n", + "10 2.541556e+02 NaN NaN \n", + "11 NaN NaN NaN \n", + "12 NaN NaN NaN \n", + "13 4.408830e+08 1.065590e+08 5.557506e+06 \n", + "14 2.640765e+08 9.572267e+07 3.121808e+06 \n", + "15 1.038681e+07 6.193464e+05 1.194535e+05 \n", + "16 1.664197e+08 1.021703e+07 2.316244e+06 \n", + "17 8.076069e+08 6.115216e+08 3.518498e+06 \n", + "18 2.065652e+08 1.351676e+08 1.264169e+06 \n", + "19 1.332751e+08 9.848878e+07 6.529246e+05 \n", + "20 7.329011e+07 3.667885e+07 6.112442e+05 \n", + "21 1.237824e+08 2.271225e+07 1.491382e+06 \n", + "22 2.797244e+08 NaN NaN \n", + "23 4.690726e+07 1.391771e+07 5.420643e+05 \n", + "24 7.820000e+04 NaN NaN \n", + "25 NaN NaN NaN \n", + "26 1.905547e+09 NaN NaN \n", + "27 6.131105e+03 NaN NaN \n", + "28 NaN NaN NaN \n", + "29 NaN NaN NaN \n", + "30 1.460520e+08 NaN NaN \n", + "31 5.062561e+08 NaN NaN \n", + "32 2.129823e+07 NaN NaN \n", + "33 1.793334e+06 NaN NaN \n", + "34 1.367783e+08 NaN NaN \n", + "35 NaN NaN NaN \n", + "36 8.121780e+08 NaN NaN \n", + "37 2.613185e+03 NaN NaN \n", + "38 NaN NaN NaN \n", + "39 NaN NaN NaN \n", + "40 3.510602e+06 NaN NaN \n", + "41 1.048475e+07 NaN NaN \n", + "42 1.460667e+07 NaN NaN \n", + "43 NaN NaN NaN \n", + "44 2.860203e+07 NaN NaN \n", + "45 9.202711e+01 NaN NaN \n", + "46 NaN NaN NaN \n", + "47 NaN NaN NaN \n", + "48 5.923008e+08 NaN NaN \n", + "49 5.923008e+08 NaN NaN \n", + "50 1.905730e+03 NaN NaN \n", + "51 NaN NaN NaN \n", + "52 1.984539e+09 NaN NaN \n", + "53 6.385260e+03 NaN NaN \n", + "54 NaN NaN NaN \n", + "55 2.796717e+09 NaN NaN \n", + "56 8.998446e+03 NaN NaN \n", + "57 NaN NaN NaN \n", + "58 2.825319e+09 NaN NaN \n", + "59 9.090473e+03 NaN NaN \n", + "60 NaN NaN NaN \n", + "61 3.417620e+09 NaN NaN \n", + "62 1.099620e+04 NaN NaN \n", + "63 NaN NaN NaN \n", + "64 2.437538e+09 NaN NaN \n", + "65 7.842787e+03 NaN NaN \n", + "66 NaN NaN NaN \n", + "67 3.029839e+09 NaN NaN \n", + "68 9.748517e+03 NaN NaN \n", + "\n", + " Site Labor Cost Site Material Cost \n", + "0 NaN NaN \n", + "1 NaN NaN \n", + "2 NaN NaN \n", + "3 NaN NaN \n", + "4 NaN NaN \n", + "5 NaN NaN \n", + "6 NaN NaN \n", + "7 NaN NaN \n", + "8 NaN NaN \n", + "9 NaN NaN \n", + "10 NaN NaN \n", + "11 NaN NaN \n", + "12 NaN NaN \n", + "13 2.791525e+08 5.517141e+07 \n", + "14 1.576432e+08 1.071061e+07 \n", + "15 6.159946e+06 3.607522e+06 \n", + "16 1.153494e+08 4.085327e+07 \n", + "17 1.890068e+08 7.078536e+06 \n", + "18 6.692093e+07 4.476621e+06 \n", + "19 3.478629e+07 0.000000e+00 \n", + "20 3.213464e+07 4.476621e+06 \n", + "21 8.038539e+07 2.068480e+07 \n", + "22 NaN NaN \n", + "23 2.937788e+07 3.611675e+06 \n", + "24 NaN NaN \n", + "25 NaN NaN \n", + "26 NaN NaN \n", + "27 NaN NaN \n", + "28 NaN NaN \n", + "29 NaN NaN \n", + "30 NaN NaN \n", + "31 NaN NaN \n", + "32 NaN NaN \n", + "33 NaN NaN \n", + "34 NaN NaN \n", + "35 NaN NaN \n", + "36 NaN NaN \n", + "37 NaN NaN \n", + "38 NaN NaN \n", + "39 NaN NaN \n", + "40 NaN NaN \n", + "41 NaN NaN \n", + "42 NaN NaN \n", + "43 NaN NaN \n", + "44 NaN NaN \n", + "45 NaN NaN \n", + "46 NaN NaN \n", + "47 NaN NaN \n", + "48 NaN NaN \n", + "49 NaN NaN \n", + "50 NaN NaN \n", + "51 NaN NaN \n", + "52 NaN NaN \n", + "53 NaN NaN \n", + "54 NaN NaN \n", + "55 NaN NaN \n", + "56 NaN NaN \n", + "57 NaN NaN \n", + "58 NaN NaN \n", + "59 NaN NaN \n", + "60 NaN NaN \n", + "61 NaN NaN \n", + "62 NaN NaN \n", + "63 NaN NaN \n", + "64 NaN NaN \n", + "65 NaN NaN \n", + "66 NaN NaN \n", + "67 NaN NaN \n", + "68 NaN NaN \n", + "update_cons_dur\n", + "sum_old_lab_hrs,sum_new_lab_hrs [6629088.27585892] [12373618.31745577]\n", + "baseline_construction_duration 64.0\n", + "labor_hour_ratio [1.86656412]\n", "in function update indirect\n" ] } @@ -210,8 +862,222 @@ "output_type": "stream", "text": [ "4\n", - "T_end,ref_construction_duration,cons_duration_no_delay,supply_chain_delay,actual_construction_duration_plus_delay\n", - "111.4278125 100 [121.63844064] 11.427812500000002 [133.06625314]\n", + " Account Title \\\n", + "0 10 Capitalized Pre-Construction Costs \n", + "1 11 Land & Land Rights \n", + "2 12 Site Permits \n", + "3 13 Plant Licensing \n", + "4 14 Plant Permits \n", + "5 15 Plant Studies \n", + "6 16 Plant Reports \n", + "7 18 Other Pre-Construction Costs \n", + "8 NaN NaN \n", + "9 NaN 10s - Subtotal \n", + "10 NaN 10s - $/kWe \n", + "11 NaN NaN \n", + "12 20 Capitalized Direct Costs \n", + "13 21 Structures & Improvements \n", + "14 212 Reactor Containment Building \n", + "15 213 Turbine Room and Heater Bay \n", + "16 211 plus 214 to 219 Othe Structures & Improvements \n", + "17 22 Reactor System \n", + "18 23 Energy Conversion System \n", + "19 232.1 Electricity Generation Systems \n", + "20 233 Ultimate Heat Sink \n", + "21 24 Electrical Equipment \n", + "22 25 Initial fuel inventory \n", + "23 26 Miscellaneous Equipment \n", + "24 28 Simulator \n", + "25 NaN NaN \n", + "26 NaN 20s - Subtotal \n", + "27 NaN 20s - $/kWe \n", + "28 NaN NaN \n", + "29 30 Capitalized Indirect Services Costs \n", + "30 31 Factory & Field Indirect Costs \n", + "31 32 Factory and construction supervision \n", + "32 33 Start-Up Costs \n", + "33 34 Shipping & Transportation Costs \n", + "34 35 Engineering Services \n", + "35 NaN NaN \n", + "36 NaN 30s - Subtotal \n", + "37 NaN 30s - $/kWe \n", + "38 NaN NaN \n", + "39 50 Capitalized Supplementary Costs \n", + "40 51 Taxes \n", + "41 52 Insurance \n", + "42 54 Decommissioning \n", + "43 NaN NaN \n", + "44 NaN 50s - Subtotal \n", + "45 NaN 50s - $/kWe \n", + "46 NaN NaN \n", + "47 60 Capitalized Financial Costs \n", + "48 62 Interest \n", + "49 - 60s - Subtotal \n", + "50 NaN 60s - $/kWe \n", + "51 NaN NaN \n", + "52 NaN Total Direct Capital Cost (Accounts 10 to 20) \n", + "53 NaN (Accounts 10 to 20) US$/kWe \n", + "54 NaN NaN \n", + "55 NaN Base Construction Cost (Accounts 10 to 30) \n", + "56 NaN (Accounts 10 to 30) US$/kWe \n", + "57 NaN NaN \n", + "58 NaN Total Overnight Cost (Accounts 10 to 50) \n", + "59 NaN (Accounts 10 to 50) US$/kWe \n", + "60 NaN NaN \n", + "61 NaN Total Capital Investment Cost (All Accounts) \n", + "62 NaN (Accounts 10 to 60) US$/kWe \n", + "63 NaN NaN \n", + "64 NaN Total Overnight Cost - ITC reduced \n", + "65 NaN Total Overnight Cost -ITC reduced (US$/kWe) \n", + "66 NaN NaN \n", + "67 NaN Total Capital Investment Cost - ITC reduced \n", + "68 NaN Total Capital Investment Cost - ITC reduced (U... \n", + "\n", + " Total Cost (USD) Factory Equipment Cost Site Labor Hours \\\n", + "0 NaN NaN NaN \n", + "1 1.500000e+07 NaN NaN \n", + "2 0.000000e+00 NaN NaN \n", + "3 1.070098e+08 NaN NaN \n", + "4 4.721019e+06 NaN NaN \n", + "5 0.000000e+00 NaN NaN \n", + "6 0.000000e+00 NaN NaN \n", + "7 3.619448e+07 NaN NaN \n", + "8 NaN NaN NaN \n", + "9 1.629253e+08 NaN NaN \n", + "10 1.542853e+02 NaN NaN \n", + "11 NaN NaN NaN \n", + "12 NaN NaN NaN \n", + "13 1.533300e+09 1.365362e+08 2.090920e+07 \n", + "14 6.966448e+08 9.992741e+07 9.638431e+06 \n", + "15 1.522573e+07 1.036695e+06 1.749114e+05 \n", + "16 8.214295e+08 3.557211e+07 1.109586e+07 \n", + "17 1.975878e+09 1.478440e+09 8.719725e+06 \n", + "18 4.755064e+08 3.072326e+08 3.008178e+06 \n", + "19 3.301050e+08 2.344884e+08 1.794686e+06 \n", + "20 1.454013e+08 7.274425e+07 1.213492e+06 \n", + "21 2.358203e+08 4.326955e+07 2.841261e+06 \n", + "22 4.516865e+08 NaN 0.000000e+00 \n", + "23 3.594357e+08 9.942946e+07 4.282531e+06 \n", + "24 0.000000e+00 NaN NaN \n", + "25 NaN NaN NaN \n", + "26 5.031627e+09 NaN NaN \n", + "27 4.764798e+03 NaN NaN \n", + "28 NaN NaN NaN \n", + "29 NaN NaN NaN \n", + "30 6.913711e+08 NaN NaN \n", + "31 2.734393e+09 NaN NaN \n", + "32 1.150361e+08 NaN NaN \n", + "33 9.686167e+06 NaN NaN \n", + "34 7.387675e+08 NaN NaN \n", + "35 NaN NaN NaN \n", + "36 4.289254e+09 NaN NaN \n", + "37 4.061793e+03 NaN NaN \n", + "38 NaN NaN NaN \n", + "39 NaN NaN NaN \n", + "40 1.875000e+05 NaN NaN \n", + "41 3.820433e+07 NaN NaN \n", + "42 8.141760e+06 NaN NaN \n", + "43 NaN NaN NaN \n", + "44 4.653359e+07 NaN NaN \n", + "45 4.406590e+01 NaN NaN \n", + "46 NaN NaN NaN \n", + "47 NaN NaN NaN \n", + "48 3.202162e+09 NaN NaN \n", + "49 3.202162e+09 NaN NaN \n", + "50 3.032350e+03 NaN NaN \n", + "51 NaN NaN NaN \n", + "52 5.194552e+09 NaN NaN \n", + "53 4.919084e+03 NaN NaN \n", + "54 NaN NaN NaN \n", + "55 9.483806e+09 NaN NaN \n", + "56 8.980877e+03 NaN NaN \n", + "57 NaN NaN NaN \n", + "58 9.530340e+09 NaN NaN \n", + "59 9.024943e+03 NaN NaN \n", + "60 NaN NaN NaN \n", + "61 1.273250e+10 NaN NaN \n", + "62 1.205729e+04 NaN NaN \n", + "63 NaN NaN NaN \n", + "64 8.699310e+09 NaN NaN \n", + "65 8.237983e+03 NaN NaN \n", + "66 NaN NaN NaN \n", + "67 1.190147e+10 NaN NaN \n", + "68 1.127033e+04 NaN NaN \n", + "\n", + " Site Labor Cost Site Material Cost \n", + "0 NaN NaN \n", + "1 NaN NaN \n", + "2 NaN NaN \n", + "3 NaN NaN \n", + "4 NaN NaN \n", + "5 NaN NaN \n", + "6 NaN NaN \n", + "7 NaN NaN \n", + "8 NaN NaN \n", + "9 NaN NaN \n", + "10 NaN NaN \n", + "11 NaN NaN \n", + "12 NaN NaN \n", + "13 1.045892e+09 3.508716e+08 \n", + "14 4.700620e+08 1.266554e+08 \n", + "15 9.024686e+06 5.164350e+06 \n", + "16 5.668055e+08 2.190519e+08 \n", + "17 4.656677e+08 3.177007e+07 \n", + "18 1.594144e+08 8.859309e+06 \n", + "19 9.561666e+07 0.000000e+00 \n", + "20 6.379777e+07 8.859309e+06 \n", + "21 1.531438e+08 3.940702e+07 \n", + "22 0.000000e+00 0.000000e+00 \n", + "23 2.310455e+08 2.896080e+07 \n", + "24 NaN NaN \n", + "25 NaN NaN \n", + "26 NaN NaN \n", + "27 NaN NaN \n", + "28 NaN NaN \n", + "29 NaN NaN \n", + "30 NaN NaN \n", + "31 NaN NaN \n", + "32 NaN NaN \n", + "33 NaN NaN \n", + "34 NaN NaN \n", + "35 NaN NaN \n", + "36 NaN NaN \n", + "37 NaN NaN \n", + "38 NaN NaN \n", + "39 NaN NaN \n", + "40 NaN NaN \n", + "41 NaN NaN \n", + "42 NaN NaN \n", + "43 NaN NaN \n", + "44 NaN NaN \n", + "45 NaN NaN \n", + "46 NaN NaN \n", + "47 NaN NaN \n", + "48 NaN NaN \n", + "49 NaN NaN \n", + "50 NaN NaN \n", + "51 NaN NaN \n", + "52 NaN NaN \n", + "53 NaN NaN \n", + "54 NaN NaN \n", + "55 NaN NaN \n", + "56 NaN NaN \n", + "57 NaN NaN \n", + "58 NaN NaN \n", + "59 NaN NaN \n", + "60 NaN NaN \n", + "61 NaN NaN \n", + "62 NaN NaN \n", + "63 NaN NaN \n", + "64 NaN NaN \n", + "65 NaN NaN \n", + "66 NaN NaN \n", + "67 NaN NaN \n", + "68 NaN NaN \n", + "update_cons_dur\n", + "sum_old_lab_hrs,sum_new_lab_hrs [23099589.55901049] [39760892.80720583]\n", + "baseline_construction_duration 100.0\n", + "labor_hour_ratio [1.72128135]\n", "in function update indirect\n" ] }, @@ -454,6 +1320,14 @@ "metadata": {}, "outputs": [], "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0b4dfbc1-3f1e-48c5-b089-035bb954f4c4", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/src/crf/api.py b/src/crf/api.py index db91a33..79b1e92 100644 --- a/src/crf/api.py +++ b/src/crf/api.py @@ -1,50 +1,4 @@ import csv -import pickle -import numpy as np - -from .io.excel_inputs import InputStore -from .io.excel_levers import read_levers_sheet -from .sampling.sampler import sample_levers -from .sampling.lever_schema import ( - STATIC_KEYS, - attach_internal_ids, - sample_column_to_levers, - static_row_from_levers, -) -from .sampling.postprocess import apply_itc_rounding -from .model.avg_runner import run_avg_all_units -from .utils.serialize import write_csv_row, stream_pickle_dump - - -def normalize_levers(levers: dict) -> dict: - """Convert raw lever dict to model-ready normalized values.""" - def label01(x, zero_label, one_label): - if x == 0: return zero_label - if x == 1: return one_label - return x - - return { - "num_orders": int(levers["num_orders"]), - "ITC_0": float(levers["itc_percent"]) / 100.0, - "n_ITC": levers["n_itc"], - "interest_rate_0": float(levers["interest_percent"]) / 100.0, - "design_completion_0": float(levers["design_completion_percent"]) / 100.0, - "Design_Maturity_0": levers["design_maturity"], - "proc_exp_0": levers["proc_exp"], - "N_proc": levers["N_proc"], - "ce_exp_0": levers["ce_exp"], - "N_cons": levers["N_cons"], - "ae_exp_0": levers["ae_exp"], - "N_AE": levers["N_AE"], - "standardization_0": float(levers["standardization_percent"]) / 100.0, - "mod_0": label01(levers["modularity_code"], "stick_built", "modularized"), - "BOP_grade_0": label01(levers["bop_grade_code"], "nuclear", "non_nuclear"), - "RB_grade_0": label01(levers["rb_grade_code"], "nuclear", "non_nuclear"), - } - - -import csv -import pickle import numpy as np from .io.excel_inputs import InputStore diff --git a/src/crf/model/avg_runner.py b/src/crf/model/avg_runner.py index c8dc0b7..a0272f3 100644 --- a/src/crf/model/avg_runner.py +++ b/src/crf/model/avg_runner.py @@ -1,5 +1,6 @@ import numpy as np from .pipeline import calculate_final_result +# Ignore runtime warning def run_avg_all_units(config: dict, inp: dict, store): """ @@ -7,25 +8,37 @@ def run_avg_all_units(config: dict, inp: dict, store): OCC_i, TCI_i, duration_i plus summary metrics. """ num_orders = int(inp["num_orders"]) - OCC, TCI, DUR = [], [], [] + n_itc = int(inp["n_ITC"]) + + OCC, NETOCC, TCI, NCI, DUR = [], [], [], [], [] + for n_th in range(1, num_orders + 1): - _, occ, tci, dur = calculate_final_result(config, inp, store, n_th=n_th) + _, occ, netocc, tci, nci, dur = calculate_final_result(config, inp, store, n_th=n_th) OCC.append(occ) TCI.append(tci) DUR.append(dur) + if n_th <= n_itc: + NETOCC.append(netocc) + NCI.append(nci) + OCC = np.array(OCC, dtype=float) + NETOCC = np.array(NETOCC, dtype=float) TCI = np.array(TCI, dtype=float) + NCI = np.array(NCI, dtype=float) DUR = np.array(DUR, dtype=float) # summaries occLastUnit = float(OCC[-1]) TCILastUnit = float(TCI[-1]) durationsLastUnit = float(DUR[-1]) - - avg_OCC = float(np.mean(OCC)) - avg_TCI = float(np.mean(TCI)) + if n_itc > 0: + avg_OCC = float(np.mean(NETOCC)) + avg_TCI = float(np.mean(NCI)) + else: + avg_OCC = float(np.mean(OCC)) + avg_TCI = float(np.mean(TCI)) avg_duration = float(np.mean(DUR)) final_startup_duration = max(7, config["startup_0"] * (1 - 0.3) ** np.log2(num_orders)) @@ -36,11 +49,15 @@ def run_avg_all_units(config: dict, inp: dict, store): # expand arrays to OCC_i / TCI_i / duration_i out = {} for i, v in enumerate(OCC): - out[f"OCC_{i}"] = float(v) + out[f"OCC_{i+1}"] = float(v) + for i, v in enumerate(NETOCC): + out[f"NETOCC_{i+1}"] = float(v) for i, v in enumerate(TCI): - out[f"TCI_{i}"] = float(v) + out[f"TCI_{i+1}"] = float(v) + for i, v in enumerate(NCI): + out[f"NCI_{i+1}"] = float(v) for i, v in enumerate(DUR): - out[f"duration_{i}"] = float(v) + out[f"duration_{i+1}"] = float(v) out.update({ "cons_duration_cumulative_wz_startup": float(cons_duration_cumulative_wz_startup), diff --git a/src/crf/model/core_accounts.py b/src/crf/model/core_accounts.py index d0ced42..adb8e02 100644 --- a/src/crf/model/core_accounts.py +++ b/src/crf/model/core_accounts.py @@ -204,6 +204,10 @@ def sum_lab_hrs(db: pd.DataFrame) -> float: ) def update_cons_duration(db0: pd.DataFrame, db1: pd.DataFrame, ref_duration: float) -> float: + """Calculate new construction duration based on change in labor hours for accounts 21, 22, 23, 24, and 26. + The formula is: new_duration = 0.3 * labor_hours_delta * ref_duration + ref_duration, where labor_hours_delta = (new_hours - old_hours) / old_hours. + this function should be used before the standardization effect is applied. + """ def _sum_hours(db): return float( db.loc[db["Account"].astype(str).str.strip().isin(["21","22","23","24","26"]), "Site Labor Hours"] @@ -213,10 +217,6 @@ def _sum_hours(db): sum_old = _sum_hours(db0) sum_new = _sum_hours(db1) - - if sum_old == 0: - return float(ref_duration) - lab_delta = (sum_new - sum_old) / sum_old return float(0.3 * lab_delta * ref_duration + ref_duration) @@ -227,6 +227,8 @@ def update_cons_duration_2( prev_cons_duration: float, baseline_lab_hours: float ) -> float: + """Calculate new construction duration based on change in labor hours for accounts 21, 22, 23, 24, and 26. This should be used after the standardization effect is applied, so the labor hours change should be compared to the baseline labor hours instead of the previous labor hours. NOTE I think I should merge this with the previous function and just pass in the baseline labor hours as an argument. + """ def _sum_hours(db): return float( db.loc[db["Account"].astype(str).str.strip().isin(["21","22","23","24","26"]), "Site Labor Hours"] @@ -236,10 +238,6 @@ def _sum_hours(db): sum_old = _sum_hours(db0) sum_new = _sum_hours(db1) - - if baseline_lab_hours == 0: - return float(prev_cons_duration) - lab_delta = (sum_new - sum_old) / float(baseline_lab_hours) return float(0.3 * lab_delta * ref_duration + float(prev_cons_duration)) diff --git a/src/crf/model/direct_cost.py b/src/crf/model/direct_cost.py index 38eb3bf..cf14a55 100644 --- a/src/crf/model/direct_cost.py +++ b/src/crf/model/direct_cost.py @@ -19,7 +19,9 @@ "Factory Equipment Cost", "Site Labor Hours", "Site Labor Cost", "Site Material Cost" ] -# exclude account 25 because it is the initial fuel cost and should not be affected by rework +# exclude account 25 because it is the initial fuel cost and +# should not be affected by rework. Those accounts are end level accounts +# that do not have sub-accounts ACCT_DIRECT = [212, 213, "211 plus 214 to 219", 22, "232.1", 233, 24, 26] @@ -87,13 +89,10 @@ def add_BOP_RP_grades( # duration update from grade change duration_ref = 125 if reactor_type == "HTGR" else 80 # duration_ref = 100 if reactor_type == "HTGR" else 64 - print(f"DEBUG: reactor_type={reactor_type}, duration_ref={duration_ref}") new_dur = update_cons_duration(df, db2, duration_ref) - print(f"DEBUG: new_dur before modulized change={new_dur}") # modularity factor on duration; for n>=2 assume modularized mod = mod_0 if n_th == 1 else "modularized" mod_factor = 0.8 if mod == "modularized" else 1.0 - return db2, new_dur * mod_factor @@ -164,9 +163,7 @@ def add_reworking_productivity( setv(db, acct, "Site Labor Cost", float(getv(df, acct, "Site Labor Cost")) * rework / productivity) db2 = update_high_level_costs(db, power)[COLS].copy() - print(f"DEBUG: ref_duration={ref_duration}, prev_cons_duration={prev_cons_duration}, baseline_lab_hours={baseline_lab_hours}") new_dur = float(update_cons_duration_2(df, db2, ref_duration, prev_cons_duration, baseline_lab_hours)) - print(f"DEBUG: new_dur={new_dur}") return db2, new_dur diff --git a/src/crf/model/finance.py b/src/crf/model/finance.py index 07056d8..fae90ff 100644 --- a/src/crf/model/finance.py +++ b/src/crf/model/finance.py @@ -3,7 +3,7 @@ import numpy as np import pandas as pd - +from ..utils.df_ops import setv from .core_accounts import update_high_level_costs, ITC_reduction_factor COLS = [ @@ -76,12 +76,12 @@ def update_interest_cost( startup = startup_0 else: startup = max(7, startup_0 * (1 - 0.3) ** np.log2(n_th)) - int_exp_startup = (tot_int_exp_construction + tot_overnight_cost) * ((1 + interest_rate) ** (startup / 12)) \ - (tot_int_exp_construction + tot_overnight_cost) db = df.copy() - db.loc[db["Account"].eq(62), "Total Cost (USD)"] = float(int_exp_startup + tot_int_exp_construction) + int_curved = float(tot_int_exp_construction + int_exp_startup) + setv(db, "62", "Total Cost (USD)", int_curved) db2 = update_high_level_costs(db, power)[COLS].copy() @@ -108,7 +108,6 @@ def update_itc( itc_reduced_occ = tot_overnight_cost * itc_factor occ_reduction = tot_overnight_cost - itc_reduced_occ - # Titles must exist (same as your original) db.loc[db["Title"].eq("Total Overnight Cost - ITC reduced"), "Total Cost (USD)"] = itc_reduced_occ db.loc[db["Title"].eq("Total Overnight Cost -ITC reduced (US$/kWe)"), "Total Cost (USD)"] = itc_reduced_occ / reactor_power db.loc[db["Title"].eq("Total Capital Investment Cost - ITC reduced"), "Total Cost (USD)"] = tot_cap_investment - occ_reduction @@ -119,9 +118,4 @@ def update_itc( db.loc[db["Title"].eq("Total Capital Investment Cost - ITC reduced (US$/kWe)"), "Total Cost (USD)"] = levelized_NCI db2 = update_high_level_costs(db, reactor_power)[COLS].copy() - print('DEBUG db2') - # print line 22 to 26 and line 37 38 - print(db2[22:27]) - # print line 37 and 38 Title Total Cost (USD) - print(db2.iloc[[37, 38]][["Title", "Total Cost (USD)"]]) return db2, itc_reduced_occ / reactor_power, levelized_NCI diff --git a/src/crf/model/indirect_cost.py b/src/crf/model/indirect_cost.py index c75930f..738af69 100644 --- a/src/crf/model/indirect_cost.py +++ b/src/crf/model/indirect_cost.py @@ -32,17 +32,17 @@ def update_indirect_cost( sum_new_mat_cost = float(db.loc[mask, "Site Material Cost"].fillna(0.0).sum()) sum_new_lab_cost = float(db.loc[mask, "Site Labor Cost"].fillna(0.0).sum()) sum_new_lab_hrs = float(db.loc[mask, "Site Labor Hours"].fillna(0.0).sum()) - print(f"DEBUG: sum_new_mat_cost={sum_new_mat_cost}, sum_new_lab_cost={sum_new_lab_cost}, sum_new_lab_hrs={sum_new_lab_hrs}") + # print(f"DEBUG: sum_new_mat_cost={sum_new_mat_cost}, sum_new_lab_cost={sum_new_lab_cost}, sum_new_lab_hrs={sum_new_lab_hrs}") dur = float(final_construction_duration) - print(f"DEBUG: final_construction_duration={dur}, standardization={standardization}, factor_35={factor_35}") + # print(f"DEBUG: final_construction_duration={dur}, standardization={standardization}, factor_35={factor_35}") val31 = (sum_new_mat_cost * 0.785 * sum_new_lab_hrs / dur / 160 / 1058) + sum_new_lab_cost * 0.36 val32 = sum_new_lab_cost * 0.36 * 3.661 * dur / 72 val33 = 0.04207006 * val32 val34 = 0.00354234616938 * val32 val35 = (0.27017603 * val32) * factor_35 - print(f"DEBUG: val31={val31}, val32={val32}, val33={val33}, val34={val34}, val35={val35}") + # print(f"DEBUG: val31={val31}, val32={val32}, val33={val33}, val34={val34}, val35={val35}") setv(db, 31, "Total Cost (USD)", val31) setv(db, 32, "Total Cost (USD)", val32) setv(db, 33, "Total Cost (USD)", val33) diff --git a/src/crf/model/learning.py b/src/crf/model/learning.py index 89541fa..1885401 100644 --- a/src/crf/model/learning.py +++ b/src/crf/model/learning.py @@ -86,15 +86,11 @@ def act_cons_duration_plus_delay( T_26 = 0.21 * (B_21 + D) + B_26 + D T_end = max(T_21, T_22, T_23, T_24, T_25, T_26) - - supply_chain_delay = max(T_end - ref_construction_duration, 0) - print(f"DEBUG: cons_duration_no_delay={cons_duration_no_delay}") + supply_chain_delay = max(T_end - ref_construction_duration, 0) return float(cons_duration_no_delay) + float(supply_chain_delay) - def duration_learning_effect(n_th: int, standardization_0: float, actual_construction_duration_plus_delay: float): standardization = min(0.7, standardization_0) if n_th == 1 else standardization_0 - # fitted_LR_duration = 0.15 * standardization / 0.7 fitted_LR_duration = 0.103719051 * standardization / 0.7 duration_multiplier = (1 - fitted_LR_duration) ** np.log2(n_th) return float(duration_multiplier) * float(actual_construction_duration_plus_delay) diff --git a/src/crf/model/pipeline.py b/src/crf/model/pipeline.py index 139b288..19acda8 100644 --- a/src/crf/model/pipeline.py +++ b/src/crf/model/pipeline.py @@ -57,6 +57,7 @@ def calculate_final_result(config: dict, inp: dict, store, n_th: int): n_th=n_th, power=power, ) + org_occ = float(tot_occ/power) + org_tci = float(tot_cap/power) final_df, net_occ, nci = update_itc(with_interest, tot_occ, tot_cap, n_th, inp["ITC_0"], inp["n_ITC"], power) - - return final_df, net_occ, nci, float(final_dur) + return final_df, org_occ, net_occ, org_tci, nci, float(final_dur) diff --git a/test/test_crf_smoke.py b/test/test_crf_smoke.py index f23e889..9845b82 100644 --- a/test/test_crf_smoke.py +++ b/test/test_crf_smoke.py @@ -19,7 +19,6 @@ def main(): "staggering_ratio": 0.75, } - # baseline-style levers (raw form, like from Excel) levers = { "num_orders": 13, "itc_percent": 40, @@ -40,14 +39,10 @@ def main(): } result = run_one_scenario(config, levers) - - print("\nTest Successful") - print("avg_OCC:", result["avg_OCC"]) - print("avg_TCI:", result["avg_TCI"]) - print("avg_duration:", result["avg_duration"]) for k, v in result.items(): - if k not in ["avg_OCC", "avg_TCI", "avg_duration"]: - print(f"{k}: {v}") + print(f"{k}: {v}") + print("\nTest Successful") + if __name__ == "__main__": main() From 91f0809b4936d7cdb2e5fdf8ecf3290328cdde87 Mon Sep 17 00:00:00 2001 From: "jia.zhou" <48254033+JiaZhou-PU@users.noreply.github.com> Date: Sat, 28 Feb 2026 18:08:45 -0600 Subject: [PATCH 08/25] HTGR pass --- src/crf/api.py | 45 +++++++++----- src/crf/data/HTGR_baseline.csv | 5 -- src/crf/data/SFR_baseline.csv | 104 +++++++++++---------------------- src/crf/model/avg_runner.py | 49 ++++++++++++++-- src/crf/model/core_accounts.py | 41 ++++++++++--- src/crf/model/pipeline.py | 4 -- test/test_crf_smoke.py | 53 +++++++++++++++-- 7 files changed, 189 insertions(+), 112 deletions(-) diff --git a/src/crf/api.py b/src/crf/api.py index 79b1e92..c08bca0 100644 --- a/src/crf/api.py +++ b/src/crf/api.py @@ -47,7 +47,7 @@ def run_one_scenario(config: dict, levers: dict) -> dict: levers = apply_itc_rounding(levers) inp = normalize_levers(levers) - result = run_avg_all_units(config=config, inp=inp, store=store) + result = run_avg_all_units(config=config, inp=inp, store=store, details=True) static_vals = static_row_from_levers(levers) return {**static_vals, **result} @@ -68,37 +68,49 @@ def run_sampling_from_excel( # headers: build from max num_orders in sampled set max_orders = int(np.max(samples[0, :])) - headers = ( - STATIC_KEYS - + [f"OCC_{i}" for i in range(max_orders)] - + [f"TCI_{i}" for i in range(max_orders)] - + [f"duration_{i}" for i in range(max_orders)] - + [ + headers_wo_itc = ( + STATIC_KEYS + + [f"OCC_{i}" for i in range(max_orders)] + + [f"TCI_{i}" for i in range(max_orders)] + + [f"duration_{i}" for i in range(max_orders)] + + [ "cons_duration_cumulative_wz_startup", "occLastUnit", "TCILastUnit", "durationsLastUnit", "avg_OCC", "avg_TCI", "avg_duration", - ] + ] + ) + headers_w_itc = ( + STATIC_KEYS + + [f"OCC_{i}" for i in range(max_orders)] + + [f"NETOCC_{i}" for i in range(max_orders)] + + [f"TCI_{i}" for i in range(max_orders)] + + [f"NCI_{i}" for i in range(max_orders)] + + [f"duration_{i}" for i in range(max_orders)] + + [ + "cons_duration_cumulative_wz_startup", + "occLastUnit", "TCILastUnit", "durationsLastUnit", + "avg_OCC", "avg_TCI", "avg_duration", + ] ) with open(out_csv, "w", newline="", encoding="utf-8") as csv_f, open(out_pkl, "wb") as pkl_f: writer = csv.DictWriter(csv_f, fieldnames=headers) writer.writeheader() - for i in range(n_samples): - # levers = unpack_sample_column(samples[:, i]) - # row = run_one_scenario(config, levers) - - levers_raw = sample_column_to_levers(samples, levers_df, i) row = run_one_scenario(config, levers_raw) - + n_itc = int(levers_raw["n_itc"]) + if n_itc > 0: + headers = headers_w_itc + else: + headers = headers_wo_itc write_csv_row(writer, row, headers) stream_pickle_dump(row, pkl_f) levers = apply_itc_rounding(levers) inp = normalize_levers(levers) - result = run_avg_all_units(config=config, inp=inp, store=store) + result = run_avg_all_units(config=config, inp=inp, store=store, details=False) static_vals = static_row_from_levers(levers) return {**static_vals, **result} @@ -118,7 +130,8 @@ def run_sampling_from_excel( # headers: build from max num_orders in sampled set max_orders = int(np.max(samples[0, :])) - + + # NOTE if n_itc is not 0 we might need to change this into NETOCC and NCI for the first n_itc units and OCC/TCI for the rest. headers = ( STATIC_KEYS + [f"OCC_{i}" for i in range(max_orders)] diff --git a/src/crf/data/HTGR_baseline.csv b/src/crf/data/HTGR_baseline.csv index 501a4d9..d1e18b1 100644 --- a/src/crf/data/HTGR_baseline.csv +++ b/src/crf/data/HTGR_baseline.csv @@ -1,5 +1,4 @@ Account,Title,Total Cost (USD),Factory Equipment Cost,Site Labor Hours,Site Labor Cost,Site Material Cost -10,Capitalized Pre-Construction Costs,,,,, 11,Land & Land Rights,15000000,,,, 12,Site Permits,0,,,, 13,Plant Licensing,107009771.98697068,,,, @@ -7,7 +6,6 @@ Account,Title,Total Cost (USD),Factory Equipment Cost,Site Labor Hours,Site Labo 15,Plant Studies,0,,,, 16,Plant Reports,0,,,, 18,Other Pre-Construction Costs,36194481.701475374,,,, -20,Capitalized Direct Costs,,,,, 21,Structures & Improvements,914902741.8258162,90263521.11,11824706.17,591583080.9,233056139.8 212,Reactor Containment Building,413396747.7312481,65728880.25,5420558.48,264358228.1,83309639.36 213,Turbine Room and Heater Bay,15257054.820128009,1136504.843,163947.4218,8458991.226,5661558.752 @@ -20,15 +18,12 @@ Account,Title,Total Cost (USD),Factory Equipment Cost,Site Labor Hours,Site Labo 25,Initial fuel inventory,451686470.5748004,451686470.5748004,0,0,0 26,Miscellaneous Equipment,214388490.9241457,65401342.47,2408453.335,129937709.9,19049438.52 28,Simulator,0,0,0,0,0 -30,Capitalized Indirect Services Costs,,,,, 31,Factory & Field Indirect Costs,691371059.4821017,,,, 32,Factory and construction supervision,2734393138.1498117,,,, 33,Start-Up Costs,115036083.38555087,,,, 34,Shipping & Transportation Costs,9686167.142,,,, 35,Engineering Services,738767482.8,,,, -50,Capitalized Supplementary Costs,,,,, 51,Taxes,187500,,,, 52,Insurance,38204331.42647941,,,, 54,Decommissioning ,8141760,,,, -60,Capitalized Financial Costs,,,,, 62,Interest ,3202161760.899656,,,, \ No newline at end of file diff --git a/src/crf/data/SFR_baseline.csv b/src/crf/data/SFR_baseline.csv index e8e39b4..fe23002 100644 --- a/src/crf/data/SFR_baseline.csv +++ b/src/crf/data/SFR_baseline.csv @@ -1,70 +1,34 @@ -Account,Title,Total Cost (USD),Factory Equipment Cost,Site Labor Hours,Site Labor Cost,Site Material Cost -10,Capitalized Pre-Construction Costs,,,,, -11,Land & Land Rights,11000000.0,,,, -12,Site Permits,1598890.7521620637,,,, -13,Plant Licensing,24382988.437500004,,,, -14,Plant Permits,12679166.666666668,,,, -15,Plant Studies,12679166.666666668,,,, -16,Plant Reports,3972185.8507187515,,,, -18,Other Pre-Construction Costs,12679166.666666668,,,, -,,,,,, -,10s - Subtotal,78991565.04038084,,,, -,10s - $/kWe,254.15561467304,,,, -,,,,,, -20,Capitalized Direct Costs,,,,, -21,Structures & Improvements,244678343.4084428,64350800.8593666,2899406.53716477,145691461.90599,34636080.6430862 -212,Reactor Containment Building,145108927.86932483,57583605.3695275,1605670.3085077,81082169.3410215,6443153.15877585 -213,Turbine Room and Heater Bay,9518417.644642511,620963.974754291,102399.473629672,5280509.16508102,3616944.5048072 -211 plus 214 to 219,Othe Structures & Improvements,90050997.89447546,6146231.515084816,1191336.755027398,59328783.39988749,24575982.97950315 -22,Reactor System,661334796.7533815,559862843.635677,1809703.51462689,97213738.5700561,4258214.54764847 -23,Energy Conversion System,238844021.25290728,189802947.4112764,874095.8194883969,46348086.0900651,2692987.75156579 -232.1,Electricity Generation Systems,197558120.73250762,167738159.268431,559708.577958467,29819961.4640766,0.0 -233,Ultimate Heat Sink,41285900.52039969,22064788.1428454,314387.24152993,16528124.6259885,2692987.75156579 -24,Electrical Equipment,67451644.91245879,13662940.7771874,767077.153200817,41345408.9752344,12443295.160037 -25,Initial fuel inventory,279724434.0,,,, -26,Miscellaneous Equipment,25655315.754125603,8372438.25664057,278805.251378048,15110212.4141026,2172665.08338243 -28,Simulator,78200.0,,,, -,,,,,, -,20s - Subtotal,1517766756.081316,,,, -,20s - $/kWe,4883.419421111054,,,, -,,,,,, -30,Capitalized Indirect Services Costs,,,,, -31,Factory & Field Indirect Costs,146051996.53692532,,,, -32,Factory and construction supervision,506256124.80995834,,,, -33,Start-Up Costs,21298225.546122435,,,, -34,Shipping & Transportation Costs,1793334.4599472817,,,, -35,Engineering Services,136778270.01496467,,,, -,,,,,, -,30s - Subtotal,812177951.367918,,,, -,30s - $/kWe,2613.1851717114478,,,, -,,,,,, -50,Capitalized Supplementary Costs,,,,, -51,Taxes,3510602.4683867483,,,, -52,Insurance,10484751.183521552,,,, -54,Decommissioning ,14606673.443824586,,,, -,,,,,, -,50s - Subtotal,28602027.095732886,,,, -,50s - $/kWe,92.02711420763477,,,, -,,,,,, -60,Capitalized Financial Costs,,,,, -62,Interest ,592300799.7656411,,,, --,60s - Subtotal,592300799.7656411,,,, -,60s - $/kWe,1905.729728975679,,,, -,,,,,, -,Total Direct Capital Cost (Accounts 10 to 20),1596758321.121697,,,, -,(Accounts 10 to 20) US$/kWe,5137.575035784095,,,, -,,,,,, -,Base Construction Cost (Accounts 10 to 30),2408936272.489615,,,, -,(Accounts 10 to 30) US$/kWe,7750.760207495543,,,, -,,,,,, -,Total Overnight Cost (Accounts 10 to 50),2437538299.5853477,,,, -,(Accounts 10 to 50) US$/kWe,7842.787321703177,,,, -,,,,,, -,Total Capital Investment Cost (All Accounts),3029839099.350989,,,, -,(Accounts 10 to 60) US$/kWe,9748.517050678856,,,, -,,,,,, -,Total Overnight Cost - ITC reduced,2437538299.5853477,,,, -,Total Overnight Cost -ITC reduced (US$/kWe),7842.787321703177,,,, -,,,,,, -,Total Capital Investment Cost - ITC reduced,3029839099.350989,,,, -,Total Capital Investment Cost - ITC reduced (US$/kWe),9748.517050678856,,,, +Account,Title,Total Cost (USD),Factory Equipment Cost,Site Labor Hours,Site Labor Cost,Site Material Cost +10,Capitalized Pre-Construction Costs,,,,, +11,Land & Land Rights,11000000,,,, +12,Site Permits,1598890.7521620637,,,, +13,Plant Licensing,24382988.437500004,,,, +14,Plant Permits,12679166.666666668,,,, +15,Plant Studies,12679166.666666668,,,, +16,Plant Reports,3972185.8507187515,,,, +18,Other Pre-Construction Costs,12679166.666666668,,,, +20,Capitalized Direct Costs,,,,, +21,Structures & Improvements,244678343.4084428,64350800.86,2899406.537,145691461.9,34636080.64 +212,Reactor Containment Building,145108927.86932483,57583605.37,1605670.309,81082169.34,6443153.159 +213,Turbine Room and Heater Bay,9518417.644642511,620963.9748,102399.4736,5280509.165,3616944.505 +211 plus 214 to 219,Othe Structures & Improvements,90050997.89447546,6146231.515084816,1191336.755027398,59328783.39988749,24575982.97950315 +22,Reactor System,661334796.7533815,559862843.6,1809703.515,97213738.57,4258214.548 +23,Energy Conversion System,238844021.25290728,189802947.4112764,874095.8194883969,46348086.09,2692987.752 +232.1,Electricity Generation Systems,197558120.73250762,167738159.3,559708.578,29819961.46,0 +233,Ultimate Heat Sink,41285900.52039969,22064788.14,314387.2415,16528124.63,2692987.752 +24,Electrical Equipment,67451644.91245879,13662940.78,767077.1532,41345408.98,12443295.16 +25,Initial fuel inventory,279724434,,,, +26,Miscellaneous Equipment,25655315.754125603,8372438.257,278805.2514,15110212.41,2172665.083 +28,Simulator,78200,,,, +30,Capitalized Indirect Services Costs,,,,, +31,Factory & Field Indirect Costs,146051996.53692532,,,, +32,Factory and construction supervision,506256124.80995834,,,, +33,Start-Up Costs,21298225.546122435,,,, +34,Shipping & Transportation Costs,1793334.4599472817,,,, +35,Engineering Services,136778270.01496467,,,, +50,Capitalized Supplementary Costs,,,,, +51,Taxes,3510602.4683867483,,,, +52,Insurance,10484751.183521552,,,, +54,Decommissioning ,14606673.443824586,,,, +60,Capitalized Financial Costs,,,,, +62,Interest ,592300799.7656411,,,, \ No newline at end of file diff --git a/src/crf/model/avg_runner.py b/src/crf/model/avg_runner.py index a0272f3..3203cbc 100644 --- a/src/crf/model/avg_runner.py +++ b/src/crf/model/avg_runner.py @@ -2,7 +2,7 @@ from .pipeline import calculate_final_result # Ignore runtime warning -def run_avg_all_units(config: dict, inp: dict, store): +def run_avg_all_units(config: dict, inp: dict, store, details=False): """ Runs n_th=1..num_orders and returns a CSV-friendly dict: OCC_i, TCI_i, duration_i plus summary metrics. @@ -11,31 +11,53 @@ def run_avg_all_units(config: dict, inp: dict, store): n_itc = int(inp["n_ITC"]) OCC, NETOCC, TCI, NCI, DUR = [], [], [], [], [] + if details: + # Stores the 10s, 20s, 30s, 50s and 60s, with 20s equipment, material, labor costs + D10s, D20s, D30s, D50s, D60s = [], [], [], [], [] + D20_equip, D20_mat, D20_labor = [], [], [] for n_th in range(1, num_orders + 1): - _, occ, netocc, tci, nci, dur = calculate_final_result(config, inp, store, n_th=n_th) + final_df, occ, netocc, tci, nci, dur = calculate_final_result(config, inp, store, n_th=n_th) OCC.append(occ) TCI.append(tci) DUR.append(dur) if n_th <= n_itc: NETOCC.append(netocc) NCI.append(nci) - + if details: + D10s.append(float(final_df.loc[final_df["Title"].eq("10s - $/kWe"), "Total Cost (USD)"].iloc[0])) + D20s.append(float(final_df.loc[final_df["Title"].eq("20s - $/kWe"), "Total Cost (USD)"].iloc[0])) + D30s.append(float(final_df.loc[final_df["Title"].eq("30s - $/kWe"), "Total Cost (USD)"].iloc[0])) + D50s.append(float(final_df.loc[final_df["Title"].eq("50s - $/kWe"), "Total Cost (USD)"].iloc[0])) + D60s.append(float(final_df.loc[final_df["Title"].eq("60s - $/kWe"), "Total Cost (USD)"].iloc[0])) + D20_equip.append(float(final_df.loc[final_df["Title"].eq("20s - $/kWe"), "Factory Equipment Cost"].iloc[0])) + D20_mat.append(float(final_df.loc[final_df["Title"].eq("20s - $/kWe"), "Site Material Cost"].iloc[0])) + D20_labor.append(float(final_df.loc[final_df["Title"].eq("20s - $/kWe"), "Site Labor Cost"].iloc[0])) OCC = np.array(OCC, dtype=float) NETOCC = np.array(NETOCC, dtype=float) TCI = np.array(TCI, dtype=float) NCI = np.array(NCI, dtype=float) DUR = np.array(DUR, dtype=float) + if details: + D10s = np.array(D10s, dtype=float) + D20s = np.array(D20s, dtype=float) + D30s = np.array(D30s, dtype=float) + D50s = np.array(D50s, dtype=float) + D60s = np.array(D60s, dtype=float) + D20_equip = np.array(D20_equip, dtype=float) + D20_mat = np.array(D20_mat, dtype=float) + D20_labor = np.array(D20_labor, dtype=float) # summaries occLastUnit = float(OCC[-1]) TCILastUnit = float(TCI[-1]) durationsLastUnit = float(DUR[-1]) if n_itc > 0: - avg_OCC = float(np.mean(NETOCC)) - avg_TCI = float(np.mean(NCI)) + # from 1st to n_ITC-th unit (inclusive) have ITC, so use NETOCC/NCI for avg; from (n_ITC+1)-th to last unit have no ITC, so use OCC/TCI for avg; + avg_OCC = float(np.mean(NETOCC))*(n_itc/num_orders) + float(np.mean(OCC[n_itc:]))*((num_orders-n_itc)/num_orders) + avg_TCI = float(np.mean(NCI))*(n_itc/num_orders) + float(np.mean(TCI[n_itc:]))*((num_orders-n_itc)/num_orders) else: avg_OCC = float(np.mean(OCC)) avg_TCI = float(np.mean(TCI)) @@ -58,6 +80,23 @@ def run_avg_all_units(config: dict, inp: dict, store): out[f"NCI_{i+1}"] = float(v) for i, v in enumerate(DUR): out[f"duration_{i+1}"] = float(v) + if details: + for i, v in enumerate(D10s): + out[f"D10s_{i+1}"] = float(v) + for i, v in enumerate(D20s): + out[f"D20s_{i+1}"] = float(v) + for i, v in enumerate(D30s): + out[f"D30s_{i+1}"] = float(v) + for i, v in enumerate(D50s): + out[f"D50s_{i+1}"] = float(v) + for i, v in enumerate(D60s): + out[f"D60s_{i+1}"] = float(v) + for i, v in enumerate(D20_equip): + out[f"D20_equip_{i+1}"] = float(v) + for i, v in enumerate(D20_mat): + out[f"D20_mat_{i+1}"] = float(v) + for i, v in enumerate(D20_labor): + out[f"D20_labor_{i+1}"] = float(v) out.update({ "cons_duration_cumulative_wz_startup": float(cons_duration_cumulative_wz_startup), diff --git a/src/crf/model/core_accounts.py b/src/crf/model/core_accounts.py index adb8e02..1d28be2 100644 --- a/src/crf/model/core_accounts.py +++ b/src/crf/model/core_accounts.py @@ -17,8 +17,8 @@ # Final results ("Total Direct Capital Cost (Accounts 10 to 20)", None), ("(Accounts 10 to 20) US$/kWe", None), - ("Base Construction Cost (Accounts 10 to 30)", None), - ("(Accounts 10 to 30) US$/kWe", None), + ("Base Construction Cost (Accounts 20 to 30)", None), + ("(Accounts 20 to 30) US$/kWe", None), ("Total Overnight Cost (Accounts 10 to 50)", None), ("(Accounts 10 to 50) US$/kWe", None), ("Total Capital Investment Cost (All Accounts)", None), @@ -137,6 +137,22 @@ def update_high_level_costs(db: pd.DataFrame, reactor_power: float) -> pd.DataFr db["Account"].isin(["21", "22", "23", "24", "25", "26", "28"]), "Total Cost (USD)" ].fillna(0.0).sum() + db.loc[db["Title"] == "20s - Subtotal", "Factory Equipment Cost"] = db.loc[ + db["Account"].isin(["21", "22", "23", "24", "25", "26", "28"]), "Factory Equipment Cost" + ].fillna(0.0).sum() + + db.loc[db["Title"] == "20s - Subtotal", "Site Material Cost"] = db.loc[ + db["Account"].isin(["21", "22", "23", "24", "25", "26", "28"]), "Site Material Cost" + ].fillna(0.0).sum() + + db.loc[db["Title"] == "20s - Subtotal", "Site Labor Cost"] = db.loc[ + db["Account"].isin(["21", "22", "23", "24", "25", "26", "28"]), "Site Labor Cost" + ].fillna(0.0).sum() + + db.loc[db["Title"] == "20s - Subtotal", "Site Labor Hours"] = db.loc[ + db["Account"].isin(["21", "22", "23", "24", "25", "26", "28"]), "Site Labor Hours" + ].fillna(0.0).sum() + db.loc[db["Title"] == "30s - Subtotal", "Total Cost (USD)"] = db.loc[ db["Account"].isin(["31", "32", "33", "34", "35"]), "Total Cost (USD)" ].fillna(0.0).sum() @@ -154,6 +170,16 @@ def update_high_level_costs(db: pd.DataFrame, reactor_power: float) -> pd.DataFr db.loc[db["Title"] == f"{t} - $/kWe", "Total Cost (USD)"] = ( db.loc[db["Title"] == f"{t} - Subtotal", "Total Cost (USD)"].values / reactor_power ) + # 20s equipment, material, labor costs per kWe + db.loc[db["Title"] == "20s - $/kWe", "Factory Equipment Cost"] = ( + db.loc[db["Title"] == "20s - Subtotal", "Factory Equipment Cost"].values / reactor_power + ) + db.loc[db["Title"] == "20s - $/kWe", "Site Material Cost"] = ( + db.loc[db["Title"] == "20s - Subtotal", "Site Material Cost"].values / reactor_power + ) + db.loc[db["Title"] == "20s - $/kWe", "Site Labor Cost"] = ( + db.loc[db["Title"] == "20s - Subtotal", "Site Labor Cost"].values / reactor_power + ) # final rollups db.loc[db["Title"] == "Total Direct Capital Cost (Accounts 10 to 20)", "Total Cost (USD)"] = ( @@ -161,13 +187,14 @@ def update_high_level_costs(db: pd.DataFrame, reactor_power: float) -> pd.DataFr + db.loc[db["Title"] == "20s - Subtotal", "Total Cost (USD)"].values ) - db.loc[db["Title"] == "Base Construction Cost (Accounts 10 to 30)", "Total Cost (USD)"] = ( - db.loc[db["Title"] == "Total Direct Capital Cost (Accounts 10 to 20)", "Total Cost (USD)"].values + db.loc[db["Title"] == "Base Construction Cost (Accounts 20 to 30)", "Total Cost (USD)"] = ( + + db.loc[db["Title"] == "20s - Subtotal", "Total Cost (USD)"].values + db.loc[db["Title"] == "30s - Subtotal", "Total Cost (USD)"].values ) db.loc[db["Title"] == "Total Overnight Cost (Accounts 10 to 50)", "Total Cost (USD)"] = ( - db.loc[db["Title"] == "Base Construction Cost (Accounts 10 to 30)", "Total Cost (USD)"].values + db.loc[db["Title"] == "10s - Subtotal", "Total Cost (USD)"].values + + db.loc[db["Title"] == "Base Construction Cost (Accounts 20 to 30)", "Total Cost (USD)"].values + db.loc[db["Title"] == "50s - Subtotal", "Total Cost (USD)"].values ) @@ -180,8 +207,8 @@ def update_high_level_costs(db: pd.DataFrame, reactor_power: float) -> pd.DataFr db.loc[db["Title"] == "(Accounts 10 to 20) US$/kWe", "Total Cost (USD)"] = ( db.loc[db["Title"] == "Total Direct Capital Cost (Accounts 10 to 20)", "Total Cost (USD)"].values / reactor_power ) - db.loc[db["Title"] == "(Accounts 10 to 30) US$/kWe", "Total Cost (USD)"] = ( - db.loc[db["Title"] == "Base Construction Cost (Accounts 10 to 30)", "Total Cost (USD)"].values / reactor_power + db.loc[db["Title"] == "(Accounts 20 to 30) US$/kWe", "Total Cost (USD)"] = ( + db.loc[db["Title"] == "Base Construction Cost (Accounts 20 to 30)", "Total Cost (USD)"].values / reactor_power ) db.loc[db["Title"] == "(Accounts 10 to 50) US$/kWe", "Total Cost (USD)"] = ( db.loc[db["Title"] == "Total Overnight Cost (Accounts 10 to 50)", "Total Cost (USD)"].values / reactor_power diff --git a/src/crf/model/pipeline.py b/src/crf/model/pipeline.py index 19acda8..f11e263 100644 --- a/src/crf/model/pipeline.py +++ b/src/crf/model/pipeline.py @@ -9,7 +9,6 @@ def calculate_final_result(config: dict, inp: dict, store, n_th: int): Returns: (final_df, levelized_net_OCC, levelized_NCI, final_construction_duration_scalar) """ base0, power = store.get_baseline(config["reactor_type"]) - # direct updates (returns df + duration without supply chain delay) direct_df, dur_no_delay = update_direct_cost( store=store, @@ -28,7 +27,6 @@ def calculate_final_result(config: dict, inp: dict, store, n_th: int): N_cons=inp["N_cons"], mod_0=inp["mod_0"], ) - # duration: add delay + learning dur_plus_delay = act_cons_duration_plus_delay( reactor_type=config["reactor_type"], @@ -42,10 +40,8 @@ def calculate_final_result(config: dict, inp: dict, store, n_th: int): # learning on direct costs direct_plus_learning = learning_effect(direct_df, n_th, inp["standardization_0"], power) - # indirect costs with_indirect = update_indirect_cost(n_th, inp["standardization_0"], direct_plus_learning, final_dur, power) - # insurance + interest + ITC with_insurance = insurance_cost_update(base0, with_indirect, power) with_interest, tot_occ, tot_cap = update_interest_cost( diff --git a/test/test_crf_smoke.py b/test/test_crf_smoke.py index 9845b82..6c05b36 100644 --- a/test/test_crf_smoke.py +++ b/test/test_crf_smoke.py @@ -2,7 +2,7 @@ import os import sys - +import pandas as pd src_path = os.path.abspath(os.path.join(os.pardir, 'src')) sys.path.insert(0, src_path) from crf import run_one_scenario @@ -11,7 +11,8 @@ def main(): config = { - "reactor_type": "HTGR", # or "SFR" + # "reactor_type": "HTGR", # or "SFR" + "reactor_type": "SFR", # or "SFR" "f_22": 250_000_000, "f_2321": 150_000_000, "land_cost_per_acre_0": 22000, @@ -21,8 +22,8 @@ def main(): levers = { "num_orders": 13, - "itc_percent": 40, - "n_itc": 4, + "itc_percent": 0, + "n_itc": 0, "interest_percent": 6, "design_completion_percent": 80, "design_maturity": 1, @@ -39,8 +40,50 @@ def main(): } result = run_one_scenario(config, levers) + # transfer the result into more readable format + # all OCC, NETOCC, TCI, NCI can be put into a table with columns plant number and metric name, but for simplicity we just print them out here. + results_df = pd.DataFrame({"Plant_number": list(range(1, int(levers["num_orders"])+1))}) for k, v in result.items(): - print(f"{k}: {v}") + if k.startswith("OCC_"): + results_df[f"OCC"] = results_df["Plant_number"].apply(lambda x: result.get(f"OCC_{x}", None)) + elif k.startswith("NETOCC_"): + results_df[f"NETOCC"] = results_df["Plant_number"].apply(lambda x: result.get(f"NETOCC_{x}", None)) + elif k.startswith("TCI_"): + results_df[f"TCI"] = results_df["Plant_number"].apply(lambda x: result.get(f"TCI_{x}", None)) + elif k.startswith("NCI_"): + results_df[f"NCI"] = results_df["Plant_number"].apply(lambda x: result.get(f"NCI_{x}", None)) + elif k.startswith("duration_"): + results_df[f"duration"] = results_df["Plant_number"].apply(lambda x: result.get(f"duration_{x}", None)) + elif k.startswith("D10s_"): + results_df[f"D10s"] = results_df["Plant_number"].apply(lambda x: result.get(f"D10s_{x}", None)) + elif k.startswith("D20s_"): + results_df[f"D20s"] = results_df["Plant_number"].apply(lambda x: result.get(f"D20s_{x}", None)) + elif k.startswith("D30s_"): + results_df[f"D30s"] = results_df["Plant_number"].apply(lambda x: result.get(f"D30s_{x}", None)) + elif k.startswith("D50s_"): + results_df[f"D50s"] = results_df["Plant_number"].apply(lambda x: result.get(f"D50s_{x}", None)) + elif k.startswith("D60s_"): + results_df[f"D60s"] = results_df["Plant_number"].apply(lambda x: result.get(f"D60s_{x}", None)) + elif k.startswith("D20_equip_"): + results_df[f"D20_equip"] = results_df["Plant_number"].apply(lambda x: result.get(f"D20_equip_{x}", None)) + elif k.startswith("D20_mat_"): + results_df[f"D20_mat"] = results_df["Plant_number"].apply(lambda x: result.get(f"D20_mat_{x}", None)) + elif k.startswith("D20_labor_"): + results_df[f"D20_labor"] = results_df["Plant_number"].apply(lambda x: result.get(f"D20_labor_{x}", None)) + + + print("Results by plant number:\n") + print(results_df.round(2).fillna("").to_string(index=False)) + + summary_metrics = ["cons_duration_cumulative_wz_startup", "occLastUnit", "TCILastUnit", "durationsLastUnit", "avg_OCC", "avg_TCI", "avg_duration"] + print("\nSummary metrics:\n") + for metric in summary_metrics: + print(f"{metric}: {result[metric]:.2f}") + + + + # for k, v in result.items(): + # print(f"{k}: {v}") print("\nTest Successful") From 62ee25f3bfb499ab4ad31f531fcd480f45d84279 Mon Sep 17 00:00:00 2001 From: "jia.zhou" <48254033+JiaZhou-PU@users.noreply.github.com> Date: Wed, 4 Mar 2026 16:19:37 -0600 Subject: [PATCH 09/25] SFR test not pass dur to duration and misinfo in the report --- .../CostReduction_exploration_mode.ipynb | 8087 +++++++++-------- src/crf/data/SFR_baseline.csv | 4 +- src/crf/data/SFR_baselineold.csv | 34 + src/crf/model/core_accounts.py | 7 + src/crf/model/direct_cost.py | 10 +- src/crf/model/learning.py | 24 +- src/crf/model/pipeline.py | 7 +- 7 files changed, 4177 insertions(+), 3996 deletions(-) create mode 100644 src/crf/data/SFR_baselineold.csv diff --git a/Cost_Reduction/CostReduction_exploration_mode.ipynb b/Cost_Reduction/CostReduction_exploration_mode.ipynb index ee05c43..1bc7754 100644 --- a/Cost_Reduction/CostReduction_exploration_mode.ipynb +++ b/Cost_Reduction/CostReduction_exploration_mode.ipynb @@ -67,659 +67,659 @@ "data": { "text/html": [ "\n", - "