v2x-risk 0.1.0__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.
v2x_risk-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Benjamin Quito
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,209 @@
1
+ Metadata-Version: 2.4
2
+ Name: v2x-risk
3
+ Version: 0.1.0
4
+ Summary: Reproducible LSTM-GAT model for V2X traffic anomaly and collision-risk classification
5
+ Author: Benjamin Quito
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/benjaminquito/V2X
8
+ Project-URL: Repository, https://github.com/benjaminquito/V2X
9
+ Project-URL: Paper, https://doi.org/10.4236/ojsst.2026.163010
10
+ Project-URL: Issues, https://github.com/benjaminquito/V2X/issues
11
+ Keywords: v2x,traffic-anomaly-detection,collision-risk,lstm,graph-attention-network,reproducible-research
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Education
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: numpy<3,>=1.26
23
+ Requires-Dist: openpyxl<4,>=3.1
24
+ Requires-Dist: pandas<3,>=2.1
25
+ Requires-Dist: PyYAML<7,>=6
26
+ Requires-Dist: scikit-learn<2,>=1.3
27
+ Requires-Dist: torch<3,>=2.2
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest<9,>=8; extra == "dev"
30
+ Requires-Dist: ruff<1,>=0.9; extra == "dev"
31
+ Dynamic: license-file
32
+
33
+ # V2X Traffic Anomaly and Collision-Risk Model
34
+
35
+ [![CI](https://github.com/benjaminquito/V2X/actions/workflows/ci.yml/badge.svg)](https://github.com/benjaminquito/V2X/actions/workflows/ci.yml)
36
+ [![PyPI](https://img.shields.io/pypi/v/v2x-risk.svg)](https://pypi.org/project/v2x-risk/)
37
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
38
+
39
+ This repository is a reproducible implementation of the revised model associated with
40
+ *Predicting Traffic Anomalies and Collision Risks in V2X Systems: A Deep Learning Approach
41
+ Using LSTM and GNN*. It implements the later reviewer-response design:
42
+
43
+ - a two-layer LSTM for 15-step vehicle histories;
44
+ - a two-layer, multi-head Graph Attention Network (GAT) for per-frame vehicle interactions;
45
+ - directed proximity edges between vehicles less than 20 m apart;
46
+ - a trainable softmax attention module that fuses temporal and spatial representations; and
47
+ - three-class prediction: Low (0), Medium (1), and High (2) risk.
48
+
49
+ No trained weights or real-data results are claimed by this repository. The NGSIM data is not
50
+ redistributed. Figures stated in the manuscript or later development chat are historical claims and
51
+ must not be treated as results reproduced by this code.
52
+
53
+ ## Why this implementation differs from the uploaded manuscript
54
+
55
+ The uploaded manuscript describes an earlier model in several places: 50-step, three-feature
56
+ sequences; two GCN layers; continuous risk scores; and fixed 0.6/0.4 fusion. The later revision
57
+ changed these to 15-step, four-feature inputs; GAT; three-class outputs; and learned attention
58
+ fusion. This repository implements the later design requested for the reviewer response while
59
+ preserving the original claims as historical metadata only. See
60
+ [`docs/METHODOLOGY.md`](docs/METHODOLOGY.md) for the complete reconciliation.
61
+
62
+ The earlier chat evaluation paired LSTM samples and graphs by truncating both collections to the
63
+ same length. That does not establish that the two inputs describe the same vehicle and frame. Here,
64
+ each graph node is built only when that exact vehicle has a valid history ending at the graph's
65
+ frame. Both branches therefore operate on an explicitly aligned sample.
66
+
67
+ The later chat's 99.90% accuracy and 1.00 weighted-F1 claim is not a repository baseline: the shared
68
+ evaluation snippet did not demonstrate trained fusion weights or key-based modality alignment. It
69
+ is mentioned only to prevent accidental reuse as a regenerated result.
70
+
71
+ ## Repository layout
72
+
73
+ ```text
74
+ configs/ Full-data and smoke-test configurations
75
+ data/ Raw/processed placeholders (generated data is ignored)
76
+ docs/ Data, methodology, and reproducibility notes
77
+ results/ Historical claims and regenerated-output placeholder
78
+ src/v2x_risk/ Preprocessing, model, training, and evaluation code
79
+ tests/ Unit and end-to-end smoke tests
80
+ ```
81
+
82
+ ## Installation
83
+
84
+ Python 3.10 or newer is required.
85
+
86
+ Install the published package:
87
+
88
+ ```bash
89
+ python -m pip install v2x-risk
90
+ ```
91
+
92
+ For repository development, create an isolated environment and install the editable package:
93
+
94
+ ```bash
95
+ python3 -m venv .venv
96
+ source .venv/bin/activate
97
+ python -m pip install --upgrade pip
98
+ python -m pip install -e ".[dev]"
99
+ ```
100
+
101
+ To reproduce the exact dependency versions used for the repository smoke test, install
102
+ `requirements-lock.txt` before the editable package:
103
+
104
+ ```bash
105
+ python -m pip install -r requirements-lock.txt
106
+ python -m pip install --no-deps -e .
107
+ ```
108
+
109
+ PyTorch is the only deep-learning dependency. The GAT layer uses native PyTorch scatter operations,
110
+ so no separately compiled graph library is required.
111
+
112
+ ## Quick execution check
113
+
114
+ The smoke run creates a small synthetic, NGSIM-shaped dataset, preprocesses it, trains one epoch,
115
+ evaluates all three branches, and writes a visual report:
116
+
117
+ ```bash
118
+ python -m v2x_risk.smoke --config configs/smoke.yaml
119
+ ```
120
+
121
+ Smoke metrics only verify that the pipeline executes. They are not research results and must not be
122
+ compared with the paper.
123
+
124
+ Open `results/regenerated/smoke/run_report.html` after the command completes to inspect the visual
125
+ pipeline summary.
126
+
127
+ ## Reproduce with NGSIM
128
+
129
+ 1. Obtain an NGSIM trajectory CSV from the
130
+ [U.S. DOT NGSIM Open Data portal](https://data.transportation.gov/stories/s/Next-Generation-Simulation-NGSIM-Open-Data/i5zb-xe34/)
131
+ and place it at `data/raw/NGSIM.csv`.
132
+ 2. Review [`configs/default.yaml`](configs/default.yaml), especially the coordinate conversion and
133
+ risk-label assumptions.
134
+ 3. Run the complete pipeline:
135
+
136
+ ```bash
137
+ python -m v2x_risk.preprocess --config configs/default.yaml
138
+ python -m v2x_risk.train --config configs/default.yaml
139
+ python -m v2x_risk.evaluate --config configs/default.yaml --split test
140
+ ```
141
+
142
+ Or use:
143
+
144
+ ```bash
145
+ ./scripts/reproduce.sh configs/default.yaml
146
+ ```
147
+
148
+ The loader detects CSV and Excel OOXML content from the file signature. This means an uploaded
149
+ workbook can still be read if it was accidentally named `NGSIM.csv`, although using the correct
150
+ `.xlsx` suffix is recommended.
151
+
152
+ Generated files include:
153
+
154
+ - `data/processed/ngsim_aligned_graph_windows.npz`: aligned graph/history samples;
155
+ - `data/processed/scaler.json`: train-only min-max parameters;
156
+ - `data/processed/manifest.json`: source hash, counts, split sizes, and preprocessing metadata;
157
+ - `results/regenerated/best_model.pt`: best checkpoint by validation macro F1;
158
+ - `results/regenerated/metrics.json`: training history and branch-level results;
159
+ - `results/regenerated/test_metrics.json`: independently regenerated test metrics; and
160
+ - `results/regenerated/test_predictions.csv`: per-frame predictions and learned fusion weights;
161
+ - `results/regenerated/run_summary.txt`: a plain-language completion summary; and
162
+ - `results/regenerated/run_report.html`: a visual completion and results dashboard.
163
+
164
+ At the end of `scripts/reproduce.sh`, the terminal prints `V2X RUN COMPLETED SUCCESSFULLY` followed
165
+ by the main dataset counts, held-out metrics, and output locations. Open `run_report.html` in a web
166
+ browser to verify the completed run visually.
167
+
168
+ The default configuration follows the paper's 70/15/15 proportions and five training epochs. The
169
+ split is chronological and inserts a 14-frame gap at boundaries to reduce leakage from overlapping
170
+ 15-step windows. Min-max scaling is fit on the training split only.
171
+
172
+ ## Required CSV columns
173
+
174
+ Core inputs are `Vehicle_ID`, `Frame_ID`, `Local_X`, and `Local_Y`. The preferred input also has
175
+ `v_Vel` and `v_Acc`. When either kinematic field is absent, preprocessing derives it from
176
+ longitudinal position and elapsed time using the explicit settings under `data.kinematics`.
177
+ Existing kinematic columns are never replaced. If the CSV already contains `Risk_Class`, it is used
178
+ after validation. Otherwise, `Time_Headway` and `Space_Headway` are also required and labels are
179
+ generated from the explicit thresholds in the configuration. The manuscript did not state the
180
+ numerical labeling thresholds, so the supplied values are transparent repository assumptions that
181
+ researchers should review or replace.
182
+
183
+ Combined files should include `Location`. Preprocessing keeps locations separate, segments reused
184
+ vehicle IDs into contiguous frame runs, and keys graph snapshots by location and timestamp. This
185
+ prevents observations from separate roads or recording sessions from entering the same trajectory
186
+ or proximity graph.
187
+
188
+ Derived speed and acceleration are a transparent compatibility fallback. Report their use and do
189
+ not treat results based on derived kinematics as directly comparable to experiments that used the
190
+ original NGSIM `v_Vel` and `v_Acc` measurements.
191
+
192
+ See [`docs/DATA.md`](docs/DATA.md) before combining NGSIM sites or changing units.
193
+
194
+ ## Tests
195
+
196
+ ```bash
197
+ pytest
198
+ ruff check .
199
+ ```
200
+
201
+ The test suite checks class encoding, radius-graph construction, split gaps, tensor shapes, fusion
202
+ weight normalization, and an end-to-end synthetic run.
203
+
204
+ ## Result-reporting rule
205
+
206
+ Only files produced under `results/regenerated/` by a completed run may be described as regenerated
207
+ results. Report the raw-data SHA-256 hash, configuration, seed, split policy, and checkpoint with
208
+ every result. Do not copy the 99.90% accuracy or 1.00 F1 claim into a new report unless this aligned
209
+ pipeline independently produces it on the intended held-out data.
@@ -0,0 +1,177 @@
1
+ # V2X Traffic Anomaly and Collision-Risk Model
2
+
3
+ [![CI](https://github.com/benjaminquito/V2X/actions/workflows/ci.yml/badge.svg)](https://github.com/benjaminquito/V2X/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/v2x-risk.svg)](https://pypi.org/project/v2x-risk/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
+
7
+ This repository is a reproducible implementation of the revised model associated with
8
+ *Predicting Traffic Anomalies and Collision Risks in V2X Systems: A Deep Learning Approach
9
+ Using LSTM and GNN*. It implements the later reviewer-response design:
10
+
11
+ - a two-layer LSTM for 15-step vehicle histories;
12
+ - a two-layer, multi-head Graph Attention Network (GAT) for per-frame vehicle interactions;
13
+ - directed proximity edges between vehicles less than 20 m apart;
14
+ - a trainable softmax attention module that fuses temporal and spatial representations; and
15
+ - three-class prediction: Low (0), Medium (1), and High (2) risk.
16
+
17
+ No trained weights or real-data results are claimed by this repository. The NGSIM data is not
18
+ redistributed. Figures stated in the manuscript or later development chat are historical claims and
19
+ must not be treated as results reproduced by this code.
20
+
21
+ ## Why this implementation differs from the uploaded manuscript
22
+
23
+ The uploaded manuscript describes an earlier model in several places: 50-step, three-feature
24
+ sequences; two GCN layers; continuous risk scores; and fixed 0.6/0.4 fusion. The later revision
25
+ changed these to 15-step, four-feature inputs; GAT; three-class outputs; and learned attention
26
+ fusion. This repository implements the later design requested for the reviewer response while
27
+ preserving the original claims as historical metadata only. See
28
+ [`docs/METHODOLOGY.md`](docs/METHODOLOGY.md) for the complete reconciliation.
29
+
30
+ The earlier chat evaluation paired LSTM samples and graphs by truncating both collections to the
31
+ same length. That does not establish that the two inputs describe the same vehicle and frame. Here,
32
+ each graph node is built only when that exact vehicle has a valid history ending at the graph's
33
+ frame. Both branches therefore operate on an explicitly aligned sample.
34
+
35
+ The later chat's 99.90% accuracy and 1.00 weighted-F1 claim is not a repository baseline: the shared
36
+ evaluation snippet did not demonstrate trained fusion weights or key-based modality alignment. It
37
+ is mentioned only to prevent accidental reuse as a regenerated result.
38
+
39
+ ## Repository layout
40
+
41
+ ```text
42
+ configs/ Full-data and smoke-test configurations
43
+ data/ Raw/processed placeholders (generated data is ignored)
44
+ docs/ Data, methodology, and reproducibility notes
45
+ results/ Historical claims and regenerated-output placeholder
46
+ src/v2x_risk/ Preprocessing, model, training, and evaluation code
47
+ tests/ Unit and end-to-end smoke tests
48
+ ```
49
+
50
+ ## Installation
51
+
52
+ Python 3.10 or newer is required.
53
+
54
+ Install the published package:
55
+
56
+ ```bash
57
+ python -m pip install v2x-risk
58
+ ```
59
+
60
+ For repository development, create an isolated environment and install the editable package:
61
+
62
+ ```bash
63
+ python3 -m venv .venv
64
+ source .venv/bin/activate
65
+ python -m pip install --upgrade pip
66
+ python -m pip install -e ".[dev]"
67
+ ```
68
+
69
+ To reproduce the exact dependency versions used for the repository smoke test, install
70
+ `requirements-lock.txt` before the editable package:
71
+
72
+ ```bash
73
+ python -m pip install -r requirements-lock.txt
74
+ python -m pip install --no-deps -e .
75
+ ```
76
+
77
+ PyTorch is the only deep-learning dependency. The GAT layer uses native PyTorch scatter operations,
78
+ so no separately compiled graph library is required.
79
+
80
+ ## Quick execution check
81
+
82
+ The smoke run creates a small synthetic, NGSIM-shaped dataset, preprocesses it, trains one epoch,
83
+ evaluates all three branches, and writes a visual report:
84
+
85
+ ```bash
86
+ python -m v2x_risk.smoke --config configs/smoke.yaml
87
+ ```
88
+
89
+ Smoke metrics only verify that the pipeline executes. They are not research results and must not be
90
+ compared with the paper.
91
+
92
+ Open `results/regenerated/smoke/run_report.html` after the command completes to inspect the visual
93
+ pipeline summary.
94
+
95
+ ## Reproduce with NGSIM
96
+
97
+ 1. Obtain an NGSIM trajectory CSV from the
98
+ [U.S. DOT NGSIM Open Data portal](https://data.transportation.gov/stories/s/Next-Generation-Simulation-NGSIM-Open-Data/i5zb-xe34/)
99
+ and place it at `data/raw/NGSIM.csv`.
100
+ 2. Review [`configs/default.yaml`](configs/default.yaml), especially the coordinate conversion and
101
+ risk-label assumptions.
102
+ 3. Run the complete pipeline:
103
+
104
+ ```bash
105
+ python -m v2x_risk.preprocess --config configs/default.yaml
106
+ python -m v2x_risk.train --config configs/default.yaml
107
+ python -m v2x_risk.evaluate --config configs/default.yaml --split test
108
+ ```
109
+
110
+ Or use:
111
+
112
+ ```bash
113
+ ./scripts/reproduce.sh configs/default.yaml
114
+ ```
115
+
116
+ The loader detects CSV and Excel OOXML content from the file signature. This means an uploaded
117
+ workbook can still be read if it was accidentally named `NGSIM.csv`, although using the correct
118
+ `.xlsx` suffix is recommended.
119
+
120
+ Generated files include:
121
+
122
+ - `data/processed/ngsim_aligned_graph_windows.npz`: aligned graph/history samples;
123
+ - `data/processed/scaler.json`: train-only min-max parameters;
124
+ - `data/processed/manifest.json`: source hash, counts, split sizes, and preprocessing metadata;
125
+ - `results/regenerated/best_model.pt`: best checkpoint by validation macro F1;
126
+ - `results/regenerated/metrics.json`: training history and branch-level results;
127
+ - `results/regenerated/test_metrics.json`: independently regenerated test metrics; and
128
+ - `results/regenerated/test_predictions.csv`: per-frame predictions and learned fusion weights;
129
+ - `results/regenerated/run_summary.txt`: a plain-language completion summary; and
130
+ - `results/regenerated/run_report.html`: a visual completion and results dashboard.
131
+
132
+ At the end of `scripts/reproduce.sh`, the terminal prints `V2X RUN COMPLETED SUCCESSFULLY` followed
133
+ by the main dataset counts, held-out metrics, and output locations. Open `run_report.html` in a web
134
+ browser to verify the completed run visually.
135
+
136
+ The default configuration follows the paper's 70/15/15 proportions and five training epochs. The
137
+ split is chronological and inserts a 14-frame gap at boundaries to reduce leakage from overlapping
138
+ 15-step windows. Min-max scaling is fit on the training split only.
139
+
140
+ ## Required CSV columns
141
+
142
+ Core inputs are `Vehicle_ID`, `Frame_ID`, `Local_X`, and `Local_Y`. The preferred input also has
143
+ `v_Vel` and `v_Acc`. When either kinematic field is absent, preprocessing derives it from
144
+ longitudinal position and elapsed time using the explicit settings under `data.kinematics`.
145
+ Existing kinematic columns are never replaced. If the CSV already contains `Risk_Class`, it is used
146
+ after validation. Otherwise, `Time_Headway` and `Space_Headway` are also required and labels are
147
+ generated from the explicit thresholds in the configuration. The manuscript did not state the
148
+ numerical labeling thresholds, so the supplied values are transparent repository assumptions that
149
+ researchers should review or replace.
150
+
151
+ Combined files should include `Location`. Preprocessing keeps locations separate, segments reused
152
+ vehicle IDs into contiguous frame runs, and keys graph snapshots by location and timestamp. This
153
+ prevents observations from separate roads or recording sessions from entering the same trajectory
154
+ or proximity graph.
155
+
156
+ Derived speed and acceleration are a transparent compatibility fallback. Report their use and do
157
+ not treat results based on derived kinematics as directly comparable to experiments that used the
158
+ original NGSIM `v_Vel` and `v_Acc` measurements.
159
+
160
+ See [`docs/DATA.md`](docs/DATA.md) before combining NGSIM sites or changing units.
161
+
162
+ ## Tests
163
+
164
+ ```bash
165
+ pytest
166
+ ruff check .
167
+ ```
168
+
169
+ The test suite checks class encoding, radius-graph construction, split gaps, tensor shapes, fusion
170
+ weight normalization, and an end-to-end synthetic run.
171
+
172
+ ## Result-reporting rule
173
+
174
+ Only files produced under `results/regenerated/` by a completed run may be described as regenerated
175
+ results. Report the raw-data SHA-256 hash, configuration, seed, split policy, and checkpoint with
176
+ every result. Do not copy the 99.90% accuracy or 1.00 F1 claim into a new report unless this aligned
177
+ pipeline independently produces it on the intended held-out data.
@@ -0,0 +1,67 @@
1
+ [build-system]
2
+ requires = ["setuptools>=69", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "v2x-risk"
7
+ version = "0.1.0"
8
+ description = "Reproducible LSTM-GAT model for V2X traffic anomaly and collision-risk classification"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ authors = [{name = "Benjamin Quito"}]
12
+ license = "MIT"
13
+ keywords = [
14
+ "v2x",
15
+ "traffic-anomaly-detection",
16
+ "collision-risk",
17
+ "lstm",
18
+ "graph-attention-network",
19
+ "reproducible-research",
20
+ ]
21
+ classifiers = [
22
+ "Development Status :: 3 - Alpha",
23
+ "Intended Audience :: Education",
24
+ "Intended Audience :: Science/Research",
25
+ "Programming Language :: Python :: 3",
26
+ "Programming Language :: Python :: 3.10",
27
+ "Programming Language :: Python :: 3.11",
28
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
29
+ ]
30
+ dependencies = [
31
+ "numpy>=1.26,<3",
32
+ "openpyxl>=3.1,<4",
33
+ "pandas>=2.1,<3",
34
+ "PyYAML>=6,<7",
35
+ "scikit-learn>=1.3,<2",
36
+ "torch>=2.2,<3",
37
+ ]
38
+
39
+ [project.optional-dependencies]
40
+ dev = [
41
+ "pytest>=8,<9",
42
+ "ruff>=0.9,<1",
43
+ ]
44
+
45
+ [project.scripts]
46
+ v2x-preprocess = "v2x_risk.preprocess:main"
47
+ v2x-train = "v2x_risk.train:main"
48
+ v2x-evaluate = "v2x_risk.evaluate:main"
49
+ v2x-report = "v2x_risk.report:main"
50
+ v2x-smoke = "v2x_risk.smoke:main"
51
+
52
+ [project.urls]
53
+ Homepage = "https://github.com/benjaminquito/V2X"
54
+ Repository = "https://github.com/benjaminquito/V2X"
55
+ Paper = "https://doi.org/10.4236/ojsst.2026.163010"
56
+ Issues = "https://github.com/benjaminquito/V2X/issues"
57
+
58
+ [tool.setuptools.packages.find]
59
+ where = ["src"]
60
+
61
+ [tool.pytest.ini_options]
62
+ addopts = "-q"
63
+ testpaths = ["tests"]
64
+
65
+ [tool.ruff]
66
+ line-length = 100
67
+ target-version = "py310"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """Reproducible V2X spatiotemporal risk classification."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,59 @@
1
+ from __future__ import annotations
2
+
3
+ from copy import deepcopy
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ import yaml
8
+
9
+
10
+ class ConfigError(ValueError):
11
+ """Raised when a configuration is incomplete or internally inconsistent."""
12
+
13
+
14
+ def load_config(path: str | Path) -> dict[str, Any]:
15
+ config_path = Path(path)
16
+ with config_path.open("r", encoding="utf-8") as handle:
17
+ config = yaml.safe_load(handle)
18
+ if not isinstance(config, dict):
19
+ raise ConfigError(f"Configuration must be a mapping: {config_path}")
20
+ validate_config(config)
21
+ return config
22
+
23
+
24
+ def validate_config(config: dict[str, Any]) -> None:
25
+ for section in ("data", "model", "training"):
26
+ if section not in config:
27
+ raise ConfigError(f"Missing required configuration section: {section}")
28
+
29
+ data = config["data"]
30
+ model = config["model"]
31
+ split = data.get("split", {})
32
+ ratios = [float(split.get(name, -1)) for name in ("train", "validation", "test")]
33
+ if any(value <= 0 for value in ratios) or abs(sum(ratios) - 1.0) > 1e-9:
34
+ raise ConfigError("data.split train/validation/test values must be positive and sum to 1")
35
+ if int(data.get("sequence_length", 0)) < 2:
36
+ raise ConfigError("data.sequence_length must be at least 2")
37
+ if float(data.get("graph_radius_m", 0)) <= 0:
38
+ raise ConfigError("data.graph_radius_m must be positive")
39
+ if len(data.get("feature_columns", [])) != int(model.get("input_dim", -1)):
40
+ raise ConfigError("model.input_dim must match the number of data.feature_columns")
41
+ if int(model.get("num_classes", 0)) != 3:
42
+ raise ConfigError("This implementation requires exactly three risk classes")
43
+
44
+
45
+ def with_overrides(config: dict[str, Any], overrides: dict[str, Any]) -> dict[str, Any]:
46
+ """Return a deep copy with recursively merged overrides."""
47
+
48
+ merged = deepcopy(config)
49
+
50
+ def merge(target: dict[str, Any], source: dict[str, Any]) -> None:
51
+ for key, value in source.items():
52
+ if isinstance(value, dict) and isinstance(target.get(key), dict):
53
+ merge(target[key], value)
54
+ else:
55
+ target[key] = value
56
+
57
+ merge(merged, overrides)
58
+ validate_config(merged)
59
+ return merged