ev-flow 3.0.0__tar.gz → 3.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. {ev_flow-3.0.0 → ev_flow-3.0.2}/.gitignore +1 -7
  2. ev_flow-3.0.2/ATTRIBUTION.md +76 -0
  3. ev_flow-3.0.2/CHANGELOG.md +42 -0
  4. {ev_flow-3.0.0 → ev_flow-3.0.2}/PKG-INFO +36 -3
  5. {ev_flow-3.0.0 → ev_flow-3.0.2}/README.md +32 -1
  6. {ev_flow-3.0.0 → ev_flow-3.0.2}/pyproject.toml +6 -2
  7. {ev_flow-3.0.0 → ev_flow-3.0.2}/src/pev_synth/__init__.py +1 -1
  8. {ev_flow-3.0.0 → ev_flow-3.0.2}/src/pev_synth/api.py +3 -3
  9. ev_flow-3.0.2/src/pev_synth/py.typed +1 -0
  10. {ev_flow-3.0.0 → ev_flow-3.0.2}/src/pev_synth/validator.py +8 -7
  11. {ev_flow-3.0.0 → ev_flow-3.0.2}/LICENSE +0 -0
  12. {ev_flow-3.0.0 → ev_flow-3.0.2}/src/pev_synth/_meta.py +0 -0
  13. {ev_flow-3.0.0 → ev_flow-3.0.2}/src/pev_synth/_paths.py +0 -0
  14. {ev_flow-3.0.0 → ev_flow-3.0.2}/src/pev_synth/_phev_fuel_economy.py +0 -0
  15. {ev_flow-3.0.0 → ev_flow-3.0.2}/src/pev_synth/_seeds.py +0 -0
  16. {ev_flow-3.0.0 → ev_flow-3.0.2}/src/pev_synth/_utc_migration.py +0 -0
  17. {ev_flow-3.0.0 → ev_flow-3.0.2}/src/pev_synth/acs_loader.py +0 -0
  18. {ev_flow-3.0.0 → ev_flow-3.0.2}/src/pev_synth/cache_regen.py +0 -0
  19. {ev_flow-3.0.0 → ev_flow-3.0.2}/src/pev_synth/donor_matcher.py +0 -0
  20. {ev_flow-3.0.0 → ev_flow-3.0.2}/src/pev_synth/hourly_resampler.py +0 -0
  21. {ev_flow-3.0.0 → ev_flow-3.0.2}/src/pev_synth/nhts_loader.py +0 -0
  22. {ev_flow-3.0.0 → ev_flow-3.0.2}/src/pev_synth/nhts_nextgen_loader.py +0 -0
  23. {ev_flow-3.0.0 → ev_flow-3.0.2}/src/pev_synth/plug_in_model.py +0 -0
  24. {ev_flow-3.0.0 → ev_flow-3.0.2}/src/pev_synth/regions.py +0 -0
  25. {ev_flow-3.0.0 → ev_flow-3.0.2}/src/pev_synth/sales_mix_data.py +0 -0
  26. {ev_flow-3.0.0 → ev_flow-3.0.2}/src/pev_synth/soc_trajectory.py +0 -0
  27. {ev_flow-3.0.0 → ev_flow-3.0.2}/src/pev_synth/travel_week_builder.py +0 -0
  28. {ev_flow-3.0.0 → ev_flow-3.0.2}/src/pev_synth/validation_bounds_curator.py +0 -0
  29. {ev_flow-3.0.0 → ev_flow-3.0.2}/src/pev_synth/vehicle_archetypes.py +0 -0
@@ -44,11 +44,5 @@ results/
44
44
  # tooling/repo; not part of the ev-flow package.
45
45
  docs/Scientific publication/
46
46
 
47
- # Private / internal: AI-assistant instructions and internal working docs.
48
- # These stay on the local dev machine and are deliberately NOT published to
49
- # the public GitHub repo. Public-facing docs live in documentation/ instead.
50
- CLAUDE.md
51
- AGENTS.md
52
- .cursorrules
53
- .github/copilot-instructions.md
47
+ # Internal working docs kept on the local dev machine, not published.
54
48
  docs/
@@ -0,0 +1,76 @@
1
+ # Data sources, licensing & attribution
2
+
3
+ `ev-flow` is MIT-licensed (see [LICENSE](LICENSE)). It is grounded in several
4
+ upstream public data sources. This file discharges the attribution and notice
5
+ obligations of those sources. Two notices below are **legally required** and
6
+ are reproduced verbatim; the remainder are courtesy citations (the data is
7
+ either U.S. Government public-domain or carries no binding attribution clause,
8
+ but we credit it as a matter of scientific good practice).
9
+
10
+ `ev-flow`'s code is built to consume the SPEECh Original Model — its loader,
11
+ schema, and CC-BY citations live in `pev_synth.plug_in_model`. The SPEECh-derived
12
+ JSON is built by the user from the upstream Mendeley source and is **not**
13
+ redistributed in the wheel, but SPEECh is the load-bearing model dependency, so
14
+ its CC-BY-4.0 notice is reproduced here (and in the source). This file is shipped
15
+ in the sdist.
16
+
17
+ ---
18
+
19
+ ## Required notices
20
+
21
+ ### SPEECh Original Model — CC BY 4.0
22
+
23
+ > Contains data from "SPEECh Original Model" by Siobhan Powell, Gustavo Vianna
24
+ > Cezar, and Ram Rajagopal, Mendeley Data, V1 (2021),
25
+ > <https://doi.org/10.17632/gvk34mybtb.1>, licensed under CC BY 4.0
26
+ > (<https://creativecommons.org/licenses/by/4.0/>). **Modified:** a subset of
27
+ > the home and work driver-group Gaussian-mixture components was converted from
28
+ > pickle to JSON and reweighted.
29
+
30
+ Associated publication: Powell, S.; Cezar, G. V.; Rajagopal, R. (2022).
31
+ *Scalable probabilistic estimates of electric vehicle charging given observed
32
+ driver behavior.* Applied Energy 309, 118382.
33
+ <https://doi.org/10.1016/j.apenergy.2021.118382>
34
+
35
+ ### U.S. Census Bureau Data API — required disclaimer
36
+
37
+ `pev_synth.acs_loader` accesses the Census Bureau Data API at runtime. Per the
38
+ [Census Data API Terms of Service](https://www.census.gov/data/developers/about/terms-of-service.html),
39
+ any product using the API must display the following notice:
40
+
41
+ > This product uses the Census Bureau Data API but is not endorsed or certified
42
+ > by the Census Bureau.
43
+
44
+ (The underlying ACS PUMS statistics are public domain; this is an
45
+ endorsement-disclaimer, not a copyright attribution. The Census terms also
46
+ prohibit using the data to re-identify any individual, household, or entity.)
47
+
48
+ ---
49
+
50
+ ## Courtesy citations
51
+
52
+ These sources impose no binding attribution requirement on `ev-flow`, but are
53
+ credited here.
54
+
55
+ | Source | Used for | License / status | Citation |
56
+ |---|---|---|---|
57
+ | **NHTS 2017** (& NextGen 2022) — USDOT/FHWA, hosted by ORNL | Travel micro-data backbone (`nhts_loader`, `nhts_nextgen_loader`) | U.S. Government work — public domain (17 U.S.C. §105); public-use confidentiality condition (no respondent re-identification) | U.S. Department of Transportation, Federal Highway Administration. 2017 National Household Travel Survey (and 2022 NextGen NHTS). <https://nhts.ornl.gov> |
58
+ | **U.S. Census ACS 5-Year PUMS** | Household-mix raking/calibration (`acs_loader`) | U.S. Government work — public domain; API use governed by Census Data API ToS (see required notice above) | U.S. Census Bureau, ACS 5-Year PUMS, via the Census Data API. <https://api.census.gov/data> |
59
+ | **EPA / DOE fueleconomy.gov** | PHEV combined fuel economy (`_phev_fuel_economy`) | U.S. Government data — public domain (17 U.S.C. §105); site administered by ORNL for DOE/EPA | U.S. EPA & U.S. DOE, fueleconomy.gov vehicle data. <https://www.fueleconomy.gov/ws/> |
60
+ | **EV WATTS Public Database** — DOE/Energetics (INL, NREL, PNNL) | Workplace cohort calibration & validation bounds (`sales_mix_data`, `validation_bounds_curator`) | DOE-sponsored public dataset; attribution requested, not required | Pritchard, E. *EV Watts Public Database.* U.S. DOE, 2023. <https://doi.org/10.15483/1970735> |
61
+ | **California CVRP rebate statistics** | CA regional BEV/PHEV sales-mix weights (`sales_mix_data`) | © Center for Sustainable Energy (for CARB); no open-data license; aggregate facts, partly re-derived | California Clean Vehicle Rebate Project (CVRP), Center for Sustainable Energy / CARB. <https://cleanvehiclerebate.org/en/rebate-statistics> |
62
+ | **NYSERDA Drive Clean Rebate** | NY regional sales-mix weights (approximation) (`sales_mix_data`) | NY State Open Data (Open NY) Terms of Use — permissive, no attribution required | NYSERDA Electric Vehicle Drive Clean Rebate Data (Open NY). <https://data.ny.gov/Energy-Environment/NYSERDA-Electric-Vehicle-Drive-Clean-Rebate-Data-B/thd2-fu8y> |
63
+ | **Argonne National Laboratory — EV-FACTS** | National BEV/PHEV sales-mix shape (`sales_mix_data`) | Published DOE/Argonne analysis (© UChicago Argonne, LLC); aggregate share data is non-copyrightable fact | Argonne National Laboratory, Light-Duty Electric Drive Vehicles Monthly Sales Updates / EV-FACTS. <https://www.anl.gov/ev-facts/model-sales> |
64
+ | **NOAA NCEI** — Integrated Surface Database / U.S. Climate Normals 1991–2020 | Winter ambient-temperature uplift (`travel_week_builder`, `regions`) | U.S. Government work — public domain (NCEI applies CC0-1.0); attribution requested | NOAA National Centers for Environmental Information, Integrated Surface Database / U.S. Climate Normals 1991–2020. |
65
+ | **NREL technical reports** (TP-5400-81065, TP-5400-69031, CP-5400-85902) & EVI-Pro Lite | Modeling priors & validation bounds (`vehicle_archetypes`, `validation_bounds_curator`) | U.S. Government-sponsored publications; scholarly citation | National Renewable Energy Laboratory, U.S. DOE. (Report numbers cited inline in source.) |
66
+
67
+ **CAISO** market prices are *not* shipped with `ev-flow`. The optional
68
+ `caiso_data` dependency (imported behind a guard in `validator`) is a separate
69
+ package; if a downstream user supplies it, CAISO's own terms apply to that user.
70
+
71
+ ---
72
+
73
+ *This audit was performed against each source's official terms. Where a source
74
+ could not be verified to high confidence, it is credited conservatively. If you
75
+ are a rights holder and believe attribution here is incorrect, please open an
76
+ issue.*
@@ -0,0 +1,42 @@
1
+ # Changelog
2
+
3
+ All notable changes to **ev-flow** are documented here. This project follows
4
+ [Semantic Versioning](https://semver.org).
5
+
6
+ ## [3.0.2] - 2026-06-09
7
+
8
+ ### Added
9
+ - The PEP 561 `py.typed` marker now ships inside the `pev_synth` wheel, so
10
+ downstream type checkers (mypy, pyright) pick up ev-flow's inline type hints
11
+ when the package is installed from PyPI.
12
+ - `ATTRIBUTION.md` is now included in the source distribution, documenting every
13
+ upstream data source's license and the required SPEECh (CC BY 4.0) and U.S.
14
+ Census Bureau Data API notices.
15
+
16
+ ### Fixed
17
+ - Cross-platform correctness: all `meta.json`, `validation_report.json`, and
18
+ `validation_report.md` reads and writes in `api` and `validator` now pass an
19
+ explicit `encoding="utf-8"`, instead of relying on the platform default
20
+ encoding. This makes bundle metadata and validation reports round-trip
21
+ identically on Windows (where the default was previously `cp1252`).
22
+
23
+ ## [3.0.1] - 2026-06-02
24
+
25
+ ### Fixed
26
+ - Pin `pandas>=2.2,<3`. pandas 3.0 changed the default datetime resolution
27
+ from nanoseconds to microseconds, which broke the 15-minute plug-status
28
+ rasteriser, the SoC-trajectory parquet schema, and UTC/DST timestamp
29
+ handling for users who resolved pandas 3.0 on Python 3.11+. The published
30
+ 3.0.0 declared an uncapped `pandas>=2.2`; this release caps it.
31
+
32
+ ## [3.0.0] - 2026-06-01
33
+
34
+ ### Added
35
+ - Initial public release. Synthetic plug-in electric vehicle charging dataset
36
+ pipeline and library API, grounded in NHTS 2017 micro-data and a regional
37
+ sales-mix model across 8 US regions.
38
+ - PyPI distribution name `ev-flow`; Python import name `pev_synth`.
39
+
40
+ [3.0.2]: https://github.com/bertravacca/ev-flow/releases/tag/v3.0.2
41
+ [3.0.1]: https://github.com/bertravacca/ev-flow/releases/tag/v3.0.1
42
+ [3.0.0]: https://github.com/bertravacca/ev-flow/releases/tag/v3.0.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ev-flow
3
- Version: 3.0.0
3
+ Version: 3.0.2
4
4
  Summary: Synthetic plug-in electric vehicle charging dataset pipeline and library API.
5
5
  Project-URL: Homepage, https://github.com/bertravacca/ev-flow
6
6
  Project-URL: Repository, https://github.com/bertravacca/ev-flow
@@ -41,14 +41,16 @@ Classifier: Programming Language :: Python :: 3.13
41
41
  Classifier: Topic :: Scientific/Engineering
42
42
  Requires-Python: >=3.10
43
43
  Requires-Dist: numpy>=1.26
44
- Requires-Dist: pandas>=2.2
44
+ Requires-Dist: pandas<3,>=2.2
45
45
  Requires-Dist: pyarrow>=16
46
46
  Requires-Dist: pytz>=2024.1
47
47
  Requires-Dist: requests>=2.32
48
48
  Requires-Dist: scikit-learn>=1.4
49
49
  Requires-Dist: scipy>=1.12
50
50
  Provides-Extra: dev
51
+ Requires-Dist: bandit>=1.7; extra == 'dev'
51
52
  Requires-Dist: build>=1.2; extra == 'dev'
53
+ Requires-Dist: pip-audit>=2.7; extra == 'dev'
52
54
  Requires-Dist: pytest-cov>=5; extra == 'dev'
53
55
  Requires-Dist: pytest>=8; extra == 'dev'
54
56
  Requires-Dist: ruff>=0.5; extra == 'dev'
@@ -57,6 +59,14 @@ Description-Content-Type: text/markdown
57
59
 
58
60
  # ev-flow
59
61
 
62
+ [![CI](https://github.com/bertravacca/ev-flow/actions/workflows/ci.yml/badge.svg)](https://github.com/bertravacca/ev-flow/actions/workflows/ci.yml)
63
+ [![codecov](https://codecov.io/gh/bertravacca/ev-flow/branch/main/graph/badge.svg)](https://codecov.io/gh/bertravacca/ev-flow)
64
+ [![security](https://github.com/bertravacca/ev-flow/actions/workflows/security.yml/badge.svg)](https://github.com/bertravacca/ev-flow/actions/workflows/security.yml)
65
+ [![PyPI version](https://img.shields.io/pypi/v/ev-flow.svg)](https://pypi.org/project/ev-flow/)
66
+ [![Python versions](https://img.shields.io/pypi/pyversions/ev-flow.svg)](https://pypi.org/project/ev-flow/)
67
+ [![DOI](https://zenodo.org/badge/1245084094.svg)](https://zenodo.org/badge/latestdoi/1245084094)
68
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
69
+
60
70
  Synthetic plug-in electric vehicle (PEV) charging dataset pipeline and library API.
61
71
 
62
72
  `ev-flow` generates realistic, fleet-scale charging behavior for residential and workplace EVs, grounded in the National Household Travel Survey (NHTS) and a regional sales-mix model. It exposes both a low-level pipeline (NHTS loading, donor matching, travel-week building, plug-in modeling, state-of-charge trajectory, hourly rasterisation) and a clean `Fleet` / `Profile` library API for downstream studies.
@@ -154,6 +164,29 @@ pip install -e ".[dev]"
154
164
  pytest
155
165
  ```
156
166
 
167
+ ## Versioning
168
+
169
+ `ev-flow` follows [Semantic Versioning](https://semver.org). See
170
+ [`documentation/versioning.md`](documentation/versioning.md) for what counts as
171
+ a major/minor/patch change, the deprecation policy, and the distinction between
172
+ the package version and a cache's `methodology_version`.
173
+
174
+ ## Data sources & attribution
175
+
176
+ `ev-flow` is grounded in public data sources. Two upstream notices are required:
177
+
178
+ - **SPEECh Original Model** (Powell, Cezar & Rajagopal, Mendeley Data, 2021) is
179
+ licensed **CC BY 4.0**. ev-flow consumes a modified (pickle→JSON, reweighted)
180
+ subset of its driver-group mixtures. <https://doi.org/10.17632/gvk34mybtb.1>
181
+ - This product uses the **U.S. Census Bureau Data API** but is not endorsed or
182
+ certified by the Census Bureau.
183
+
184
+ Full per-source licensing, citations, and the modification statement are in
185
+ [`ATTRIBUTION.md`](ATTRIBUTION.md) (NHTS, Census ACS PUMS, EPA fueleconomy.gov,
186
+ EV WATTS, CVRP, NYSERDA, Argonne EV-FACTS, NOAA, NREL).
187
+
157
188
  ## License
158
189
 
159
- MIT. See `LICENSE`.
190
+ MIT. See `LICENSE`. Note that the MIT license covers the `ev-flow` *code*; the
191
+ upstream data sources retain their own licenses — see
192
+ [`ATTRIBUTION.md`](ATTRIBUTION.md).
@@ -1,5 +1,13 @@
1
1
  # ev-flow
2
2
 
3
+ [![CI](https://github.com/bertravacca/ev-flow/actions/workflows/ci.yml/badge.svg)](https://github.com/bertravacca/ev-flow/actions/workflows/ci.yml)
4
+ [![codecov](https://codecov.io/gh/bertravacca/ev-flow/branch/main/graph/badge.svg)](https://codecov.io/gh/bertravacca/ev-flow)
5
+ [![security](https://github.com/bertravacca/ev-flow/actions/workflows/security.yml/badge.svg)](https://github.com/bertravacca/ev-flow/actions/workflows/security.yml)
6
+ [![PyPI version](https://img.shields.io/pypi/v/ev-flow.svg)](https://pypi.org/project/ev-flow/)
7
+ [![Python versions](https://img.shields.io/pypi/pyversions/ev-flow.svg)](https://pypi.org/project/ev-flow/)
8
+ [![DOI](https://zenodo.org/badge/1245084094.svg)](https://zenodo.org/badge/latestdoi/1245084094)
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
10
+
3
11
  Synthetic plug-in electric vehicle (PEV) charging dataset pipeline and library API.
4
12
 
5
13
  `ev-flow` generates realistic, fleet-scale charging behavior for residential and workplace EVs, grounded in the National Household Travel Survey (NHTS) and a regional sales-mix model. It exposes both a low-level pipeline (NHTS loading, donor matching, travel-week building, plug-in modeling, state-of-charge trajectory, hourly rasterisation) and a clean `Fleet` / `Profile` library API for downstream studies.
@@ -97,6 +105,29 @@ pip install -e ".[dev]"
97
105
  pytest
98
106
  ```
99
107
 
108
+ ## Versioning
109
+
110
+ `ev-flow` follows [Semantic Versioning](https://semver.org). See
111
+ [`documentation/versioning.md`](documentation/versioning.md) for what counts as
112
+ a major/minor/patch change, the deprecation policy, and the distinction between
113
+ the package version and a cache's `methodology_version`.
114
+
115
+ ## Data sources & attribution
116
+
117
+ `ev-flow` is grounded in public data sources. Two upstream notices are required:
118
+
119
+ - **SPEECh Original Model** (Powell, Cezar & Rajagopal, Mendeley Data, 2021) is
120
+ licensed **CC BY 4.0**. ev-flow consumes a modified (pickle→JSON, reweighted)
121
+ subset of its driver-group mixtures. <https://doi.org/10.17632/gvk34mybtb.1>
122
+ - This product uses the **U.S. Census Bureau Data API** but is not endorsed or
123
+ certified by the Census Bureau.
124
+
125
+ Full per-source licensing, citations, and the modification statement are in
126
+ [`ATTRIBUTION.md`](ATTRIBUTION.md) (NHTS, Census ACS PUMS, EPA fueleconomy.gov,
127
+ EV WATTS, CVRP, NYSERDA, Argonne EV-FACTS, NOAA, NREL).
128
+
100
129
  ## License
101
130
 
102
- MIT. See `LICENSE`.
131
+ MIT. See `LICENSE`. Note that the MIT license covers the `ev-flow` *code*; the
132
+ upstream data sources retain their own licenses — see
133
+ [`ATTRIBUTION.md`](ATTRIBUTION.md).
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ev-flow"
7
- version = "3.0.0"
7
+ version = "3.0.2"
8
8
  description = "Synthetic plug-in electric vehicle charging dataset pipeline and library API."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -35,7 +35,7 @@ classifiers = [
35
35
  ]
36
36
  dependencies = [
37
37
  "numpy>=1.26",
38
- "pandas>=2.2",
38
+ "pandas>=2.2,<3",
39
39
  "pyarrow>=16",
40
40
  "requests>=2.32",
41
41
  "scikit-learn>=1.4",
@@ -50,6 +50,8 @@ dev = [
50
50
  "ruff>=0.5",
51
51
  "build>=1.2",
52
52
  "twine>=5",
53
+ "bandit>=1.7",
54
+ "pip-audit>=2.7",
53
55
  ]
54
56
 
55
57
  [project.urls]
@@ -73,6 +75,8 @@ only-include = [
73
75
  "src/pev_synth",
74
76
  "README.md",
75
77
  "LICENSE",
78
+ "ATTRIBUTION.md",
79
+ "CHANGELOG.md",
76
80
  "pyproject.toml",
77
81
  ]
78
82
 
@@ -52,7 +52,7 @@ from .api import (
52
52
  # "workplace"]`` (``fleet_depot`` was de-scoped — plan §2.6).
53
53
  from .regions import REGIONS, Region, list_profile_types, list_regions
54
54
 
55
- __version__ = "3.0.0"
55
+ __version__ = "3.0.2"
56
56
 
57
57
  __all__ = [
58
58
  "Fleet",
@@ -1471,7 +1471,7 @@ class Fleet:
1471
1471
  "api_version": _api_version,
1472
1472
  "storage_timezone": _STORAGE_TZ,
1473
1473
  }
1474
- with (target / "meta.json").open("w") as f:
1474
+ with (target / "meta.json").open("w", encoding="utf-8") as f:
1475
1475
  json.dump(meta_out, f, indent=2, default=str)
1476
1476
 
1477
1477
  return target
@@ -1498,7 +1498,7 @@ class Fleet:
1498
1498
  f"meta.json is required to load a Fleet bundle; "
1499
1499
  f"expected at {meta_file}"
1500
1500
  )
1501
- with meta_file.open() as f:
1501
+ with meta_file.open(encoding="utf-8") as f:
1502
1502
  meta = json.load(f)
1503
1503
  profile_type = meta.get("profile_type")
1504
1504
  if not isinstance(profile_type, str) or not profile_type:
@@ -1700,7 +1700,7 @@ def generate_profiles(
1700
1700
  meta: dict[str, Any] = {}
1701
1701
  if meta_file.exists():
1702
1702
  try:
1703
- with meta_file.open() as f:
1703
+ with meta_file.open(encoding="utf-8") as f:
1704
1704
  meta = json.load(f)
1705
1705
  except json.JSONDecodeError:
1706
1706
  meta = {}
@@ -0,0 +1 @@
1
+
@@ -2587,7 +2587,7 @@ def run(
2587
2587
  meta_for_checks: dict[str, Any] = {}
2588
2588
  meta_path = output_root / "meta.json"
2589
2589
  if meta_path.exists():
2590
- with meta_path.open() as f:
2590
+ with meta_path.open(encoding="utf-8") as f:
2591
2591
  meta_for_checks = json.load(f)
2592
2592
 
2593
2593
  # RFC-008.r — the legacy ``energy_kwh`` alias is acceptable ONLY when
@@ -2862,10 +2862,10 @@ def run(
2862
2862
  # Write JSON + MD.
2863
2863
  json_path = output_root / "validation_report.json"
2864
2864
  md_path = output_root / "validation_report.md"
2865
- with json_path.open("w") as f:
2865
+ with json_path.open("w", encoding="utf-8") as f:
2866
2866
  json.dump(report.to_dict(), f, indent=2, sort_keys=False, default=str)
2867
2867
  f.write("\n")
2868
- md_path.write_text(_emit_markdown(report) + "\n")
2868
+ md_path.write_text(_emit_markdown(report) + "\n", encoding="utf-8")
2869
2869
 
2870
2870
  # Update meta.json with M9 provenance (idempotent — overwrites the
2871
2871
  # `validator` key only; keeps everything else byte-stable except for
@@ -2958,7 +2958,8 @@ def _run_with_replicates(
2958
2958
  # Re-emit the markdown with the aggregated (mean, std, n=R) columns.
2959
2959
  md_path = parent_root / "validation_report.md"
2960
2960
  md_path.write_text(
2961
- _emit_markdown(canonical, replicate_stats=replicate_stats, R=R) + "\n"
2961
+ _emit_markdown(canonical, replicate_stats=replicate_stats, R=R) + "\n",
2962
+ encoding="utf-8",
2962
2963
  )
2963
2964
  # Re-emit JSON with the replicate stats appended.
2964
2965
  aggregated = canonical.to_dict()
@@ -2968,7 +2969,7 @@ def _run_with_replicates(
2968
2969
  }
2969
2970
  aggregated["replicates"] = {"R": R, "n_dirs": len(replicate_dirs)}
2970
2971
  json_path = parent_root / "validation_report.json"
2971
- with json_path.open("w") as f:
2972
+ with json_path.open("w", encoding="utf-8") as f:
2972
2973
  json.dump(aggregated, f, indent=2, sort_keys=False, default=str)
2973
2974
  f.write("\n")
2974
2975
 
@@ -2995,7 +2996,7 @@ def _update_meta_json(meta_path: Path, report: Report) -> None:
2995
2996
  :data:`MASTER_SEED`.
2996
2997
  """
2997
2998
  if meta_path.exists():
2998
- with meta_path.open() as f:
2999
+ with meta_path.open(encoding="utf-8") as f:
2999
3000
  meta = json.load(f)
3000
3001
  else:
3001
3002
  meta = {}
@@ -3015,7 +3016,7 @@ def _update_meta_json(meta_path: Path, report: Report) -> None:
3015
3016
  "report_json": "validation_report.json",
3016
3017
  "report_md": "validation_report.md",
3017
3018
  }
3018
- with meta_path.open("w") as f:
3019
+ with meta_path.open("w", encoding="utf-8") as f:
3019
3020
  json.dump(meta, f, indent=2, sort_keys=True)
3020
3021
  f.write("\n")
3021
3022
 
File without changes
File without changes
File without changes
File without changes