Changelog#
All notable changes to BREOS are documented here. Format follows Keep a Changelog.
[0.3.3] - 2026-07-02#
Removed#
The
Suntech_STP550S_NOMTcatalog module. Its datasheet points were NMOT ratings (800 W/m², Mpp = 415 W), but the CEC single-diode fit interpretsVmp/Imp/Voc/Iscas STC values, so the entry produced silently wrong model parameters. Configs referencing it now fail with the standard “Module ‘…’ not found. Available: …” error; useSuntech_STP550S_STC(the same physical module at STC) instead.
Added#
breos sweep, a serial parameter-grid CLI command that expands a[sweep]section in a normal App config and writes one combined CSV with varied parameters, resolved system sizing, BREOS version, and scalar result metrics.configs/examples/sweep.tomlas a runnable sweep example over module count and battery size.Release-smoke tests for the README quickstart, the Monte Carlo example path, and the pymoo-backed multi-objective optimization helper.
breos.solar.resolve_pvwatts_losses, used by dry-run/config inspection to report resolved PVWatts loss components and their combined percentage.sell_price_inflationApp config key and--sell-price-inflationCLI flag (default0.0).CostParamsandcost_analysis_projectionalready supported an annual export-price inflation, but no config key existed and neither the App runner nor the Monte Carlo runner passed it, so the public paths always projected with0.0. The value is validated invalidate_config, threaded through both projection call sites, and shown inbreos run --dry-run/validate-config --json. The0.0default reproduces existing results bit-for-bit.
Changed#
breos run --dry-runandbreos validate-config --jsonnow include the fully resolved static PVWatts loss stack instead of only echoingpv_loss_overrides.BatteryConfig.battery_typeis now explicit about the native degradation model being LFP-only:"LFP"normalizes to"lfp", while unsupported chemistries raise instead of silently reusing LFP cycle-aging parameters.BatteryConfig.eol_percentagenow defaults to0.70, aligning with the App config defaultbattery_eol_percentage = 0.70and the optimizer’s battery-spec fallback (previously0.80and0.8respectively — three surfaces, two values). App and CLI results are unchanged (they always pass the config value explicitly), but directBatteryConfigusers who relied on the implicit0.80will now see batteries replaced later, at 70% SOH; passeol_percentage=0.8to keep the old threshold. The same applies to optimization battery specs without an expliciteol_percentage.
Documentation#
Updated the README and
CITATION.cffto cite the SSRN preprint DOI (10.2139/ssrn.7032064).Added the BLAST degradation-engine design note and refreshed roadmap priorities around model accuracy, validation, energy-loss accounting, and future default-model profiles.
Fixed#
dc_to_ac(and thereforecalculate_pv_production_ac) clipped ~4% below the intended inverter AC nameplate: it passed the nameplate (pv_peak_power_w / inverter_loading_ratio) as pvlib’spdc0, which is a DC-input limit whose AC nameplate iseta_inv_nom * pdc0. The DC limit is now derived asnameplate / eta_inv_nom, so clipping happens at the same AC rating used byInverterConfig.size_from_pv, the App energy balance,economics.calculate_costs, and the CLI’s reportedac_rating_kw. This raisesdc_to_ac/calculate_pv_production_acoutputs slightly at every operating point (most visibly during clipping hours); App simulation results are unchanged because the App path converts DC throughsimulate_energy_balance, notdc_to_ac.PVModuleParamsno longer discards a user-suppliedgamma_pmp: the constructor argument existed but__post_init__unconditionally overwrote it withT_Pmax_pct. It now only defaults toT_Pmax_pctwhen not given, matching thealpha_sc_abs/beta_voc_absoverride pattern. Catalog modules and configs that never setgamma_pmpare unaffected.
[0.3.2] - 2026-06-26#
Upgrading: config validation is now strict — a config with an unknown top-level key (e.g. a typo like
batery_kwh) that silently defaulted in 0.3.1 now raises listing the offending key(s). Fix or remove stray keys before upgrading. All other changes preserve prior behaviour by default.
Removed#
The
nrel-pysamruntime dependency. It was only ever reached transitively, through pvlib’sfit_cec_sam, to fit the CEC single-diode parameters on the default PV path.nrel-pysampublishes no Python 3.14 wheel or sdist and was the sole blocker to running BREOS on 3.14.
Added#
breos.cec_fit.fit_cec_params: a pure-scipy/pvlibimplementation of the CEC 6-parameter coefficient calculator (Dobos 2012, DOI:10.1115/1.4005759), a drop-in forpvlib.ivtools.sdm.fit_cec_sam. Across every bundled module it reproduces the SAM fit to within 0.03% on maximum power over a temperature x irradiance grid and 0.004% on annual energy, so model results are unchanged. Validated against thenrel-pysamoracle bytools/validate_cec_fit.py.Python 3.14 support: the
3.14classifier and CI matrix entry, now that thenrel-pysamblocker is gone.Config validation now rejects unknown top-level keys. A typo such as
batery_kwhpreviously slipped throughmerge_defaultsand silently defaulted (e.g. the battery to0), producing plausible-but-wrong results; it now raises listing the offending key(s). The optionalmontecarlosection is recognised so Monte Carlo configs still validate.Configurable sky-diffusion (transposition) model via a
transposition_modelconfig key and--transposition-model/--sky-modelCLI flag, threaded throughcalculate_pv_production_dc, the tracking and multi-array variants, and theAppconfig surface. Supportsisotropic(default),klucher,haydavies,reindl,king,perez, andperez-driessevia pvlib’sget_total_irradiance; the extra inputs the anisotropic models need (extraterrestrial DNI, relative airmass) are derived internally. The defaultisotropicreproduces prior results bit-for-bit. Per-array overrides are supported inpv_arrays.Configurable ground reflectance and Perez coefficients to drive those models with real site information:
albedo(0-1) or a namedsurface_type("snow","sea","grass", …) sets the ground-diffuse reflectance for every model (previously fixed at pvlib’s 0.25), andmodel_perezselects the Perez coefficient set. All three are App config keys with matching--albedo/--surface-type/--perez-modelCLI flags and per-array overrides; not setting them leaves the previous defaults unchanged.
Changed#
The default PV path fits CEC parameters via
breos.cec_fit.fit_cec_paramsinstead ofpvlib.ivtools.sdm.fit_cec_sam;breos/solar.pyand the public API are otherwise unchanged.The two placeholder
Generic_400WandGeneric_600W_Bifacialcatalog modules now carry realistic mono-PERC datasheet specifications (their previous made-up values fit cleanly under SAM only via an internal short-circuit-current heuristic); their nameplate power and keys are unchanged.resolve_pv_systemno longer mutates the merged config in place to record the derivedn_modules; the resolved count is materialised into a fresh dict byresolve_app_config, so the dict wrapped by the frozenResolvedAppConfigis built once and the caller’s input dict is left untouched.
[0.3.1] - 2026-06-25#
Changed#
Pinned
requires-pythonto>=3.11,<3.14. The transitivenrel-pysamdependency (reached through pvlib’s CEC fit) publishes no Python 3.14 wheel or sdist, so installs on 3.14 could not resolve. This is a stopgap; 0.3.2 removes thenrel-pysamdependency and lifts the cap.
[0.3.0] - 2026-06-24#
Fixed#
TMY timezone misalignment (results-changing):
fetch_tmy_weather_datarelabeled PVGIS’s UTC-ordered rows with local-time labels, shifting irradiance against the computed solar position by the location’s UTC offset (~1 h for Berlin, ~10 h for Melbourne; UTC+0 locations were unaffected). Rows are now rolled to start at local midnight while each timestamp keeps its correct UTC instant.Battery phantom export (results-changing): when temperature derating or daily SOH decline shrank
Emaxbelow the stored energy, the negative charge room silently drained the battery intoSell_To_Grid. Stored energy is now clamped into the derated window, mirroring the Numba kernel.Load profiles are pinned to the location’s wall clock instead of the UTC clock, so H0 morning/evening peaks land at the correct local hours across DST (previously ~1 h off in Iberia during summer).
optimize_tilt/optimize_tilt_brentreported “kWh” without accounting for the timestep (4x off at 15-minute resolution; ranking was unaffected).BatteryConfig.initial_resistance_growthwas never read bysimulate_energy_balance; it now seeds the resistance state when the continuation argument is not supplied.get_module_infoprinted the efficiency fraction as a percent and crashed on modules without efficiency metadata.Removed three dead, shadowed plotting functions and an undefined
MONTH_LABELSreference that crashed the TMY-vs-historical monthly plot.
Added#
Inverter AC clipping in the
Appenergy pipeline: PV output, export, and battery discharge now saturate at the AC rating implied byinverter_loading_ratio— the same rating used for inverter CAPEX. DC surplus above the rating still charges a DC-coupled battery (BatteryConfig.inverter_ac_capacity_w,None= legacy uncapped model).Configurable PVWatts system losses:
breos.solar.DEFAULT_PVWATTS_LOSSES(~14.1% combined) with aloss_overrideshook on the production functions and apv_loss_overridesApp config key.Battery operating parameters as App config keys:
battery_min_soc,battery_max_soc,battery_eol_percentage, andbattery_rte(previously hardcoded to 0.10/0.90/0.70/sqrt(0.95)).enable_resistance_fadenow feeds the resistance-derated round-trip efficiency back into the energy loop (previously tracking-only).Battery degradation calibration variants are explicit for the 0.3.0 release:
naumann_lam_field_calibratedremains the default v1 field calibration,naumann_lam_field_calibrated_v1is an equivalent explicit alias, andnaumann_lam_field_calibrated_v2exposes the v2 field-calibrated fit with LamEa/nfixed andk0/bfitted to field data.Parity tests for the optional Numba kernels: the duplicated LFP derate constants against
battery.lfp_capacity_factor, and the energy-balance kernel against the reference path under shared-model conditions.CLI discovery and inspection commands:
breos list {locations,modules,cost-presets,emissions,load-profiles}prints packaged option keys,breos validate-config <config>checks a config file and summarizes the resolved choices, andbreos run --dry-runwrites the resolved configuration as JSON without running a simulation.listandvalidate-configaccept--jsonfor machine-readable output.PyPI distribution: 0.3.0 is the first release installable with
pip install breos. Taggedv*releases onmainnow publish to PyPI through GitHub Actions trusted publishing (OIDC), running the release artifact verifier before upload, with a manually triggered TestPyPI dry-run path.Projection-based LCOE support:
breos.economics.calculate_lcoe_from_projectioncomputes LCOE from the simulated multi-year cost projection, and the batch location comparison tool now writeslcoe_eur_kwhplus LCOE heatmaps.
Changed#
Renamed remaining pre-release “PVBAT” branding to BREOS in the Polysun comparison plots: the
plot_degradation_methodology_comparisonfirst argument is nowbreos_soh, the scenario/location dicts passed toplot_lifetime_prediction_comparisonandplot_temperature_sensitivity_comparisonuse thebreos_eol_yearkey, legend labels read “BREOS (Naumann)”, and the SOH comparison figure is saved aspolysun_breos_soh_comparison*.png.Cost defaults are single-sourced from the
CostParamsdataclass:cost_params_from_configand the App preset fallbacks no longer carry their own diverging literals (packaged presets are unaffected).Config validation rejects out-of-range values at load time: negative
battery_kwh, top-leveltilt/azimuth,inverter_efficiency,inverter_loading_ratio,projection_years,pv_degradation_rate, and the new battery keys.PV-only App runs construct an explicit inverter model, so a configured
inverter_efficiencynow applies without a battery (previously ignored).App.result()["lcoe_eur_kwh"]now uses the simulated projection, including O&M and battery replacement costs, instead of the simpler CAPEX + fixed annual O&M helper.Library progress messages (weather file discovery, saved files, CSV conversions) go through
loggingunderbreos.*logger names instead of unconditionalprint(). Functions with averboseflag still print.Slimmed the default runtime dependency set to the BREOS core simulation stack and moved heavier workflow packages behind extras:
plots,optimization,weather,fast,validation, andlocation-tools. NREL-PySAM stays in the core set because the default PV model fits CEC single-diode parameters at runtime via pvlib’sfit_cec_sam. (Removed in 0.3.2.)The
devextra now installs optional feature dependencies so contributor test runs continue to cover optional paths.
Documentation#
Install snippets in the README and docs point at PyPI (
pip install breos) instead of git tag installs, and the quickstart gained a “10-minute first run” walkthrough with a pip-friendly inline config, the matchingconfigs/examples/quickstart.tomlsource-checkout example, the new option-discovery commands, and a representative output excerpt with plausibility ranges.New recipes page with validated copy-paste configs: PV-only home, PV plus battery, custom latitude/longitude/timezone, east-west roof with
pv_arrays, 15-minute resolution, external E-REDES/BDEW/REE load profiles, and offline runs with cached weather.New generated “Packaged options” reference page listing locations, PV modules, cost presets, emissions factors, and load profiles. It is built by
tools/generate_option_docs.pyfrom the packaged data and source constants, and a test fails CI when the page drifts.README documents the fixed PVWatts loss components, the inverter clipping convention, the
weather/working-directory override, the Open-Meteo.cache.sqlitefile, logging configuration, and the new config keys.README describes the Numba kernels honestly as approximate standalone screening engines that
breos.Appdoes not use; the module docstring carries the same warning.Clarified that the
bdew_h0alias maps to the bundled demandlib BDEW-H0-shaped profile"1", distinct from the external BDEW H0 2025 dataset (profile"7").Replaced stream-of-consciousness working notes in
economics,optimization, andplottingwith factual comments, and fixed mislabeled docstrings (total_pvis post-inverter AC; the Suntech NOMT catalog entry documents its NMOT-condition rating).
[0.2.3] - 2026-06-08#
Changed#
Lowered the minimum supported Python from 3.13 to 3.11 — the real floor, set by pandas, timezonefinder, and stdlib
tomllib. CI now runs a 3.11/3.12/3.13 matrix.Relaxed the pvlib constraint from
==0.14.0to>=0.14.0,<0.16after verifying the full API surface and the test suite against pvlib 0.15.1.breos.__version__is now resolved from installed package metadata (importlib.metadata) instead of a hardcoded literal, so it can no longer drift frompyproject.toml.
Added#
CITATION.cff,CODE_OF_CONDUCT.md, andSECURITY.mdfor open-source release readiness.
Removed#
Duplicate top-level
rlp/*.csvload-profile files (byte-identical to the packagedbreos/data/rlp/copies that runtime actually uses).rlp/README.mdis retained as external-RLP guidance.
Documentation#
README badge and installation docs now state Python 3.11+.
Trimmed
ATTRIBUTIONS.mdto reference only the packaged load-profile paths.
[0.2.2] - 2026-06-07#
Documentation#
Expanded third-party notices with dependency credits, runtime data-source caveats, and scientific/model attribution guidance.
[0.2.1] - 2026-06-03#
Documentation#
Updated installation guidance to use the stable GitHub tag until PyPI publishing is available.
Added PyPI trusted publishing to the roadmap.
Documented the full CI/release validation gates in the contributor guide.
Standardized API documentation wording around domain areas.
[0.2.0] - 2026-06-03#
Changed#
Narrowed the top-level
breos.__all__release surface to the stable facade, key configuration/result objects, and core composition helpers. Lower-level module APIs remain importable from their modules.
[0.1.0] - 2026-04-30#
Added#
Public API facade (
breos.App) — single entry point for simulations: config dict in, plain dict out.Command line entry point (
breos run) for running simulations from shell flags or TOML/JSON config files.Test suite — pytest coverage of the public API, battery, economics, emissions, and solar modules (all offline).
GitHub Actions CI on every push/PR.
cost_params_from_config()— config parser forCostParams.Marginal grid carbon intensity support in
EmissionsParamsfor more accurate CO₂ avoidance accounting.
Changed#
Renamed PV
slope→tilteverywhere: function parameters, dataclass fields, docstrings, CLI/config keys, public API. Includesoptimize_slope()→optimize_tilt()and thetools/azitilt_optimizer.pyscript.Calendar model name canonicalized to
naumann_lam_field_calibrated(the legacy aliasnaumann_lam_calibratedhas been removed).Constants renamed:
LAM_NAUMANN_FIELD_CALIBRATED_*→NAUMANN_LAM_FIELD_CALIBRATED_*; alias indirections (LAM_CAL_K0_FRAC…) dropped.Configs modernized: per-unit cost keys (
maintenance_cost_per_panel,other_cost_per_module); emissions schema renamed and country list expanded.Polysun degradation now tracks the actual
last_replacement_yearinstead of approximating withn_replacements × int(total_life)— handles fractional lifetimes and cycle-driven replacements correctly.Numba degradation kernel now treats SOC reversals as half-cycles (rainflow-aligned) and applies LFP temperature derating per timestep, matching the Python reference path.
Fixed#
NPV discount factor now uses
(1 + r) ** Year(time-0 NPV) instead of(1 + r) ** (Year - 1). Affects allcost_analysis_projectionoutputs.
Removed#
Out-of-scope kernels (
combined_energy_balance_kernel,batch_combined_energy_balance_kernel) and non-core energy-system code paths. BREOS focuses on PV + battery simulation.