laurel.pipelines.prepare_totals package
Submodules
laurel.pipelines.prepare_totals.nodes module
Kedro pipeline nodes for the prepare_totals pipeline (Model Module 1 — Select SoWs).
Prepares the vehicle-count denominators and adoption-projection tables that
the evaluate_impacts pipeline uses to scale telematics observations to
realistic fleet-level charging loads. This pipeline implements the first
half of Model Module 1 (Select States of the World): constructing the
disaggregation crosswalk that maps national NLR Ledna adoption forecasts
down to the (region × operating-distance class × weight class) strata used
by the model. An optional ACF (Advanced Clean Fleets) mandate variant
overrides projections wherever the regulatory minimum exceeds the forecast.
Pipeline overview
prepare_for_merging — Renames VIUS microdata columns and derives each respondent’s primary operating-distance class from a set of distance-bin indicator columns.
aggregate_vius_totals — One-hot encodes the home-base-code variable, redistributes survey weight from out-of-state or unknown home-base respondents, and aggregates into a conditional probability table
P(region, op_dist | weight_class)used as a disaggregation factor.aggregate_adoption_forecast_totals — Renames and aggregates the raw NLR Ledna vehicle-count projections to the required group level, applying a vehicle-stock multiplier to convert units if necessary.
create_disaggregated_adoption — Merges the VIUS disaggregation factors onto the national adoption totals to produce stratum-level vehicle counts.
build_mandates_by_group — Expands the sparse ACF mandate schedule into a dense (year × operating-distance class × weight class × state) table, interpolating linearly between known mandate fractions and broadcasting each mandate to the set of MOU-signatory states.
concat_projections_with_mandates — Appends a mandate-adjusted copy of the adoption projections in which ZEV fractions that fall below the regulatory minimum are raised to the mandate level, while non-ZEV fractions are correspondingly reduced.
Key design decisions
Proportional weight redistribution: Vehicles with “Home Base not in Register State” cannot be attributed to a known state, so their weight is redistributed to same-stratum “Home Base in Register State” respondents. This preserves the marginal totals while making every vehicle attributable.
Zero-denominator default technology: When Ledna projects zero adoption in an entire emissions class, the model defaults to a single representative fuel type (
default_fuel_types) with probability 1.0 rather than leaving the conditional probability undefined.Mandate override threshold: The mandate override is only applied when the mandate fraction exceeds the forecast ZEV fraction for a group; if the market forecast already meets or exceeds the mandate, the forecast is left unchanged.
References
Passow, F., & Rajagopal, R. (2026). Identifying indicators to inform proactive substation upgrades for charging electric heavy-duty trucks. Applied Energy.
NLR. (2023). Ledna: Light- and Medium-Duty Electric Vehicle Adoption Model. California Air Resources Board. Advanced Clean Fleets regulation.
- laurel.pipelines.prepare_totals.nodes.aggregate_adoption_forecast_totals(adopts, params)[source]
Aggregate and unit-scale the NLR Ledna adoption-forecast vehicle counts.
Renames columns, groups to the required dimensionality, and multiplies by a stock scalar (e.g., to convert from thousands of vehicles to individual vehicles).
- Parameters:
adopts (
DataFrame) – Raw Ledna adoption-forecast DataFrame.params (
dict) –Pipeline parameters dict with keys:
col_renamer(dict[str, str]): mapping from raw to internal column names.group_cols(list[str]): columns to group by.weight_col(str): vehicle-count column.vehicle_stock_mult(float): scalar multiplier applied to the aggregated totals (e.g., 1000 if Ledna reports in thousands).
- Return type:
DataFrame- Returns:
A
pd.DataFramewith one row pergroup_colscombination and a scaled vehicle-count column.
- laurel.pipelines.prepare_totals.nodes.aggregate_vius_totals(vius, params)[source]
Build a disaggregation crosswalk from VIUS survey weights.
Constructs the conditional probability table
P(region, op_dist | weight_class)that is later used to disaggregate national adoption-forecast totals into the (region, operating-distance class, weight class) strata required by the model.The procedure handles three home-base categories:
“Home Base in Register State”: Retained and scaled by
1 / P(known_home_base | has_home_base)to absorb the weight of out-of-state records.“Home Base not in Register State”: Dropped after contributing to the scaling denominator.
“No Home Base”: Retained but assigned a default operating distance.
Respondents with unknown operating distance have their weight zeroed out.
The final groupby aggregation over
group_colsnormalises by the conditional total to produce probabilities.- Parameters:
vius (
DataFrame) – VIUS microdata withprimary_dist_colalready assigned (output ofprepare_for_merging).params (
dict) –Pipeline parameters dict with keys:
home_base_code_col(str): column containing the home-base category label.home_base_region_col(str): output column for the region label derived from the home-base category.region_source_col(str): raw column providing the state name for “Home Base in Register State” records.weight_col(str): survey weight column.op_dist_col(str): operating-distance class column.fill_op_dist(str): default operating-distance label for “No Home Base” vehicles.spread_condition_cols(list[str]): columns defining strata within which the home-base-known probability is computed.group_cols(list[str]): columns to group by for the final aggregation.disagg_condition_cols(list[str]): conditioning columns for the normalisation denominator.out_prob_col(str): name of the output probability column.
- Return type:
DataFrame- Returns:
A
pd.DataFramewith one row per (group_cols) combination and a probability columnout_prob_colsumming to 1.0 within eachdisagg_condition_colsstratum.
- laurel.pipelines.prepare_totals.nodes.build_mandates_by_group(mands, mand_states, params)[source]
Expand the sparse ACF mandate schedule into a dense, merge-ready table.
The raw ACF mandate data provides ZEV fraction requirements at a small number of (year, vehicle-class) combinations. This function:
Maps ACF vehicle classes to the model’s operating-distance/weight-class groups via a correspondence table.
Creates a full (group × year) grid spanning
frame_years.Linearly interpolates mandate fractions between known anchor years; years before the first anchor are filled with 0.0.
Broadcasts the result across all MOU-signatory states in
mand_states.
- Parameters:
mands (
DataFrame) – Sparse ACF mandate DataFrame with (year, class, fraction) rows.mand_states (
DataFrame) – DataFrame of MOU-signatory state identifiers.params (
dict) –Pipeline parameters dict with keys:
acf_groups(dict):values(correspondence mapping),id_columns(list),value_column(str) — defines the mapping from ACF vehicle class to model group.frame_years(dict):minandmaxyear for the output grid.mandate_fraction_col(str): column name for the ZEV fraction.mou_state_col(str): column inmand_statescontaining state identifiers.col_renamer(dict[str, str]): mapping to harmonise column names with the adoption-forecast tables.weight_class_col(str): weight-class column to cast to str.
- Return type:
DataFrame- Returns:
A
pd.DataFramewith one row per (state, group, year) combination and amandate_fraction_colcolumn suitable for merging with adoption-forecast tables.
- laurel.pipelines.prepare_totals.nodes.concat_projections_with_mandates(adopts, mands, params)[source]
Append a mandate-adjusted copy of the adoption projections to the original forecasts.
The group is defined as the combination of all variables that characterise a vehicle type other than fuel type (e.g., weight class, operating distance, region, year, scenario). The mandate class (
mclass) is the ZEV designation: a vehicle is in the mandate class if and only if its fuel type is inzev_fuel_types.The override logic is:
Compute
P(mclass | group)from the adoption forecast.Merge the mandate fraction for each group from
mands.If the mandate fraction exceeds
P(mclass | group), mark the group asmandate_override = True.For overridden groups, replace ZEV counts with
N_group × mandate_fraction × P(tech | group, mclass)and non-ZEV counts withN_group × (1 − mandate_fraction) × P(tech | group, ~mclass).When Ledna projected zero ZEVs in a group, default to a single representative technology (
default_fuel_types) with probability 1.
The original and mandate-adjusted projections are stacked with a boolean index level indicating whether the mandate is active.
- Parameters:
adopts (
DataFrame) – Disaggregated adoption-forecast totals.mands (
DataFrame) – Dense mandate schedule (output ofbuild_mandates_by_group).params (
dict) –Pipeline parameters dict with keys:
group_cols(list[str]): columns that define a group (all dimensions except fuel type).fuel_type_col(str): column containing the fuel-type label.zev_fuel_types(list[str]): fuel-type labels counted as ZEVs.totals_col(str): vehicle-count column.scenario_col(str): scenario identifier (excluded from the mandate merge join to apply mandates across all scenarios).mandate_fraction_col(str): ZEV fraction column frommands.default_fuel_types(list[str]): fallback fuel type when Ledna projected zero ZEVs.mandate_active_col(str): name of the boolean index level added to the output.
- Return type:
DataFrame- Returns:
A
pd.DataFramewith a boolean index levelmandate_active_col(False= original forecast,True= mandate-adjusted forecast) and the same columns asadopts.
- laurel.pipelines.prepare_totals.nodes.create_disaggregated_adoption(adopts, disagg, params)[source]
Disaggregate national adoption totals to stratum-level vehicle counts.
Merges the VIUS-derived conditional probability table (
disagg) onto the aggregated adoption forecast (adopts) and multiplies to produce vehicle counts broken out by region and operating-distance class.- Parameters:
adopts (
DataFrame) – Aggregated adoption totals (output ofaggregate_adoption_forecast_totals).disagg (
DataFrame) – Disaggregation crosswalk with a probability column (output ofaggregate_vius_totals).params (
dict) –Pipeline parameters dict with keys:
merge_cols(list[str]): columns to join on.orig_totals_col(str): vehicle-count column inadopts.disagg_factor_col(str): probability column indisagg.final_totals_col(str): name of the output vehicle-count column (rounded to the nearest integer).keep_group_cols(list[str]): columns to retain in the output.
- Return type:
DataFrame- Returns:
A
pd.DataFramewith integer vehicle counts per stratum, sorted bykeep_group_cols.
- laurel.pipelines.prepare_totals.nodes.prepare_for_merging(vius, params)[source]
Rename VIUS columns and derive each respondent’s primary operating-distance class.
The VIUS encodes operating distance as a set of binary indicator columns (one per distance bin). This function identifies the bin with the highest value for each respondent to assign a single
primary_dist_collabel. Respondents with all-NA distance bins are assigned a sentinel value of"NA"so they can be excluded downstream.- Parameters:
vius (
DataFrame) – Raw VIUS microdata DataFrame.params (
dict) –Pipeline parameters dict with keys:
col_renamer(dict[str, str]): mapping from raw column names to internal names.dist_bin_col_prefix(str): prefix shared by all operating- distance indicator columns (used to identify them by name).primary_dist_col(str): name of the output column for the primary operating-distance label.
- Return type:
DataFrame- Returns:
The input DataFrame with columns renamed and a new
primary_dist_colcolumn added.
laurel.pipelines.prepare_totals.pipeline module
Kedro pipeline definition for the prepare_totals pipeline.
Wires the nodes from laurel.pipelines.prepare_totals.nodes into a single Pipeline object.
For full documentation of each node’s inputs, outputs, and algorithm,
see laurel.pipelines.prepare_totals.nodes.
Module contents
This is a boilerplate pipeline ‘prepare_totals’ generated using Kedro 0.19.11