| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 |
- """Reproduce the approved model assessment without changing production files.
- Uses the existing SSH alias for read-only baseline inspection and disposable,
- resource-limited OSRM containers. Synthetic XML and candidate Lua are generated
- only under an owned /tmp directory. No business database or credentials are read.
- """
- import hashlib
- import json
- from pathlib import Path
- import subprocess
- import sys
- import urllib.request
- VALHALLA_SHA = "34639a33510eb0764da77650e91f6efbb70e8daa"
- MAP_REPO = Path("D:/QtwCode/foodie/Foodie_HCM_Map")
- OUTPUT = Path(__file__).with_name("model-probe-results.json")
- def remote_probe():
- """This function executes on the SSH host, with the standard library only."""
- import datetime
- import math
- import tempfile
- import time
- import urllib.error
- import urllib.parse
- import xml.etree.ElementTree as ET
- image = "ghcr.io/project-osrm/osrm-backend:v5.27.1"
- release = Path("/opt/foodie-map-tw-20260909")
- production = ["foodie-map-tw-20260909-api-1", "foodie-map-tw-20260909-osrm-1",
- "foodie-map-tw-20260909-osrm-motorcycle-1"]
- def command(args):
- completed = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=90)
- if completed.returncode:
- raise RuntimeError("Command failed: " + " ".join(args) + "\n" + completed.stdout.decode(errors="replace")[-4000:])
- return completed.stdout
- def fingerprints():
- files = ["map_api/settings.py", "map_api/routing.py", "docker/osrm/profiles/motorcycle.lua"]
- hashes = {p: hashlib.sha256((release / p).read_bytes()).hexdigest() for p in files}
- for p in ("settings.py", "routing.py"):
- data = command(["docker", "exec", production[0], "cat", "/app/" + p])
- hashes["container:/app/" + p] = hashlib.sha256(data).hexdigest()
- hashes["container:car.lua"] = hashlib.sha256(command(
- ["docker", "exec", production[2], "cat", "/opt/car.lua"])).hexdigest()
- hashes["built_profile"] = command(["docker", "exec", production[2], "cat",
- "/data/profile.sha256"]).decode().strip()
- hashes["pbf"] = command(["docker", "exec", production[2], "cat",
- "/data/build.sha256"]).decode().strip()
- return hashes
- def read_url(url):
- with urllib.request.urlopen(url, timeout=8) as response:
- return json.load(response)
- result = {"timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(),
- "image": image, "release": str(release), "before": fingerprints(),
- "cases": [], "production_probes": {}, "errors": []}
- baseline = (release / "docker/osrm/profiles/motorcycle.lua").read_text()
- car_lua = command(["docker", "exec", production[2], "cat", "/opt/car.lua"])
- result["combined_profile_matches_graph"] = hashlib.sha256(
- car_lua + baseline.encode()).hexdigest() == result["before"]["built_profile"]
- for name in ("config", "health/ready"):
- result["production_probes"][name] = read_url("http://127.0.0.1:8081/api/" + name)
- points = "pickup_lat=25.0330&pickup_lng=121.5654&drop_lat=25.0375&drop_lng=121.5637"
- for profile in ("car", "motorcycle"):
- for endpoint, params in (("route", points),
- ("route/multi", "coords=25.0330,121.5654;25.0375,121.5637")):
- route = read_url("http://127.0.0.1:8081/api/" + endpoint + "?" + params + "&profile=" + profile)
- result["production_probes"][endpoint + ":" + profile] = {
- k: route.get(k) for k in ("profile", "distance_meters", "duration_seconds", "traffic_aware")}
- # Probe chosen by this assessment: keep access restrictions; penalize gravel
- # through rate (not speed), and add a signal weight (not duration). Neither
- # number is a copied Valhalla constant or an approved production calibration.
- candidate = baseline.replace(" car.process_way(profile, proxy, result, relations)", """
- car.process_way(profile, proxy, result, relations)
- if way:get_value_by_key('surface') == 'gravel' then
- if result.forward_rate and result.forward_rate > 0 then
- result.forward_rate = result.forward_rate * 0.2
- end
- if result.backward_rate and result.backward_rate > 0 then
- result.backward_rate = result.backward_rate * 0.2
- end
- end""").replace("return {setup=setup, process_way=process_way, process_node=car.process_node, process_turn=car.process_turn}", """
- local function process_turn(profile, turn)
- car.process_turn(profile, turn)
- if turn.has_traffic_light then turn.weight = turn.weight + 8 end
- if math.abs(turn.angle) > 60 then turn.weight = turn.weight + 3 end
- end
- return {setup=setup, process_way=process_way, process_node=car.process_node, process_turn=process_turn}
- """)
- if candidate == baseline or "turn.weight = turn.weight + 8" not in candidate:
- raise RuntimeError("Unexpected baseline shape; no automatic production adaptation.")
- result["candidate_sha256"] = hashlib.sha256(candidate.encode()).hexdigest()
- root = ET.Element("osm", version="0.6", generator="foodie-isolated-model-assessment")
- counter = 0
- samples = {}
- def node(lat, lon, tags=None):
- nonlocal counter
- counter += 1
- element = ET.SubElement(root, "node", id=str(counter), lat=str(lat), lon=str(lon), version="1")
- for key, value in (tags or {}).items():
- ET.SubElement(element, "tag", k=key, v=value)
- return counter
- def way(ids, tags):
- nonlocal counter
- counter += 1
- element = ET.SubElement(root, "way", id=str(counter), version="1")
- for identifier in ids:
- ET.SubElement(element, "nd", ref=str(identifier))
- for key, value in tags.items():
- ET.SubElement(element, "tag", k=key, v=value)
- return counter
- # Independent diamonds ensure a prohibited short road has a real legal
- # detour. Inputs use exact shared junctions, avoiding arbitrary snap choices.
- def diamond(name, tags, width=0.004, signal=False, detour=True):
- lat, lon = 25.1 + len(samples) * 0.01, 121.5
- a, b = node(lat, lon), node(lat + 0.002, lon)
- mid = node(lat + 0.001, lon, {"highway": "traffic_signals"} if signal else {})
- way([a, mid, b], {"highway": "residential", "name": name, **tags})
- if detour:
- c, d = node(lat, lon + width), node(lat + 0.002, lon + width)
- way([a, c, d, b], {"highway": "residential", "name": name + "_detour"})
- if signal:
- branch = node(lat + 0.001, lon - 0.0003)
- way([mid, branch], {"highway": "residential", "name": "signal_branch"})
- samples[name] = [(lon, lat), (lon, lat + 0.002)]
- diamond("motorcycle_no", {"motorcycle": "no"})
- diamond("motorcar_no", {"motorcar": "no", "motorcycle": "yes"})
- for road in ("motorway", "motorway_link", "trunk"):
- diamond(road, {"highway": road})
- diamond("trunk_permitted", {"highway": "trunk", "motorcycle": "yes"})
- diamond("motorroad", {"motorroad": "yes"})
- diamond("motorroad_permitted", {"motorroad": "yes", "motorcycle": "yes"})
- diamond("oneway", {"oneway": "yes"})
- diamond("oneway_override", {"oneway": "yes", "oneway:motorcycle": "no"})
- diamond("speed_override", {"maxspeed:forward": "90", "maxspeed:motorcycle": "20"}, detour=False)
- diamond("gravel_choice", {"highway": "primary", "surface": "gravel"}, width=0.001)
- diamond("gravel_forced", {"highway": "primary", "surface": "gravel"}, detour=False)
- diamond("signals", {}, signal=True, detour=False)
- diamond("conditional_access", {"motorcycle:conditional": "no @ (00:00-24:00)"})
- # Turn relation distinguishes real path exclusion from simply assigning a
- # higher turn penalty. No two-stage-turn claim is made by this fixture.
- def turn_case(name, restriction_key):
- nonlocal counter
- lat, lon = 25.1 + len(samples) * 0.01, 121.5
- a, via, b = node(lat, lon), node(lat + 0.001, lon), node(lat + 0.001, lon + 0.001)
- c, d = node(lat + 0.003, lon), node(lat + 0.003, lon + 0.001)
- from_way = way([a, via], {"highway": "residential"})
- to_way = way([via, b], {"highway": "residential"})
- way([via, c, d, b], {"highway": "residential"})
- if restriction_key:
- counter += 1
- relation = ET.SubElement(root, "relation", id=str(counter), version="1")
- for kind, ref, role in (("way", from_way, "from"), ("node", via, "via"), ("way", to_way, "to")):
- ET.SubElement(relation, "member", type=kind, ref=str(ref), role=role)
- ET.SubElement(relation, "tag", k="type", v="restriction")
- ET.SubElement(relation, "tag", k=restriction_key, v="no_right_turn")
- samples[name] = [(lon, lat + 0.0001), (lon + 0.0009, lat + 0.001)]
- turn_case("motorcycle_turn", "restriction:motorcycle")
- turn_case("generic_turn", "restriction")
- turn_case("unrestricted_turn", None)
- # libosmium expects ordered entity types even though generated scenarios
- # were appended independently. Keep nodes before ways before relations.
- entities = sorted(list(root), key=lambda e: ({"node": 0, "way": 1, "relation": 2}[e.tag], int(e.attrib["id"])))
- root[:] = entities
- fixture = ET.tostring(root, encoding="utf-8", xml_declaration=True)
- samples["offroad"] = [samples["motorcycle_no"][0], (121.7, 25.7)]
- samples["disconnected"] = [samples["motorcycle_no"][0], samples["trunk_permitted"][1]]
- result["fixture_sha256"] = hashlib.sha256(fixture).hexdigest()
- containers = []
- urls = {}
- def progress(message):
- print(message, file=sys.stderr, flush=True)
- def route(profile, case, reverse=False):
- coords = samples[case][::-1] if reverse else samples[case]
- coordinates = ";".join(f"{lon},{lat}" for lon, lat in coords)
- url = urls[profile] + "/route/v1/driving/" + coordinates + "?overview=full&geometries=geojson&radiuses=10;10"
- try:
- data = read_url(url)
- except urllib.error.HTTPError as error:
- data = json.load(error)
- data["test_profile"] = profile
- return data
- def check(name, condition, observations, kind="assertion"):
- row = {"name": name, "kind": kind, "passed": bool(condition), "observations": observations}
- result["cases"].append(row)
- progress(f"{name}: {'PASS' if condition else 'FAIL'}")
- def metric(data):
- if data.get("code") != "Ok":
- return {"code": data.get("code")}
- return {k: data["routes"][0][k] for k in ("distance", "duration", "weight")}
- def distance(data):
- return data["routes"][0]["distance"] if data.get("code") == "Ok" else math.inf
- try:
- with tempfile.TemporaryDirectory(prefix="foodie-model-eval-", dir="/tmp") as temp:
- workspace = Path(temp)
- result["temporary_workspace"] = str(workspace)
- for profile, lua in (("car", None), ("baseline", baseline), ("candidate", candidate)):
- folder = workspace / profile
- folder.mkdir()
- (folder / "fixture.osm").write_bytes(fixture)
- if lua:
- (folder / "model.lua").write_text(lua)
- profile_path = "/data/model.lua" if lua else "/opt/car.lua"
- base_args = ["docker", "run", "--rm", "--cpus=0.5", "--memory=256m",
- "-v", f"{folder}:/data", "--entrypoint", "sh", image]
- progress("Building isolated " + profile)
- build = "osrm-extract --threads 1 -p " + profile_path + " /data/fixture.osm && osrm-partition --threads 1 /data/fixture.osrm && osrm-customize --threads 1 /data/fixture.osrm"
- command(base_args[:3] + ["--network", "none"] + base_args[3:] + ["-c", build])
- identifier = command(["docker", "run", "-d", "--rm", "--cpus=0.5", "--memory=256m",
- "-p", "127.0.0.1::5000", "-v", f"{folder}:/data:ro", image,
- "osrm-routed", "--algorithm", "mld", "--threads", "1", "/data/fixture.osrm"]).decode().strip()
- containers.append(identifier)
- info = json.loads(command(["docker", "inspect", identifier]))[0]
- port = info["NetworkSettings"]["Ports"]["5000/tcp"][0]["HostPort"]
- urls[profile] = "http://127.0.0.1:" + port
- for _ in range(30):
- try:
- read_url(urls[profile] + "/nearest/v1/driving/121.5,25.1")
- break
- except (urllib.error.URLError, TimeoutError):
- time.sleep(0.2)
- else:
- raise RuntimeError("Isolated engine did not start")
- for case, blocked in (("motorcycle_no", True), ("motorcar_no", False),
- ("motorway", True), ("motorway_link", True), ("trunk", True),
- ("trunk_permitted", False), ("motorroad", True),
- ("motorroad_permitted", False)):
- for profile in ("baseline", "candidate"):
- data = route(profile, case)
- ok = data.get("code") == "Ok" and (distance(data) > 700 if blocked else distance(data) < 300)
- check(profile + ":" + case, ok, metric(data))
- car, moto = route("car", "motorcycle_no"), route("baseline", "motorcycle_no")
- check("car_vs_motorcycle_access", distance(car) < 300 < distance(moto),
- {"car": metric(car), "motorcycle": metric(moto)})
- for profile in ("baseline", "candidate"):
- forward, backward = route(profile, "oneway"), route(profile, "oneway", True)
- check(profile + ":oneway", distance(forward) < 300 and distance(backward) > 700,
- {"forward": metric(forward), "reverse": metric(backward)})
- override = route(profile, "oneway_override", True)
- check(profile + ":oneway_motorcycle_override", distance(override) < 300, metric(override))
- for turn in ("motorcycle_turn", "generic_turn"):
- data = route(profile, turn)
- check(profile + ":" + turn, distance(data) > 500 and data.get("code") == "Ok", metric(data))
- speed_car, speed_moto = route("car", "speed_override"), route("baseline", "speed_override")
- check("motorcycle_speed_limit", metric(speed_moto).get("duration", 0) > metric(speed_car).get("duration", 0),
- {"car": metric(speed_car), "motorcycle": metric(speed_moto)})
- for case in ("gravel_forced", "signals", "unrestricted_turn"):
- base, proposed = route("baseline", case), route("candidate", case)
- base_metric, proposed_metric = metric(base), metric(proposed)
- ok = (base.get("code") == proposed.get("code") == "Ok" and
- abs(base_metric["duration"] - proposed_metric["duration"]) < 0.2 and
- proposed_metric["weight"] > base_metric["weight"])
- check(case + ":weight_not_eta", ok, {"baseline": base_metric, "candidate": proposed_metric})
- base, proposed = route("baseline", "gravel_choice"), route("candidate", "gravel_choice")
- check("gravel_preference_changes_path", distance(base) < 300 and distance(proposed) > 350,
- {"baseline": metric(base), "candidate": metric(proposed)})
- for profile in ("baseline", "candidate"):
- for case, expected in (("offroad", "NoSegment"), ("disconnected", "NoRoute")):
- data = route(profile, case)
- check(profile + ":" + case, data.get("code") == expected, metric(data))
- conditional = route("baseline", "conditional_access")
- # A diagnostic, not a hidden assertion that unsupported time rules work.
- check("conditional_access_not_enforced", distance(conditional) < 300, metric(conditional), "limitation_probe")
- repeated = [*samples["motorcycle_no"], samples["motorcycle_no"][0]]
- coords = ";".join(f"{lon},{lat}" for lon, lat in repeated)
- multi = read_url(urls["baseline"] + "/route/v1/driving/" + coords + "?overview=false")
- check("ordered_three_stop_route", multi.get("code") == "Ok" and len(multi["routes"][0]["legs"]) == 2,
- {"legs": len(multi.get("routes", [{}])[0].get("legs", [])), **metric(multi)})
- except Exception as error:
- result["errors"].append(str(error))
- finally:
- for identifier in containers:
- # Only identifiers created by this run are stopped; no Compose
- # lifecycle command can accidentally target a production service.
- subprocess.run(["docker", "stop", "--time", "1", identifier], capture_output=True, timeout=15)
- result["after"] = fingerprints()
- result["production_files_unchanged"] = result["before"] == result["after"]
- result["production_containers_running"] = all(
- command(["docker", "inspect", "--format", "{{.State.Running}}", name]).decode().strip() == "true"
- for name in production)
- result["temporary_containers_removed"] = all(
- subprocess.run(["docker", "inspect", identifier], capture_output=True).returncode != 0
- for identifier in containers)
- result["temporary_workspace_removed"] = not Path(result.get("temporary_workspace", "/nonexistent")).exists()
- print(json.dumps(result, ensure_ascii=False))
- def main():
- import inspect
- sources = {}
- for path in ("src/sif/motorcyclecost.cc", "src/sif/motorscootercost.cc", "LICENSE.md", "COPYING"):
- url = f"https://raw.githubusercontent.com/valhalla/valhalla/{VALHALLA_SHA}/{path}"
- with urllib.request.urlopen(url, timeout=20) as response:
- data = response.read()
- sources[path] = {"url": url, "sha256": hashlib.sha256(data).hexdigest(), "bytes": len(data)}
- refs = {}
- for ref in ("HEAD", "origin/taiwan-map-profiles"):
- refs[ref] = subprocess.check_output(["git", "rev-parse", ref], cwd=MAP_REPO).decode().strip()
- lua = subprocess.check_output(["git", "show", "origin/taiwan-map-profiles:docker/osrm/profiles/motorcycle.lua"], cwd=MAP_REPO)
- branch_files = {}
- for ref in refs:
- branch_files[ref] = {}
- for path in ("map_api/settings.py", "map_api/routing.py", "docker/osrm/profiles/motorcycle.lua"):
- snapshot = subprocess.run(["git", "show", ref + ":" + path], cwd=MAP_REPO,
- stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
- branch_files[ref][path] = hashlib.sha256(snapshot.stdout).hexdigest() if snapshot.returncode == 0 else None
- references = {"valhalla_commit": VALHALLA_SHA, "sources": sources, "map_refs": refs, "branch_files": branch_files,
- "branch_lua_sha256": hashlib.sha256(lua).hexdigest()}
- source = "import hashlib,json,subprocess,sys,urllib.request\nfrom pathlib import Path\n" + inspect.getsource(remote_probe) + "\nremote_probe()\n"
- process = subprocess.run(["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=8", "tokyo-map", "python3 -"],
- input=source, text=True, encoding="utf-8", stdout=subprocess.PIPE, timeout=360)
- if process.returncode:
- raise RuntimeError("Remote assessment failed before producing a complete result")
- result = json.loads(process.stdout)
- result["references"] = references
- result["branch_lua_matches_deployment"] = references["branch_lua_sha256"] == result["before"]["docker/osrm/profiles/motorcycle.lua"]
- # Generated machine evidence, not a manually edited source/configuration.
- OUTPUT.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
- failures = [row["name"] for row in result["cases"] if not row["passed"]]
- print(json.dumps({"cases": len(result["cases"]), "failures": failures, "errors": result["errors"],
- "files_unchanged": result["production_files_unchanged"], "evidence": str(OUTPUT)}, ensure_ascii=False))
- if failures or result["errors"] or not all(result[key] for key in
- ("combined_profile_matches_graph", "branch_lua_matches_deployment", "production_files_unchanged",
- "production_containers_running", "temporary_containers_removed", "temporary_workspace_removed")):
- sys.exit(1)
- if __name__ == "__main__":
- main()
|