Spaces:
Running
Running
| """Scoring one check-in, end to end. | |
| fetch row -> download both photos -> gates -> scores -> write back | |
| Idempotent: re-running on the same check-in recomputes from the stored photos | |
| and overwrites the result. That matters more than it sounds, because it means | |
| every threshold change can be replayed over the entire pilot dataset without | |
| going back into the field. The photos are the raw evidence; the verdict is | |
| derived and disposable. | |
| """ | |
| from __future__ import annotations | |
| import logging | |
| import random | |
| from . import store | |
| from .embed import cosine, embed | |
| from .scoring import ( | |
| Assessment, | |
| assess, | |
| PROXIMITY_REVIEW_M, | |
| check_plant_plausibility, | |
| check_proximity, | |
| gate_duplicate, | |
| gate_gps, | |
| gate_travel, | |
| score_growth, | |
| score_liveness, | |
| score_location, | |
| score_scene, | |
| ) | |
| from .signals import ( | |
| canopy_fraction, | |
| excess_green, | |
| haversine_m, | |
| liveness_score, | |
| orb_inliers, | |
| phash, | |
| plant_reference, | |
| trunk_width_mm, | |
| ) | |
| log = logging.getLogger(__name__) | |
| # Fraction of auto-approved check-ins pulled for human spot-checking. | |
| # Without this we would have no way to estimate the false-ACCEPT rate, which is | |
| # the number that actually matters — a wrongly rejected genuine visit is | |
| # annoying, a wrongly approved fraud is fatal, and nothing else in the system | |
| # measures the second one. | |
| AUDIT_RATE = 0.10 | |
| def score_checkin(checkin_id: str) -> Assessment: | |
| row = store.get_checkin(checkin_id) | |
| if row is None: | |
| raise LookupError(f"No check-in {checkin_id}") | |
| tree = store.get_tree(row["tree_id"]) | |
| if tree is None: | |
| raise LookupError(f"Check-in {checkin_id} references a missing tree") | |
| wide = store.download_image(row["wide_photo"]) | |
| close = store.download_image(row["close_photo"]) | |
| # --- fingerprints ------------------------------------------------------ | |
| wide_ph = phash(wide) | |
| wide_emb = embed(wide) | |
| close_emb = embed(close) | |
| captured_at = store.parse_ts(row["captured_at"]) | |
| prev_visits = store.previous_checkins(row["tree_id"], row["captured_at"]) | |
| # --- gates ------------------------------------------------------------- | |
| gates = {} | |
| gates["duplicate_image"] = gate_duplicate( | |
| wide_ph, store.other_phashes(checkin_id) | |
| ) | |
| gates["gps_radius"] = gate_gps( | |
| tree["lat"], | |
| tree["lng"], | |
| row["lat"], | |
| row["lng"], | |
| row.get("gps_accuracy_m"), | |
| ) | |
| prev_any = store.previous_by_submitter( | |
| row["submitted_by"], row["captured_at"], checkin_id | |
| ) | |
| gates["travel_speed"] = gate_travel( | |
| row["lat"], | |
| row["lng"], | |
| captured_at, | |
| ( | |
| prev_any["id"], | |
| prev_any["lat"], | |
| prev_any["lng"], | |
| store.parse_ts(prev_any["captured_at"]), | |
| ) | |
| if prev_any | |
| else None, | |
| ) | |
| # --- scores ------------------------------------------------------------ | |
| scores: dict[str, dict] = {} | |
| distance = haversine_m(tree["lat"], tree["lng"], row["lat"], row["lng"]) | |
| scores["location"] = score_location(distance) | |
| green = excess_green(wide) | |
| scores["liveness"] = score_liveness(green, liveness_score(green)) | |
| # Scene match needs something to match AGAINST. On the very first visit | |
| # there is nothing, so the signal is absent rather than zero — assess() | |
| # renormalises and lowers confidence via the coverage term instead of | |
| # treating a registration as suspicious. | |
| if prev_visits: | |
| best_wide = best_close = None | |
| for p in prev_visits: | |
| pw = store.parse_embedding(p.get("wide_embedding")) | |
| pc = store.parse_embedding(p.get("close_embedding")) | |
| if pw is not None: | |
| c = cosine(wide_emb, pw) | |
| best_wide = c if best_wide is None else max(best_wide, c) | |
| if pc is not None: | |
| c = cosine(close_emb, pc) | |
| best_close = c if best_close is None else max(best_close, c) | |
| inliers = None | |
| try: | |
| prev_wide = store.download_image(prev_visits[0]["wide_photo"]) | |
| inliers = orb_inliers(wide, prev_wide) | |
| except Exception: # noqa: BLE001 - corroboration only, never fatal | |
| log.warning("ORB comparison failed for %s", checkin_id, exc_info=True) | |
| if best_wide is not None or best_close is not None: | |
| scores["scene_match"] = score_scene(best_wide, best_close, inliers) | |
| canopy_now = canopy_fraction(wide) | |
| canopy_prev = trunk_prev = None | |
| if prev_visits: | |
| prev_growth = ((prev_visits[0].get("signals") or {}).get("scores") or {}).get( | |
| "growth", {} | |
| ) | |
| canopy_prev = prev_growth.get("canopy_frac") | |
| trunk_prev = prev_growth.get("trunk_mm") | |
| # Millimetres are derived HERE, from the stored photo and the stored taps — | |
| # never from anything the client calculated. See signals.trunk_width_mm. | |
| measurement = trunk_width_mm(close, row.get("trunk_taps")) | |
| trunk_now = measurement.get("trunk_mm") if measurement and measurement.get("available") else None | |
| scores["growth"] = score_growth(canopy_now, canopy_prev, trunk_now, trunk_prev) | |
| if measurement: | |
| scores["growth"]["measurement"] = measurement | |
| # --- review flags ------------------------------------------------------ | |
| # Not gates: these must not auto-approve, but they are not accusations. See | |
| # scoring.check_proximity for why proximity cannot be a gate. | |
| review_flags = {} | |
| # Runs on EVERY visit, not just the first. Registering a chair is the | |
| # obvious case, but swapping one in at visit three is the case a | |
| # first-visit-only check would wave through. | |
| review_flags["plant_plausibility"] = check_plant_plausibility( | |
| wide_emb, close_emb, plant_reference() | |
| ) | |
| # Only on the tree's FIRST check-in, i.e. its registration. On every later | |
| # visit the tree already exists and standing near a neighbour is not news — | |
| # re-flagging it every round would bury the reviewer in the same alert. | |
| if not prev_visits: | |
| review_flags["tree_proximity"] = check_proximity( | |
| tree["lat"], | |
| tree["lng"], | |
| store.trees_near_registered_before( | |
| tree["id"], | |
| tree["lat"], | |
| tree["lng"], | |
| tree["created_at"], | |
| PROXIMITY_REVIEW_M, | |
| ), | |
| ) | |
| # --- combine and persist ---------------------------------------------- | |
| result = assess(gates, scores, review_flags=review_flags) | |
| # Carry provenance forward. write_result replaces `signals` WHOLESALE, so a | |
| # self-test tag written at insert time is destroyed by the very act of | |
| # scoring. That is not cosmetic: it made six injected attack rows | |
| # indistinguishable from real pilot data, and the only thing that still | |
| # identified them was the storage path. Provenance is a fact about where a | |
| # row came from, not part of the score, and must survive being scored. | |
| provenance = (row.get("signals") or {}).get("self_test") | |
| if provenance: | |
| result.signals["self_test"] = provenance | |
| audit = result.verdict == "verified" and random.random() < AUDIT_RATE | |
| store.write_result( | |
| checkin_id, | |
| phash=wide_ph, | |
| wide_embedding=wide_emb, | |
| close_embedding=close_emb, | |
| confidence=result.confidence, | |
| verdict=result.verdict, | |
| signals=result.signals, | |
| audit_sample=audit, | |
| ) | |
| store.update_tree_status(row["tree_id"], result.confidence, result.verdict) | |
| log.info( | |
| "scored %s -> %s (%d) gates=%s", | |
| checkin_id, | |
| result.verdict, | |
| result.confidence, | |
| {k: v.passed for k, v in gates.items()}, | |
| ) | |
| return result | |