brushline 0.1.0__py3-none-any.whl
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.
- brushline/__init__.py +7 -0
- brushline/build/__init__.py +7 -0
- brushline/build/compiler.py +278 -0
- brushline/cli.py +476 -0
- brushline/demo/__init__.py +1 -0
- brushline/demo/builder.py +103 -0
- brushline/demo/deploy.py +32 -0
- brushline/demo/pilot.py +80 -0
- brushline/demo/portfolio.py +103 -0
- brushline/demo/validation.py +48 -0
- brushline/exchange/__init__.py +31 -0
- brushline/exchange/audit.py +63 -0
- brushline/exchange/bids.py +67 -0
- brushline/exchange/contractor.py +60 -0
- brushline/exchange/owner.py +51 -0
- brushline/exchange/plan.py +102 -0
- brushline/exchange/validation.py +131 -0
- brushline/fixtures/__init__.py +19 -0
- brushline/fixtures/synthetic.py +1042 -0
- brushline/instance_ops.py +305 -0
- brushline/instance_template.py +81 -0
- brushline/integrations/__init__.py +23 -0
- brushline/integrations/canopy.py +700 -0
- brushline/integrations/capabilities.py +950 -0
- brushline/integrations/catalogue.py +805 -0
- brushline/integrations/femic.py +318 -0
- brushline/integrations/fetch.py +1112 -0
- brushline/integrations/freshforge.py +447 -0
- brushline/integrations/lidar.py +800 -0
- brushline/integrations/lidar_benchmark.py +466 -0
- brushline/integrations/lidar_coverage.py +533 -0
- brushline/integrations/terrain.py +586 -0
- brushline/model/__init__.py +71 -0
- brushline/model/archive.py +264 -0
- brushline/model/corridor.py +559 -0
- brushline/model/demo.py +217 -0
- brushline/model/plan.py +337 -0
- brushline/model/run.py +164 -0
- brushline/model/schema.py +587 -0
- brushline/model/state.py +530 -0
- brushline/model/timeline.py +167 -0
- brushline/model/validation.py +101 -0
- brushline/monitoring/__init__.py +69 -0
- brushline/monitoring/mission.py +282 -0
- brushline/monitoring/observation.py +165 -0
- brushline/monitoring/recalibration.py +107 -0
- brushline/monitoring/replan.py +58 -0
- brushline/monitoring/validation.py +102 -0
- brushline/monitoring/value.py +225 -0
- brushline/optimization/__init__.py +199 -0
- brushline/optimization/adapter.py +877 -0
- brushline/optimization/budget.py +551 -0
- brushline/optimization/evaluator.py +966 -0
- brushline/optimization/formulation.py +681 -0
- brushline/optimization/operators.py +910 -0
- brushline/optimization/pareto.py +511 -0
- brushline/optimization/telemetry.py +673 -0
- brushline/optimization/validation.py +349 -0
- brushline/resources/__init__.py +3 -0
- brushline/resources/instance/QUICKSTART.md +28 -0
- brushline/resources/instance/README.md +34 -0
- brushline/resources/instance/__init__.py +3 -0
- brushline/resources/instance/config/brushline/rebuild.steps.yaml +57 -0
- brushline/resources/instance/config/brushline/scenario.yaml +11 -0
- brushline/resources/instance/config/brushline/standards.yaml +5 -0
- brushline/resources/instance/config/brushline/targets.yaml +5 -0
- brushline/resources/instance/config/rebuild.allowlist.yaml +8 -0
- brushline/resources/instance/config/rebuild.spec.yaml +78 -0
- brushline/resources/instance/evidence/README.md +11 -0
- brushline/resources/instance/runbooks/REBUILD_RUNBOOK.md +54 -0
- brushline/resources/instance/tests/README.md +9 -0
- brushline/run.py +128 -0
- brushline/sim/__init__.py +14 -0
- brushline/sim/engine.py +363 -0
- brushline/sim/schedule.py +124 -0
- brushline/state_init.py +859 -0
- brushline/strategic/__init__.py +159 -0
- brushline/strategic/bau.py +453 -0
- brushline/strategic/dynamic_program.py +492 -0
- brushline/strategic/monitoring.py +417 -0
- brushline/strategic/portfolio.py +787 -0
- brushline/strategic/reports.py +294 -0
- brushline/strategic/scenario.py +462 -0
- brushline/strategic/validation.py +450 -0
- brushline/tactical/__init__.py +132 -0
- brushline/tactical/access.py +219 -0
- brushline/tactical/fhops.py +251 -0
- brushline/tactical/operations.py +252 -0
- brushline/tactical/program.py +323 -0
- brushline/tactical/resources.py +283 -0
- brushline/tactical/timeline.py +220 -0
- brushline/tactical/validation.py +389 -0
- brushline/vegetation/__init__.py +129 -0
- brushline/vegetation/calibration.py +710 -0
- brushline/vegetation/growth.py +592 -0
- brushline/vegetation/hazards.py +644 -0
- brushline/vegetation/model_card.py +475 -0
- brushline/vegetation/state_space.py +703 -0
- brushline/vegetation/treatment.py +589 -0
- brushline/vegetation/validation.py +704 -0
- brushline/verification/__init__.py +1 -0
- brushline/verification/acceptance.py +251 -0
- brushline/web/__init__.py +4 -0
- brushline/web/model_builder.py +1362 -0
- brushline/web/static/model-builder.css +176 -0
- brushline/web/static/model-builder.html +142 -0
- brushline/web/static/model-builder.js +509 -0
- brushline/web/static/studio.css +1 -0
- brushline/web/static/studio.html +13 -0
- brushline/web/static/studio.js +41 -0
- brushline/web/studio.py +233 -0
- brushline-0.1.0.dist-info/METADATA +162 -0
- brushline-0.1.0.dist-info/RECORD +117 -0
- brushline-0.1.0.dist-info/WHEEL +5 -0
- brushline-0.1.0.dist-info/entry_points.txt +5 -0
- brushline-0.1.0.dist-info/licenses/LICENSE +201 -0
- brushline-0.1.0.dist-info/top_level.txt +1 -0
brushline/__init__.py
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"""Compile the synthetic corridor fixture into a validated Brushline dataset.
|
|
2
|
+
|
|
3
|
+
The compiler reads the fixture's dataset, corridor graph, and state contract, joins the service
|
|
4
|
+
standards and corridor attributes onto the segments, and writes the compiled segments and states
|
|
5
|
+
tables plus a dataset manifest. It is the smallest end-to-end compile path; the production compiler
|
|
6
|
+
is P2 and P3.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from datetime import UTC, datetime
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
import pandas as pd
|
|
16
|
+
|
|
17
|
+
from brushline.model.corridor import CorridorNetwork, validate_corridor_network
|
|
18
|
+
from brushline.model.schema import (
|
|
19
|
+
ColumnSpec,
|
|
20
|
+
DatasetManifest,
|
|
21
|
+
Provenance,
|
|
22
|
+
TableSpec,
|
|
23
|
+
file_sha256,
|
|
24
|
+
load_manifest,
|
|
25
|
+
validate_dataset,
|
|
26
|
+
write_manifest,
|
|
27
|
+
)
|
|
28
|
+
from brushline.model.state import StateContract, validate_state_contract
|
|
29
|
+
|
|
30
|
+
SEGMENT_COLUMNS: tuple[str, ...] = (
|
|
31
|
+
"segment_id",
|
|
32
|
+
"corridor_id",
|
|
33
|
+
"length_m",
|
|
34
|
+
"geometry_wkt",
|
|
35
|
+
"span_id",
|
|
36
|
+
"sequence",
|
|
37
|
+
"treatment_unit_id",
|
|
38
|
+
"service_standard_id",
|
|
39
|
+
"access_point_id",
|
|
40
|
+
"max_height_m",
|
|
41
|
+
"min_clearance_m",
|
|
42
|
+
)
|
|
43
|
+
STATE_COLUMNS: tuple[str, ...] = (
|
|
44
|
+
"segment_id",
|
|
45
|
+
"state_year",
|
|
46
|
+
"functional_group",
|
|
47
|
+
"state_class",
|
|
48
|
+
"height_m",
|
|
49
|
+
"cover_pct",
|
|
50
|
+
"density_per_ha",
|
|
51
|
+
"age_years",
|
|
52
|
+
"uncertainty_confidence",
|
|
53
|
+
"source",
|
|
54
|
+
"source_version",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass(frozen=True)
|
|
59
|
+
class CompiledDataset:
|
|
60
|
+
"""The output of one fixture compile.
|
|
61
|
+
|
|
62
|
+
Parameters
|
|
63
|
+
----------
|
|
64
|
+
root : Path
|
|
65
|
+
Compiled dataset directory.
|
|
66
|
+
manifest : DatasetManifest
|
|
67
|
+
Dataset manifest.
|
|
68
|
+
segments : pandas.DataFrame
|
|
69
|
+
Compiled segment table.
|
|
70
|
+
states : pandas.DataFrame
|
|
71
|
+
Compiled state table.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
root: Path
|
|
75
|
+
manifest: DatasetManifest
|
|
76
|
+
segments: pd.DataFrame
|
|
77
|
+
states: pd.DataFrame
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def compile_fixture(
|
|
81
|
+
fixture_root: Path,
|
|
82
|
+
out: Path,
|
|
83
|
+
*,
|
|
84
|
+
force: bool = False,
|
|
85
|
+
) -> CompiledDataset:
|
|
86
|
+
"""Compile the synthetic fixture into a validated dataset.
|
|
87
|
+
|
|
88
|
+
Parameters
|
|
89
|
+
----------
|
|
90
|
+
fixture_root : Path
|
|
91
|
+
Fixture directory, such as ``examples/synthetic-corridor``.
|
|
92
|
+
out : Path
|
|
93
|
+
Compiled dataset directory.
|
|
94
|
+
force : bool, optional
|
|
95
|
+
Overwrite an existing dataset directory. Defaults to ``False``.
|
|
96
|
+
|
|
97
|
+
Returns
|
|
98
|
+
-------
|
|
99
|
+
CompiledDataset
|
|
100
|
+
The compiled directory, manifest, segments, and states.
|
|
101
|
+
|
|
102
|
+
Raises
|
|
103
|
+
------
|
|
104
|
+
FileExistsError
|
|
105
|
+
When the output directory is not empty and ``force`` is false.
|
|
106
|
+
ValueError
|
|
107
|
+
When a source contract fails validation.
|
|
108
|
+
"""
|
|
109
|
+
fixture = Path(fixture_root).expanduser().resolve()
|
|
110
|
+
source_report = validate_dataset(fixture / "dataset")
|
|
111
|
+
if not source_report.is_valid:
|
|
112
|
+
raise ValueError("; ".join(source_report.errors))
|
|
113
|
+
source_manifest = load_manifest(fixture / "dataset")
|
|
114
|
+
segments_source = pd.read_csv(
|
|
115
|
+
fixture / "dataset" / "segments.csv",
|
|
116
|
+
dtype={
|
|
117
|
+
"segment_id": "string",
|
|
118
|
+
"corridor_id": "string",
|
|
119
|
+
"geometry_wkt": "string",
|
|
120
|
+
},
|
|
121
|
+
)
|
|
122
|
+
network = CorridorNetwork.model_validate_json((fixture / "corridor.json").read_text())
|
|
123
|
+
network_report = validate_corridor_network(network)
|
|
124
|
+
if not network_report.is_valid:
|
|
125
|
+
raise ValueError("; ".join(network_report.errors))
|
|
126
|
+
states_source = StateContract.model_validate_json((fixture / "state.json").read_text())
|
|
127
|
+
state_report = validate_state_contract(
|
|
128
|
+
states_source,
|
|
129
|
+
required_segment_ids=set(segments_source["segment_id"].astype(str)),
|
|
130
|
+
)
|
|
131
|
+
if not state_report.is_valid:
|
|
132
|
+
raise ValueError("; ".join(state_report.errors))
|
|
133
|
+
|
|
134
|
+
standards = pd.DataFrame(
|
|
135
|
+
[
|
|
136
|
+
{
|
|
137
|
+
"service_standard_id": standard.service_standard_id,
|
|
138
|
+
"max_height_m": standard.max_height_m,
|
|
139
|
+
"min_clearance_m": standard.min_clearance_m,
|
|
140
|
+
}
|
|
141
|
+
for standard in network.service_standards
|
|
142
|
+
]
|
|
143
|
+
)
|
|
144
|
+
corridor_rows = pd.DataFrame(
|
|
145
|
+
[
|
|
146
|
+
{
|
|
147
|
+
"segment_id": segment.segment_id,
|
|
148
|
+
"span_id": segment.span_id,
|
|
149
|
+
"sequence": segment.sequence,
|
|
150
|
+
"treatment_unit_id": segment.treatment_unit_id,
|
|
151
|
+
"service_standard_id": segment.service_standard_id,
|
|
152
|
+
"access_point_id": segment.access_point_id,
|
|
153
|
+
}
|
|
154
|
+
for segment in network.segments
|
|
155
|
+
]
|
|
156
|
+
)
|
|
157
|
+
segments = segments_source.merge(
|
|
158
|
+
corridor_rows,
|
|
159
|
+
on="segment_id",
|
|
160
|
+
how="left",
|
|
161
|
+
validate="one_to_one",
|
|
162
|
+
).merge(standards, on="service_standard_id", how="left", validate="many_to_one")
|
|
163
|
+
if segments[["span_id", "max_height_m", "min_clearance_m"]].isna().any().any():
|
|
164
|
+
raise ValueError("a segment is missing a span or a service standard")
|
|
165
|
+
segments["access_point_id"] = segments["access_point_id"].astype("string")
|
|
166
|
+
segments["sequence"] = segments["sequence"].astype("int64")
|
|
167
|
+
segments = segments[list(SEGMENT_COLUMNS)]
|
|
168
|
+
|
|
169
|
+
states = pd.DataFrame(
|
|
170
|
+
[
|
|
171
|
+
{
|
|
172
|
+
"segment_id": state.segment_id,
|
|
173
|
+
"state_year": state.state_year,
|
|
174
|
+
"functional_group": state.functional_group,
|
|
175
|
+
"state_class": state.state_class,
|
|
176
|
+
"height_m": state.height_m,
|
|
177
|
+
"cover_pct": state.cover_pct,
|
|
178
|
+
"density_per_ha": state.density_per_ha,
|
|
179
|
+
"age_years": state.age_years,
|
|
180
|
+
"uncertainty_confidence": state.uncertainty.confidence,
|
|
181
|
+
"source": state.source,
|
|
182
|
+
"source_version": state.source_version,
|
|
183
|
+
}
|
|
184
|
+
for state in states_source.states
|
|
185
|
+
]
|
|
186
|
+
)
|
|
187
|
+
states["density_per_ha"] = states["density_per_ha"].astype("float64")
|
|
188
|
+
states["age_years"] = states["age_years"].astype("float64")
|
|
189
|
+
states = states[list(STATE_COLUMNS)]
|
|
190
|
+
|
|
191
|
+
target = Path(out).expanduser().resolve()
|
|
192
|
+
if target.exists() and any(target.iterdir()) and not force:
|
|
193
|
+
raise FileExistsError(f"{target} is not empty; pass force=True to overwrite")
|
|
194
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
195
|
+
segments_path = target / "segments.parquet"
|
|
196
|
+
states_path = target / "states.parquet"
|
|
197
|
+
segments.to_parquet(segments_path, index=False)
|
|
198
|
+
states.to_parquet(states_path, index=False)
|
|
199
|
+
|
|
200
|
+
manifest = DatasetManifest(
|
|
201
|
+
dataset_id=f"{source_manifest.dataset_id}-compiled",
|
|
202
|
+
name="Synthetic corridor compiled dataset",
|
|
203
|
+
description="The P1.6 prototype compile of the synthetic corridor fixture.",
|
|
204
|
+
created_utc=datetime.now(UTC),
|
|
205
|
+
crs=source_manifest.crs,
|
|
206
|
+
units=source_manifest.units,
|
|
207
|
+
tables=[
|
|
208
|
+
TableSpec(
|
|
209
|
+
name="segments",
|
|
210
|
+
path="segments.parquet",
|
|
211
|
+
format="parquet",
|
|
212
|
+
primary_key=["segment_id"],
|
|
213
|
+
columns=_segment_columns(),
|
|
214
|
+
row_count=len(segments),
|
|
215
|
+
sha256=file_sha256(segments_path),
|
|
216
|
+
),
|
|
217
|
+
TableSpec(
|
|
218
|
+
name="states",
|
|
219
|
+
path="states.parquet",
|
|
220
|
+
format="parquet",
|
|
221
|
+
primary_key=["segment_id", "state_year", "functional_group"],
|
|
222
|
+
columns=_state_columns(),
|
|
223
|
+
row_count=len(states),
|
|
224
|
+
sha256=file_sha256(states_path),
|
|
225
|
+
),
|
|
226
|
+
],
|
|
227
|
+
provenance=Provenance(
|
|
228
|
+
source="synthetic-corridor",
|
|
229
|
+
source_version="1.0",
|
|
230
|
+
licence="CC0-1.0",
|
|
231
|
+
created_by="brushline.build.compiler",
|
|
232
|
+
configuration={"compiler": "p1.6-prototype"},
|
|
233
|
+
),
|
|
234
|
+
content_digest="0" * 64,
|
|
235
|
+
)
|
|
236
|
+
manifest.content_digest = manifest.compute_content_digest()
|
|
237
|
+
write_manifest(target, manifest)
|
|
238
|
+
return CompiledDataset(target, manifest, segments, states)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _segment_columns() -> list[ColumnSpec]:
|
|
242
|
+
return [
|
|
243
|
+
ColumnSpec(name="segment_id", dtype="string"),
|
|
244
|
+
ColumnSpec(name="corridor_id", dtype="string"),
|
|
245
|
+
ColumnSpec(name="length_m", dtype="float64", unit="m"),
|
|
246
|
+
ColumnSpec(name="geometry_wkt", dtype="string"),
|
|
247
|
+
ColumnSpec(name="span_id", dtype="string"),
|
|
248
|
+
ColumnSpec(name="sequence", dtype="int64"),
|
|
249
|
+
ColumnSpec(name="treatment_unit_id", dtype="string"),
|
|
250
|
+
ColumnSpec(name="service_standard_id", dtype="string"),
|
|
251
|
+
ColumnSpec(name="access_point_id", dtype="string", required=False),
|
|
252
|
+
ColumnSpec(name="max_height_m", dtype="float64", unit="m"),
|
|
253
|
+
ColumnSpec(name="min_clearance_m", dtype="float64", unit="m"),
|
|
254
|
+
]
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _state_columns() -> list[ColumnSpec]:
|
|
258
|
+
return [
|
|
259
|
+
ColumnSpec(name="segment_id", dtype="string"),
|
|
260
|
+
ColumnSpec(name="state_year", dtype="int64"),
|
|
261
|
+
ColumnSpec(name="functional_group", dtype="string"),
|
|
262
|
+
ColumnSpec(name="state_class", dtype="string"),
|
|
263
|
+
ColumnSpec(name="height_m", dtype="float64", unit="m"),
|
|
264
|
+
ColumnSpec(name="cover_pct", dtype="float64", unit="%"),
|
|
265
|
+
ColumnSpec(name="density_per_ha", dtype="float64", unit="stems/ha", required=False),
|
|
266
|
+
ColumnSpec(name="age_years", dtype="float64", unit="years", required=False),
|
|
267
|
+
ColumnSpec(name="uncertainty_confidence", dtype="float64"),
|
|
268
|
+
ColumnSpec(name="source", dtype="string"),
|
|
269
|
+
ColumnSpec(name="source_version", dtype="string"),
|
|
270
|
+
]
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
__all__ = [
|
|
274
|
+
"SEGMENT_COLUMNS",
|
|
275
|
+
"STATE_COLUMNS",
|
|
276
|
+
"CompiledDataset",
|
|
277
|
+
"compile_fixture",
|
|
278
|
+
]
|