| """Validate generated CityFlow routes and print summary statistics.""" |
|
|
| from __future__ import annotations |
|
|
| import argparse |
| import json |
| import os |
| import sys |
| from pathlib import Path |
| from typing import Any |
|
|
| from .utils import build_road_index, build_roadlink_index, summarize_route_validation |
|
|
|
|
| def _load_json(path: Path) -> Any: |
| return json.loads(path.read_text(encoding="utf-8")) |
|
|
|
|
| def _resolve_from_config(config_path: Path) -> tuple[Path, Path]: |
| config = _load_json(config_path) |
| base_dir = Path(config["dir"]) |
| roadnet_path = (base_dir / config["roadnetFile"]).resolve() |
| flow_path = (base_dir / config["flowFile"]).resolve() |
| return roadnet_path, flow_path |
|
|
|
|
| def _print_summary(summary: dict[str, Any]) -> None: |
| print(f"total routes: {summary['total_routes']}") |
| print(f"valid routes: {summary['valid_routes']}") |
| print(f"invalid routes: {summary['invalid_routes']}") |
| if summary["top_failure_reasons"]: |
| formatted = ", ".join( |
| f"{reason}={count}" for reason, count in summary["top_failure_reasons"] |
| ) |
| print(f"top failure reasons: {formatted}") |
| else: |
| print("top failure reasons: none") |
|
|
|
|
| def main() -> int: |
| parser = argparse.ArgumentParser(description="Validate CityFlow flow routes.") |
| parser.add_argument( |
| "--config", |
| type=Path, |
| default=None, |
| help="Path to scenario config.json.", |
| ) |
| parser.add_argument( |
| "--roadnet", |
| type=Path, |
| default=None, |
| help="Path to roadnet.json (required if --config not provided).", |
| ) |
| parser.add_argument( |
| "--flow", |
| type=Path, |
| default=None, |
| help="Path to flow.json (required if --config not provided).", |
| ) |
| args = parser.parse_args() |
|
|
| if args.config is not None: |
| roadnet_path, flow_path = _resolve_from_config(args.config.resolve()) |
| else: |
| if args.roadnet is None or args.flow is None: |
| raise ValueError("Provide --config OR both --roadnet and --flow.") |
| roadnet_path = args.roadnet.resolve() |
| flow_path = args.flow.resolve() |
|
|
| if not roadnet_path.exists(): |
| raise FileNotFoundError(os.fspath(roadnet_path)) |
| if not flow_path.exists(): |
| raise FileNotFoundError(os.fspath(flow_path)) |
|
|
| roadnet = _load_json(roadnet_path) |
| flows = _load_json(flow_path) |
| roads_by_id = build_road_index(roadnet) |
| roadlinks_by_intersection = build_roadlink_index(roadnet) |
| summary = summarize_route_validation( |
| flow_entries=flows, |
| roads_by_id=roads_by_id, |
| roadlinks_by_intersection=roadlinks_by_intersection, |
| ) |
| _print_summary(summary) |
| return 1 if summary["invalid_routes"] > 0 else 0 |
|
|
|
|
| if __name__ == "__main__": |
| sys.exit(main()) |
|
|