Skip to main content
eFleet evaluates replacement strategies for individual vehicle needs. This specification describes the active run.py → inputs.py → fleet_results.py → outputs.py calculation.

Domain, dimensions and units

A fleet need is one vehicle service requirement, keyed by FleetNeed.key, with region, facility, annual distance, annual idle time, subtype and initial vehicle. Subtype acquisition options specify ICE, BEV and PHEV performance and prices. Counts represent vehicle needs, not weighted population samples. Distance inputs, efficiencies, retirement mileage and per-distance maintenance must use the same distance unit. The calculation does not convert kilometres to miles. The division by 10610^6 in emissions requires intensities in grams per energy unit. Monetary series are added in their supplied currency without inflation or exchange-rate conversion.

Workbook contract and defaults

Required worksheets are Inputs, Annual Inputs, Global Data, Vehicle Specs, EV Availability, Maintenance Costs, Basic Fleet Info and the three Replacement Costs (ICE), (BEV), (PHEV) sheets. Inputs reads StartYear, EndYear, Horizon, GHG_Penalty and NumPHEV_Cycles. Regional prices and intensities use annual columns recognized by Y?2..., accepting year-like labels and Y2026-style names. A complete all region provides fallback energy and carbon values. A regional energy series is selected based on its first cost-year value; missing later values are not individually backfilled. The workbook importer supplies 250 working days unless its override branch applies. That branch tests WorkingDays against an already lowercased column index, so the intended override does not take effect for ordinary string headers. Programmatic FleetNeed construction defaults to 365. Do not treat the two entry paths as having identical defaults. StudyParameters declares age and mileage penalty multipliers of 1.035 and ICE penalty multiplier of 1.01. Its global early_rep workbook assignment evaluates bool("EarlyRep_Allowed"), which is always true; per-need early replacement eligibility is the value checked by purchase selection. ghg_penalty and phev_cycles are read but do not multiply annual usage energy in _calc_usage_costs.

Time indexing

Cost years run from start_year through horizon + max_ret_age, inclusive. Purchase indicators extend through horizon, but annual decision loops use range(start_year, horizon), excluding the horizon year. end_year controls the post-study ICE penalty threshold. Costs are compared over the full cost-year array, not only study years, and are summed without discounting.

Daily and annual energy

Set d=D/Wd=D/W and i=I/Wi=I/W. Daily unconstrained electric demand is Ed=de+ieiE_d=de+ie_i.
  • ICE: Ed=0E_d=0 and Fd=df+ifiF_d=df+if_i.
  • BEV: Fd=0F_d=0 and the complete electric demand is served, even if it exceeds battery capacity. Charging availability is not a feasibility constraint here.
  • PHEV: if B>EdB>E_d, all driving and idling are electric. Otherwise, for B>0B>0, set Ed=BE_d=B, electric distance de=(B−iei)/ed_e=(B-ie_i)/e, remaining distance df=d−ded_f=d-d_e, and fuel Fd=dffF_d=d_ff when df>0d_f>0. PHEV idling remains electric. A nonpositive battery raises an error.
The comparison is strict: equality follows the battery-limited branch. Electric distance is not clipped to zero if idle energy exceeds battery capacity. Positive annual distance and working days, consistent nonzero efficiencies and physically compatible idle/battery inputs are required for meaningful results; the implementation does not comprehensively validate those conditions. Ey=WEd,Fy=WFd,Cyenergy=W(Edpye+Fdpyf)E_y=WE_d,\quad F_y=WF_d,\quad C^{energy}_y=W(E_dp^e_y+F_dp^f_y) With electric distance share a=de/da=d_e/d, maintenance for non-ICE options is D[amBEV+(1−a)mICE]D[am_{BEV}+(1-a)m_{ICE}]; ICE uses DmICEDm_{ICE}. Gy=W(Edgye+Fdgyf)106,Cycarbon=GycyG_y=\frac{W(E_dg^e_y+F_dg^f_y)}{10^6},\qquad C^{carbon}_y=G_yc_y

Retirement and synthetic penalties

The recurring replacement cycle is L=min⁡(⌊Mret/D⌋,Aret)L=\min(\lfloor M_{ret}/D\rfloor,A_{ret}). Initial eligibility uses the minimum of remaining retirement age and max⁡(0,⌈(Mret−M0)/D⌉)\max(0,\lceil(M_{ret}-M_0)/D\rceil). Purchase indicators reset the subsequent replacement schedule. A zero resulting cycle is invalid for penalty division. For penalties, age is ay=y−yvehicle+1a_y=y-y_{vehicle}+1 and mileage grows from the supplied starting mileage. Age and mileage thresholds use strict > comparisons. Let uyu_y indicate either exceeded threshold, vyv_y and zyz_y be the configured age/mileage multipliers when exceeded and one otherwise, and CyC_y be the same-powertrain replacement cost. Pyret=max⁡t(Ctut)LvyzyuyP^{ret}_y=\frac{\max_t(C_tu_t)}{L}v_yz_yu_y For ICE, jy=1[y>end_year]kICEj_y=\mathbf1[y>end\_year]k_{ICE}. The additional component is calculated using the maximum EV and same-powertrain prices after multiplication by jj: b=max⁡(max⁡t(CtEVjt)−max⁡t(Ctjt),0)LkICE,Py=Pyret+bjyb=\frac{\max(\max_t(C^{EV}_tj_t)-\max_t(C_tj_t),0)}{L}k_{ICE},\qquad P_y=P^{ret}_y+bj_y The EV price reference uses BEV when available, otherwise PHEV. These synthetic penalties influence selection; they are not cash expenditures. The repeated ICE multiplier is part of the implemented calculation.

Annual selection algorithm

Initialize a keep-all strategy and derive same-powertrain and alternative-powertrain trial strategies. For each decision year, candidate purchase costs, operating costs and penalties are updated; previously accepted decisions remain in the strategy. Candidate savings for each need are: Sn=∑y(Cn,ycurrent−Cn,ytrial)S_n=\sum_y(C^{current}_{n,y}-C^{trial}_{n,y}) total_cost_mat includes capital, energy, maintenance, carbon and penalty costs. Sort needs by savings and traverse from largest to smallest. Purchase only for strictly positive savings, an eligible early/due replacement and available fleet/subtype capacity. Equal savings follow NumPy’s ordering; there is no explicit business tie-breaker. Fleet cap is round(percent × fleet_count / 100). Subtype caps use the same rounding except a positive raw quantity below one becomes one. A zero subtype cap remains zero. Without a fleet cap, capacity equals fleet size. This greedy annual ranking is not a simultaneous multi-year optimization.

Input-series validation

check_inputs requires fuel price/intensity, subtype replacement-limit and acquisition-price series to extend through one additional year beyond the last cost year. That is horizon + max_ret_age + 1, although usage calculations end one year earlier. Missing subtype limits log an error and continue through the caught AttributeError; insufficient supplied year coverage raises ValueError. Missing initial vehicle mileage is replaced with zero with a warning.

Output contract and validation

StudyResults.replacement_strategies contains strategy objects with vehicle-by-cost-year capital, energy, fuel, electricity, maintenance, carbon, emissions and penalty matrices, plus purchase/replacement indicators and option selections. no_penalty_cost_mat excludes penalties. Reporting produces strategy summaries, replacement plans, vehicle and fleet costs/emissions and facility charging energy. Facility charging energy is annual kWh, not charger power or an hourly dispatch profile. For an illustrative BEV with D=20,000D=20,000, I=100I=100, W=250W=250, e=0.2e=0.2, ei=1e_i=1, annual electricity is 4,100 kWh. At 0.15 currency/kWh, energy costs 615/year. At 100 g/kWh, emissions are 0.41 tCO2e/year. These assumptions are examples, not defaults. Check annual energy against distance and idle inputs, cash costs separately from penalty-inclusive selection, cap rounding, cost-year endpoints, replacement timing and repeatability under the same input order. Existing tests cover usage, savings, replacement limits and penalty behaviour; missing acquisition options and invalid vehicle powertrain references raise explicit errors. Unchecked missing Excel columns, zero denominators and incompatible year labels can fail earlier in pandas or arithmetic operations.