Skip to content

Commit b4764b2

Browse files
committed
feat: add transfer-results command for generating transfer results summary
1 parent ba7881b commit b4764b2

9 files changed

Lines changed: 414 additions & 89 deletions

alembic/versions/43bc34504ee6_merge_migrations_after_staging_merge.py

Lines changed: 0 additions & 25 deletions
This file was deleted.

alembic/versions/50d1c2a3b4c5_add_unique_index_ngwmn_wellconstruction.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Add unique index for NGWMN well construction
22
33
Revision ID: 50d1c2a3b4c5
4-
Revises: 43bc34504ee6
4+
Revises: 3cb924ca51fd
55
Create Date: 2026-01-31 00:27:12.204176
66
77
"""
@@ -12,7 +12,7 @@
1212

1313
# revision identifiers, used by Alembic.
1414
revision: str = "50d1c2a3b4c5"
15-
down_revision: Union[str, Sequence[str], None] = "43bc34504ee6"
15+
down_revision: Union[str, Sequence[str], None] = "3cb924ca51fd"
1616
branch_labels: Union[str, Sequence[str], None] = None
1717
depends_on: Union[str, Sequence[str], None] = None
1818

cli/cli.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,33 @@ def associate_assets_command(
109109
associate_assets(root_directory)
110110

111111

112+
@cli.command("transfer-results")
113+
def transfer_results(
114+
summary_path: Path = typer.Option(
115+
Path("transfers") / "metrics" / "transfer_results_summary.md",
116+
"--summary-path",
117+
help="Output path for markdown summary table.",
118+
),
119+
sample_limit: int = typer.Option(
120+
25,
121+
"--sample-limit",
122+
min=1,
123+
help="Max missing/extra key samples stored per transfer.",
124+
),
125+
theme: ThemeMode = typer.Option(
126+
ThemeMode.auto, "--theme", help="Color theme: auto, light, dark."
127+
),
128+
):
129+
from transfers.transfer_results_builder import TransferResultsBuilder
130+
131+
builder = TransferResultsBuilder(sample_limit=sample_limit)
132+
results = builder.build()
133+
summary_path.parent.mkdir(parents=True, exist_ok=True)
134+
TransferResultsBuilder.write_summary(summary_path, results)
135+
typer.echo(f"Wrote comparison summary: {summary_path}")
136+
typer.echo(f"Transfer comparisons: {len(results.results)}")
137+
138+
112139
@cli.command("well-inventory-csv")
113140
def well_inventory_csv(
114141
file_path: str = typer.Argument(

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ dependencies = [
108108
package = true
109109

110110
[tool.setuptools]
111-
packages = ["alembic", "cli", "core", "db", "schemas", "services"]
111+
packages = ["alembic", "cli", "core", "db", "schemas", "services", "transfers"]
112112

113113
[project.scripts]
114114
oco = "cli.cli:cli"

tests/test_cli_commands.py

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@
1818
import textwrap
1919
import uuid
2020
from pathlib import Path
21+
from types import SimpleNamespace
22+
23+
from sqlalchemy import select
24+
from typer.testing import CliRunner
2125

2226
from cli.cli import cli
2327
from cli.service_adapter import WellInventoryResult
2428
from db import FieldActivity, FieldEvent, Observation, Sample
2529
from db.engine import session_ctx
26-
from sqlalchemy import select
27-
from typer.testing import CliRunner
2830

2931

3032
def test_initialize_lexicon_invokes_initializer(monkeypatch):
@@ -95,6 +97,50 @@ def fake_well_inventory(file_path):
9597
assert "[WELL INVENTORY IMPORT] SUCCESS" in result.output
9698

9799

100+
def test_transfer_results_command_writes_summary(monkeypatch, tmp_path):
101+
captured: dict[str, object] = {}
102+
103+
class FakeBuilder:
104+
def __init__(self, sample_limit: int = 25):
105+
captured["sample_limit"] = sample_limit
106+
107+
def build(self):
108+
captured["built"] = True
109+
return SimpleNamespace(
110+
results={"WellData": object(), "WaterLevels": object()}
111+
)
112+
113+
@staticmethod
114+
def write_summary(path, comparison):
115+
captured["summary_path"] = Path(path)
116+
captured["result_count"] = len(comparison.results)
117+
118+
monkeypatch.setattr(
119+
"transfers.transfer_results_builder.TransferResultsBuilder", FakeBuilder
120+
)
121+
122+
summary_path = tmp_path / "metrics" / "summary.md"
123+
runner = CliRunner()
124+
result = runner.invoke(
125+
cli,
126+
[
127+
"transfer-results",
128+
"--summary-path",
129+
str(summary_path),
130+
"--sample-limit",
131+
"11",
132+
],
133+
)
134+
135+
assert result.exit_code == 0, result.output
136+
assert captured["sample_limit"] == 11
137+
assert captured["built"] is True
138+
assert captured["summary_path"] == summary_path
139+
assert captured["result_count"] == 2
140+
assert f"Wrote comparison summary: {summary_path}" in result.output
141+
assert "Transfer comparisons: 2" in result.output
142+
143+
98144
def test_well_inventory_csv_command_reports_validation_errors(monkeypatch, tmp_path):
99145
inventory_file = tmp_path / "inventory.csv"
100146
inventory_file.write_text("header\nvalue\n")
@@ -198,10 +244,12 @@ def test_water_levels_cli_persists_observations(tmp_path, water_well_thing):
198244
"""
199245

200246
def _write_csv(path: Path, *, well_name: str, notes: str):
201-
csv_text = textwrap.dedent(f"""\
247+
csv_text = textwrap.dedent(
248+
f"""\
202249
field_staff,well_name_point_id,field_event_date_time,measurement_date_time,sampler,sample_method,mp_height,level_status,depth_to_water_ft,data_quality,water_level_notes
203250
CLI Tester,{well_name},2025-02-15T08:00:00-07:00,2025-02-15T10:30:00-07:00,Groundwater Team,electric tape,1.5,stable,42.5,approved,{notes}
204-
""")
251+
"""
252+
)
205253
path.write_text(csv_text)
206254

207255
unique_notes = f"pytest-{uuid.uuid4()}"

transfers/transfer.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from dataclasses import dataclass
2121

2222
from dotenv import load_dotenv
23+
2324
from transfers.thing_transfer import (
2425
transfer_rock_sample_locations,
2526
transfer_springs,
@@ -698,9 +699,10 @@ def main():
698699
profile_artifacts = transfer_all(metrics)
699700

700701
metrics.close()
701-
metrics.save_to_storage_bucket()
702-
save_log_to_bucket()
703-
upload_profile_artifacts(profile_artifacts)
702+
if get_bool_env("SAVE_TO_BUCKET", False):
703+
metrics.save_to_storage_bucket()
704+
save_log_to_bucket()
705+
upload_profile_artifacts(profile_artifacts)
704706
message("END--------------------------------------")
705707

706708

transfers/transfer_results.py

Lines changed: 0 additions & 50 deletions
This file was deleted.

transfers/transfer_results_builder.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import os
34
from pathlib import Path
45
from typing import Any
56

@@ -21,7 +22,6 @@
2122
replace_nans,
2223
get_transferable_wells,
2324
)
24-
import os
2525

2626

2727
def _normalize_key(value: Any) -> str | None:
@@ -79,9 +79,11 @@ def _build_one(self, spec: TransferComparisonSpec) -> TransferResult:
7979
if spec.source_filter:
8080
source_df = spec.source_filter(source_df)
8181
comparison_df = source_df
82+
if spec.agreed_filter:
83+
comparison_df = spec.agreed_filter(comparison_df)
8284
enabled = self._is_enabled(spec)
8385
if not enabled:
84-
comparison_df = source_df.iloc[0:0]
86+
comparison_df = comparison_df.iloc[0:0]
8587
elif spec.transfer_name == "WellData":
8688
comparison_df = self._agreed_welldata_df()
8789

@@ -179,9 +181,8 @@ def write_summary(path: Path, comparison: TransferComparisonResults) -> None:
179181
]
180182
for name in sorted(comparison.results.keys()):
181183
r = comparison.results[name]
182-
missing_agreed = r.agreed_transfer_row_count - r.destination_row_count
183184
lines.append(
184185
f"| {name} | {r.source_csv} | {r.source_row_count} | {r.agreed_transfer_row_count} | "
185-
f"{r.destination_model} | {r.destination_row_count} | {missing_agreed} |"
186+
f"{r.destination_model} | {r.destination_row_count} | {r.missing_in_destination_count} |"
186187
)
187188
path.write_text("\n".join(lines) + "\n")

0 commit comments

Comments
 (0)