feat(decision): make the accept lines tunable from the environment
Some checks failed
tests / model-free (pull_request) Has been cancelled

`LOCAL_ACCEPT` and `TEXT_MIN_SHARED` were module constants, so sweeping
them meant editing the source and restarting. Both lanes now read
`DORAYAKI_LOCAL_ACCEPT` / `DORAYAKI_TEXT_MIN_SHARED` with the constants
as defaults, which is what the calibration sweeps need and what a demo
host needs to loosen or tighten the gates without a code change.

Defaults are unchanged, so the shipped behaviour is identical.
This commit is contained in:
kiyo9w 2026-09-29 10:09:25 +09:00
parent e52684c37e
commit fbf31d74fe
2 changed files with 10 additions and 5 deletions

View file

@ -507,7 +507,7 @@ class RecognitionEngine:
know about. Without OCR the local lane stands alone.
"""
from identity import decide_identity
from identity_lanes import DecisionPolicy
from identity_lanes import LOCAL_ACCEPT, TEXT_MIN_SHARED, DecisionPolicy
if self.identity_scale != "local":
return decide_identity(
@ -525,9 +525,12 @@ class RecognitionEngine:
text_rank = sorted(
text_lane.scores_from_text(read).items(), key=lambda kv: kv[1], reverse=True
)
return DecisionPolicy(signatures=text_lane.signatures if text_lane else None).decide(
lane.rank(crop), text_rank, text_read=read
policy = DecisionPolicy(
local_accept=float(os.environ.get("DORAYAKI_LOCAL_ACCEPT", LOCAL_ACCEPT)),
text_min_shared=int(os.environ.get("DORAYAKI_TEXT_MIN_SHARED", TEXT_MIN_SHARED)),
signatures=text_lane.signatures if text_lane else None,
)
return policy.decide(lane.rank(crop), text_rank, text_read=read)
def score_crop_lanes(
self, crop: Any, method: Optional[str] = None, skip_ocr: bool = False

View file

@ -1280,7 +1280,7 @@ class LiveSession:
from identity import aggregate_votes, decide_identity
from identity_lanes import DecisionPolicy
from identity_lanes import LOCAL_ACCEPT, TEXT_MIN_SHARED, DecisionPolicy
mean_scores = aggregate_votes(track.observations)
scale = getattr(self.engine, "identity_scale", "inlier")
@ -1309,7 +1309,9 @@ class LiveSession:
# in an empty observation halves a good confidence below the
# accept line, which is the dilution the cosine branch guards too.
decision = DecisionPolicy(
signatures=text_lane.signatures if text_lane else None
local_accept=float(os.environ.get("DORAYAKI_LOCAL_ACCEPT", LOCAL_ACCEPT)),
text_min_shared=int(os.environ.get("DORAYAKI_TEXT_MIN_SHARED", TEXT_MIN_SHARED)),
signatures=text_lane.signatures if text_lane else None,
).decide(list(scores.items()), text_rank, text_read=text_read)
elif scale == "van":
# Van's contract on the stream: the aggregate's top SKU, always