Spaces:
Running on Zero
Running on Zero
| """The capability registry, and the acquisition protocol each capability declares. | |
| ADR 0006 and ADR 0021. Every model-backed feature is data, not code — which is | |
| what collapses the prototype's 26 capability screens into two layouts. | |
| **This file was rewritten against the founder's zero-training directive** | |
| (`Animap_AI_Inference_Zero_Training_Implementation_Directive.md`). The registry | |
| used to answer *"can one model do this from one unconstrained RGB photograph?"* | |
| and answered *no* nineteen times. The directive rejects that question. The | |
| question it asks instead is *"what is the minimum practical capture protocol, | |
| model combination, signal-processing method or retrieval method that makes this | |
| observable enough to be useful?"* — and a capability whose claim is corrected | |
| survives instead of disappearing. | |
| Nothing measured was thrown away. Every figure the earlier pass recorded is | |
| still in `app/dispositions.py`, verbatim, now attached to the classification it | |
| argues about rather than to a verdict it was made to justify. | |
| ## `state` is a claim ceiling, not an availability flag | |
| The most load-bearing distinction in this file. `state` says how strong a claim | |
| this capability is *entitled* to make once its stack is wired. `is_runnable` | |
| says whether an artefact and an adapter exist **today**. They are independent, | |
| and a client must render both: 27 of the 28 capabilities are `experimental` and | |
| 26 of them are `runnable: false`, which reads as *"this is a real feature, it is | |
| not switched on yet"* — not as *"this works."* | |
| The twenty-eighth is `poultry_uniformity`, and it is `unsupported_claim` | |
| (ADR 0023). It is the only capability whose claim was refused rather than | |
| corrected, and the distinction above is what made the refusal legible: its | |
| ceiling was never in doubt while the question was *when*, and collapsed the | |
| moment somebody measured what its input would be worth. | |
| Two capabilities run: `cattle_detection` and `poultry_count`, on YOLOX-m under | |
| Apache-2.0 (ADR 0017, ADR 0018). Both are `experimental` rather than | |
| `production`, because their only validation is 31 frames labelled by one | |
| non-expert and a `global-coco` provenance. | |
| ## Why state plus flags, and not one enum or one set | |
| Directive §2 names seven states: `production`, `experimental`, | |
| `human_confirmation`, `guided_capture`, `hardware_required`, `coming_soon`, | |
| `unsupported_claim`. They do not form a ladder, and §18 proves it — poultry | |
| footpad is *"Experimental / Guided Capture / Human Confirmation"* at once. | |
| They answer two different questions: | |
| - **How far can this be trusted?** `production`, `experimental`, `coming_soon`, | |
| `unsupported_claim`. Exactly one is true at a time. That is `CapabilityState`. | |
| - **What has to happen for an output to exist at all?** Guided capture, a human | |
| confirming, an accessory, a fixed installation. Any combination is possible, | |
| including none. That is a set of `Requirement`. | |
| A single enum would need one member per combination. A single flat set would | |
| let a capability be `production` and `coming_soon` together, which is | |
| meaningless. State plus flags is the only one of the three that can express §18 | |
| and cannot express nonsense. | |
| `Requirement` also carries `FIXED_INSTALLATION`, which §2 does not name but §1 | |
| and §27 do — *"continuous-monitoring version requires fixed camera/mic"*. That | |
| is the eighth reframing, and it is a requirement rather than a state for the | |
| same reason as the other three: §27 says the continuous form is a separate | |
| capability, so the flag describes an acquisition mode, not a trust level. | |
| `PRODUCTION`, `COMING_SOON`, `HARDWARE_REQUIRED` and `FIXED_INSTALLATION` have | |
| no member in the registry today — `UNSUPPORTED_CLAIM` was on that list until | |
| ADR 0023 and now holds `poultry_uniformity`. They stay because | |
| `state` is a wire vocabulary that two other services and a phone read, and | |
| adding a value later means a coordinated release across all of them. **Not | |
| because anything renders them yet** — the app's capability screens still carry | |
| the old vocabulary in comments that this change makes stale, on 43 lines across | |
| 27 Kotlin files. `REJECTED_CLAIMS` below names which hardware and which fixed | |
| installation would unlock which stronger claim. | |
| An earlier version of this paragraph added *"no Kotlin file reads `state` at | |
| all"*, which has stopped being true: | |
| `apps/android/…/capability/CapabilityEnums.kt` mirrors this vocabulary as an | |
| executable Kotlin enum. It was untracked when this was written, so it is another | |
| agent's in-flight work — but a value added here now costs a release on the phone | |
| in fact, not only in principle. | |
| `COMING_SOON` emptied when `poultry_uniformity` moved to `experimental`, and | |
| that same capability then filled `UNSUPPORTED_CLAIM`. So `state` runs two live | |
| values and two reserved ones. For a long time it ran *one* live value, which | |
| looked like a vocabulary doing no work — and was the honest reading of a | |
| registry where every capability's *claim* was supportable and almost none of | |
| them was *built*. The second fact is `is_runnable`'s to report, and it reports | |
| it for 26 of the 28. | |
| **The vocabulary earned its keep the first time a measurement came back against | |
| a capability rather than against a model.** An enum with one live value cannot | |
| say *"this one is different"*, and by the time it needs to, adding the value is | |
| a coordinated release across three codebases. | |
| ## Where this file is stricter than the directive's own status lines | |
| Fifteen capabilities carry `GUIDED_CAPTURE` although the status line in their | |
| directive section does not name it: `cattle_bcs` (§7), `cattle_gait` (§24), | |
| `cattle_identity` (§6.4), `cattle_ticks` (§25), `cattle_weight` (§22), | |
| `poultry_count` (§6.3), `poultry_eye_head`, `poultry_feather` and | |
| `poultry_hock` (§19), `poultry_fecal` (§13), `poultry_heat_stress` (§17), | |
| `poultry_inactive_birds` (§15), `poultry_respiratory` (§26), | |
| `poultry_uniformity` and `poultry_weight` (§23). Only four sections name the | |
| flag for a capability that holds it — §8, §11, §14 and §18. | |
| For twelve of the fifteen the flag follows the *capture* the section specifies | |
| rather than its status line, because §2 defines the flag by whether the user | |
| must follow a protocol: a side and a rear-quarter view, a five-metre walk, four | |
| to six samples, a held bird, a density-appropriate mode. Every one of those is | |
| an instruction the farmer has to obey for the capture to carry the signal. | |
| **Three of the fifteen do not have that justification, and should not pretend | |
| to.** §19 specifies no capture at all — no views, no samples, no duration — so | |
| `poultry_hock`, `poultry_feather` and `poultry_eye_head` carry the flag because | |
| *this file* invented their protocols (`held_bird_hock`, `whole_bird_dorsal`, | |
| `held_bird_head`), not because the directive asked for them. Presenting a bird's | |
| hock or its head to a camera really does need the bird caught and held, which is | |
| why the flag is right; but the source is a judgement made here, and | |
| `poultry_wound` shows how thin the line is — it is a lesion close-up anywhere on | |
| the body, exactly like `cattle_wound`, so it carries no flag. A reader who | |
| thinks `poultry_feather` is no more "held" than `poultry_wound` has a fair | |
| argument, and this note exists so they can find it. | |
| **An earlier draft of this paragraph said four and named four**, which was an | |
| undercount taken from the wrong reading and not a narrower definition — there is | |
| no reading of the directive under which the number is four. The list above was | |
| derived by matching every `GUIDED_CAPTURE` holder against the `## Status` block | |
| of its own section. | |
| It runs one way only. §20's *second* status block — *"Guided Capture / accessory | |
| recommended"* — belongs to the candling sub-capability for fine cracks, not to | |
| `egg_quality`, whose own status line is plain `Experimental`. So `egg_quality` | |
| carries no flag, and the candling route lives where the directive puts it: on | |
| `hairline_crack_from_ambient_photo`'s `available_with`. | |
| ## Compute is never a reason for a lower state | |
| Founder's instruction, and it constrains every classification in this file: a | |
| capability is not downgraded because a model is expensive to run. SAM 3.1, | |
| DINOv3, VGGT and a hosted multimodal reasoner do not fit on a phone and several | |
| want a GPU. That is a **hosting** decision — the inference legs can run | |
| somewhere with GPUs — and it produces a `Connectivity` tier, not a lower state | |
| and not a `hardware_required` flag. | |
| So where the model runs is answered by two fields, and neither of them is | |
| `state`: | |
| - **`connectivity`** — what a farmer gets with no signal. After this rewrite, | |
| 26 capabilities are `required`: the phone contributes nothing to the | |
| answer, so the honest badge is *"Saves now, analysis runs when you | |
| reconnect."* Two are `deferred`, because the Android app really does ship a | |
| MediaPipe COCO detector over `efficientdet_lite0.tflite` that answers | |
| *"cattle detected"* and counts visible birds offline | |
| (`apps/android/.../capture/SubjectDetector.kt`). **That file's "about 30 ms" | |
| is its own comment's claim with no benchmark behind it anywhere in this | |
| repo** — the detector is real, the latency is unverified. Per ADR 0002 every | |
| tier still saves the capture; connectivity gates analysis, never capture. | |
| - **`on_device_candidate`** — whether this could plausibly move to the phone | |
| later, which is the difference between a feature that works in a shed and one | |
| that works in the yard. Set only where the directive's own method is | |
| deterministic signal processing or an on-device detector that already runs, | |
| not wherever it would be nice. | |
| ## A licence is never a reason for a lower state either | |
| Second founder instruction, and it works the same way as the first: *"don't drop | |
| anything because of licence requirement for now. just get them done."* | |
| Nothing here is `coming_soon` or `unsupported_claim` because the best model for | |
| it is AGPL, non-commercial, research-only or gated. `cattle_identity` is the | |
| case that proves it — directive §4 names MegaDescriptor first, its weights are | |
| CC-BY-NC-4.0, and the capability is `experimental` anyway, because what decides | |
| that is whether a muzzle is photographable and whether the claim is confirmed, | |
| not whose terms the embedding arrives under. | |
| So the licence is recorded as an **attribute of the models a capability leans | |
| on**, in `model_stack`, and `licence_exposure()` answers *"which capabilities | |
| currently depend on a copyleft or non-commercial model"* without anyone going | |
| digging. The terms themselves live in `app/adapters/licences.py`, which is the | |
| adapters agent's ledger, records a primary source and a verification date for | |
| every runtime, and is the only thing here entitled to an opinion about terms. | |
| This file names runtimes; it does not restate their licences. | |
| That ledger still refuses to *serve* a disallowed runtime, which is correct and | |
| is a different decision from this one: refusing to run a model is a deployment | |
| answer, and classifying a capability is a product answer. A capability whose | |
| whole stack is unservable today is still classified on its signal, and | |
| `licence_exposure()` is how that shows up before anything is sold. | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass, field, replace | |
| from enum import Enum | |
| class CapabilityState(str, Enum): | |
| """How far a capability's output may be trusted. Exactly one is true. | |
| Ordering here is deliberate — strongest first — but it is not a ladder a | |
| capability climbs one rung at a time. `coming_soon` is not weaker than | |
| `experimental`; it is a different statement, about implementation rather | |
| than about evidence. | |
| """ | |
| #: Works reliably enough for normal use — meaning an Animap benchmark | |
| #: demonstrated it, not that a demo looked good (directive §37). Nothing | |
| #: holds this today. | |
| PRODUCTION = "production" | |
| #: The capability works and the output carries broad uncertainty the user | |
| #: can see. This is where the directive expects almost everything to sit | |
| #: for v0. | |
| EXPERIMENTAL = "experimental" | |
| #: An engineering path exists and nobody has walked it yet. Distinct from | |
| #: `experimental` with `runnable: false`: that one is wired-up work, this | |
| #: one is not designed work. | |
| COMING_SOON = "coming_soon" | |
| #: The claim itself is not scientifically supportable in any capture | |
| #: protocol this capability can offer. Directive §36 sets the bar: only after | |
| #: the **observable signal** has been shown to be absent or impractical, and | |
| #: a failed first model is not sufficient evidence. | |
| #: | |
| #: **One capability meets that bar: `poultry_uniformity`** (ADR 0023). It is | |
| #: the first member this value has had, and what met the bar was not a failed | |
| #: model — there is no model in it at all. It was error propagation: a | |
| #: coefficient of variation over measured weights carries the measurement | |
| #: error as well as the flock's spread, the inflation does not shrink with | |
| #: sample size, and the best published per-bird error for the method is a | |
| #: floor rather than an estimate. That is the observable signal shown | |
| #: impractical, which is the only thing §36 accepts. | |
| #: | |
| #: Individual claims meet it too, and more often — see `REJECTED_CLAIMS`. | |
| UNSUPPORTED_CLAIM = "unsupported_claim" | |
| class Requirement(str, Enum): | |
| """What has to happen before an output exists. Any combination, including none.""" | |
| #: Animap proposes and the user confirms or corrects. Directive §32: this is | |
| #: a feature, not a failure, and every correction is stored as ground truth. | |
| HUMAN_CONFIRMATION = "human_confirmation" | |
| #: Available only when the user follows the declared capture protocol. | |
| GUIDED_CAPTURE = "guided_capture" | |
| #: Needs an optional sensor or accessory **in the farmer's hand** — a | |
| #: moisture probe, a thermal camera, a candling backlight (directive §29). | |
| #: | |
| #: **This never means server-side compute.** A capability that wants a GPU | |
| #: for its inference leg is not `hardware_required`; that is a hosting | |
| #: decision, and the inference legs can run wherever GPUs are. Where the | |
| #: model runs is `Connectivity`'s question, not this flag's. | |
| HARDWARE_REQUIRED = "hardware_required" | |
| #: Needs a permanently placed camera or microphone (directive §27, §28). | |
| FIXED_INSTALLATION = "fixed_installation" | |
| class Modality(str, Enum): | |
| """What the capture consists of. Named as directive §34 names them.""" | |
| RGB_PHOTO = "rgb_photo" | |
| RGB_VIDEO = "rgb_video" | |
| AUDIO = "audio" | |
| CAMERA_POSE = "camera_pose" | |
| DEPTH_OPTIONAL = "depth_optional" | |
| class Connectivity(str, Enum): | |
| """Maps 1:1 to the badge and footnote the app shows. | |
| **Every tier still saves the capture.** Even `required` reads *"Saves now, | |
| analysis runs when you reconnect"* — connectivity defers analysis, it never | |
| blocks capture (ADR 0002). | |
| The tier is set by what the phone contributes to the **answer**, not by | |
| whether it can help frame the shot. Capture guidance is present everywhere | |
| and does not earn `deferred`; a partial result does. | |
| """ | |
| #: The full result with no connection. Nothing holds this. | |
| ON_DEVICE = "on_device" | |
| #: *"Captures now, full assessment after sync."* Part of the answer is | |
| #: computed on the phone. | |
| DEFERRED = "deferred" | |
| #: *"Saves now, analysis runs when you reconnect."* The phone computes none | |
| #: of the answer. This is the honest tier for anything whose first stage is | |
| #: a hosted model, and after the zero-training rewrite that is most of them. | |
| REQUIRED = "required" | |
| class Basis(str, Enum): | |
| """Where a bound came from. The three are not equally strong. | |
| Recorded per bound rather than described once, because the weakest kind is | |
| the one a reader has to be able to find. A guardrail is an engineering | |
| judgement with no measurement behind it, and saying so is the difference | |
| between a number somebody can challenge and a number that looks settled. | |
| """ | |
| #: The unit defines it. A BCS scale runs 1 to 5; a footpad grade runs 0 to 4; | |
| #: a share of a scanned region runs 0 to 100. Not a judgement. | |
| SCALE = "scale" | |
| #: The directive states or exemplifies it. | |
| DIRECTIVE = "directive" | |
| #: An engineering judgement made in this file, **with no measurement behind | |
| #: it**, chosen wide enough that a true reading cannot hit it. These are the | |
| #: ones to challenge. | |
| GUARDRAIL = "guardrail" | |
| class EvidenceItem: | |
| """One thing a capability may say in words, and where the wording came from. | |
| ## Why the words are a vocabulary and not a free-text field | |
| `evidence` and `limits` were declared as arrays of free strings, and that | |
| was the one field in the contract a model could write a sentence into. Three | |
| agents hardened the prose scan over it; each closed real phrasings and each | |
| was then beaten by a rephrasing, and the watchdogs' verdict converged on | |
| *"the identifier control is real, the publication control is theatre."* The | |
| last attempt closed twelve of fifteen numeric carriers and its own watchdog | |
| still published a forbidden uniformity figure from 28 of 28 capabilities, | |
| and a digit-free assertion — *"This flock is not uniform enough to | |
| harvest"* — had no defence at all and was never going to get one. | |
| **Anything that scans prose loses to an author who rephrases.** This | |
| repository learned that once already, in Android's `NoInventedDataTest`, and | |
| wrote it down there. So the field stops being prose. | |
| ADR 0024 carries the decision. The short form: the directive's own worked | |
| output is a rubric's observable features, not sentences — | |
| "evidence": ["prominent hooks", "visible pins", | |
| "limited fat around tail head"] | |
| — and §10's *"Multiple raised nodular lesions visible"*, §19's *"Reduced | |
| feather coverage on back and tail"*, §11's six hoof findings and §12's six | |
| faecal appearances are all items a capability can enumerate. §4's *"All | |
| calls must return structured JSON"* is read as meaning this, rather than as | |
| *"parses, and has these top-level keys"*. | |
| ## The phrase is the reader-facing string, not an identifier | |
| `claims` are snake_case identifiers a client renders. These are not: the | |
| phrase is what a farmer reads, verbatim, so the review that matters happens | |
| in this file's diff rather than in a client's string table. It also keeps | |
| the directive's own wording intact where the directive prints a line — §7's | |
| three features are here character for character. | |
| ## A phrase never carries a per-capture number | |
| §25 prints *"17 probable ticks visible across sampled regions"*, and 17 is | |
| not a property of the vocabulary. The invariant words are the phrase and the | |
| figure stays in `observations[].value`, where `OutputSpec` already bounds it | |
| on the unit the registry declared. **That is the whole of the old quantity | |
| problem, gone rather than mitigated**: a model cannot write a digit into | |
| words a farmer reads, because it cannot write the words. | |
| A phrase may carry a number that is a *constant* — a rubric's endpoints in | |
| *"Approximate grade on the 0-4 rubric"*, the seconds in a capture limit, | |
| §6.3's own uncertainty footnote. Those are reviewed here and asserted as an | |
| exact set by `tests/test_directive_payloads.py`, so adding one takes a diff. | |
| """ | |
| #: What a farmer reads. Reviewed here and nowhere else. | |
| phrase: str | |
| #: Where the wording comes from: a directive section, or `product` for a | |
| #: phrase this file wrote. **`product` is the one to challenge** — it means | |
| #: nobody outside this repository chose those words. | |
| source: str | |
| #: Why this phrase exists where the source does not make it obvious. Read by | |
| #: a person, never by code. | |
| note: str = "" | |
| #: A capability may say nothing about the capture that every other capability | |
| #: cannot also say, so the shared half of `limits` is declared once. | |
| #: | |
| #: **`limits` is enumerated for the same reason `evidence` is.** Leaving it free | |
| #: would have moved the problem rather than solved it: it is an array of strings | |
| #: on the same object, rendered under the same result, read by the same farmer. | |
| #: A control that closes one narrative field and leaves the other beside it is | |
| #: not a control. | |
| _SHARED_LIMITS: tuple[EvidenceItem, ...] = ( | |
| EvidenceItem("One view only", "§37", | |
| note="§37 asks for product uncertainty to be shown rather " | |
| "than a fabricated confidence."), | |
| EvidenceItem("Single view; veterinary review recommended", "§10"), | |
| EvidenceItem("Experimental result — a person confirms it", "§32", | |
| note="§32: human confirmation is a feature, not a failure."), | |
| EvidenceItem("Capture quality reduces confidence in this reading", "§37"), | |
| EvidenceItem("Assessed from what is visible in this capture only", "§30"), | |
| # **The escape hatch, and it is deliberately contentless.** An enumerated | |
| # vocabulary cannot describe the unanticipated, which is the real cost of | |
| # this change (ADR 0024). What it can do is say that something unanticipated | |
| # is there and route it to a person — which is §32's own answer — without | |
| # letting the model say what it thinks it is. | |
| EvidenceItem("Something outside this capability's vocabulary is visible; " | |
| "have a person look at the capture", "product", | |
| note="The only thing a model may say about an observation this " | |
| "vocabulary has no phrase for. It carries no content on " | |
| "purpose: a free-text escape hatch would be the old field " | |
| "back under a new name."), | |
| ) | |
| class ClaimQuantity: | |
| """What number, if any, one claim's `observations[].value` may carry. | |
| ## Why this exists at all | |
| `OutputSpec` bounded one quantity per capability and named the claims that | |
| carry it. Every other claim's value was unbounded — 73 of the registry's 93, | |
| across 12 capabilities that named none — so a reasoner could launder any | |
| figure through an observation nobody had bounded, and the prose gate in | |
| `app/adapters/claims.py` would then treat that figure as corroborated. The | |
| named case was `cattle_gait`: §24 forbids *"Lameness score 3"*, and | |
| `{"type": "gait_asymmetry", "value": 3}` published it. | |
| The fix cannot be a blanket bound over a capability's vocabulary, because a | |
| vocabulary is not one unit — §18 pairs a 0-to-4 footpad grade with a | |
| prevalence in percent. So the unit is declared per claim, which is what this | |
| is. | |
| ## `carries_number = False` is a claim about the claim, not a missing entry | |
| A body condition score, a count of lesions and a direction of travel are | |
| three different kinds of thing, and only two of them are quantities. | |
| *"Possible gait asymmetry"* has no magnitude; a number standing beside it is | |
| not a more precise reading, it is a score the directive refuses. Declaring | |
| that is the point of the field. | |
| ## How "no number" is enforced through a contract that cannot say it | |
| `bounds_for` is the hook `check_numeric_bounds` calls, and its answer is | |
| three slots — minimum, maximum, step. None of them says *"no number at | |
| all"*, and returning nothing in all three means *permitted*, which is the | |
| state this field exists to end. | |
| So a claim carrying no quantity declares an **empty interval**: a minimum | |
| above its maximum, which no number satisfies. It is a deliberate encoding | |
| and not a typo, and it is the only one those three slots have. | |
| **The first version of this returned 0 to 1** — absent or present, on the | |
| reasoning that a finding rides in a numeric field as a flag when it rides in | |
| one at all. A watchdog defeated it immediately, on the exact capability the | |
| field was built for: `{"type": "gait_asymmetry", "value": 1}` beside | |
| *"Lameness score 1."* published, and 91 of the 93 pairs took the same shape. | |
| A score of 1 is a score. The two digits that looked like they asserted | |
| nothing asserted the thing §24 forbids. | |
| What a categorical claim keeps is the string. `Observation.value` is | |
| `float | str | None`, `check_numeric_bounds` looks only at numbers, and | |
| *"moderate"*, *"left flank"* and *"drinker line 3"* all publish — so | |
| refusing every number costs a claim nothing it should have been saying. | |
| """ | |
| claim: str | |
| #: `None` when this claim carries no quantity — see the class docstring. | |
| unit: str | None = None | |
| minimum: float | None = None | |
| maximum: float | None = None | |
| #: The granularity the rubric supports, as JSON Schema's `multipleOf`. | |
| step: float | None = None | |
| basis: Basis | None = None | |
| #: Why these numbers and not others. Read by a person, never by code. | |
| why: str = "" | |
| #: The words this claim's `observations[].value` may take, if any. | |
| #: | |
| #: **`Observation.value` is `float | str | None`, and the string half was | |
| #: the last free-text channel a farm reads.** ADR 0022 recorded it as a | |
| #: residual and could not close it: `check_numeric_bounds` looks only at | |
| #: numbers, so `{"type": "gait_asymmetry", "value": "lameness score three"}` | |
| #: published. Enumerating `evidence` without enumerating this would have | |
| #: moved the problem one field sideways rather than solving it, which is the | |
| #: failure ADR 0024 exists to avoid. | |
| #: | |
| #: **Empty means no word at all**, which is the state most claims are in and | |
| #: is not a gap. §12's six faecal appearances are six *claims*; a `value` | |
| #: beside `visible_mucus` adds nothing a farmer reads and is a place to | |
| #: write a sentence. The claims that keep a word are the ones whose finding | |
| #: really is a word — a band, a severity, a site, a sex — and the words are | |
| #: listed here, in snake_case, because a client renders them. | |
| values: tuple[str, ...] = () | |
| #: Why this claim's value is free text, for the two that are. | |
| #: | |
| #: **Three of the registry's 94 claims carry a string nobody here can | |
| #: enumerate**, and each names something from the farm's own world rather | |
| #: than from a rubric: the animal's name (§6.4's *"This looks like Kofi"*) | |
| #: and two breeds (§6.5's *"Likely White Fulani"* and its | |
| #: *"White-Fulani-like phenotype"*). An enum of Nigerian cattle breeds would | |
| #: refuse the crossbred animal §6.5 exists to protect, and an enum of animal | |
| #: names is a farm's register, not a registry's vocabulary. | |
| #: | |
| #: **An earlier version of this comment said two, and named two.** The third | |
| #: — `breed_like_phenotype` — was the one a watchdog published through, and | |
| #: an undercount of an open channel is the worst kind: it names the holes and | |
| #: leaves one out. | |
| #: | |
| #: All three sit under `HUMAN_CONFIRMATION`, so a farmer sees the word and is | |
| #: asked; all three are scanned by every prose rule in | |
| #: `app/adapters/claims.py`; and a free value has to be shaped like a name | |
| #: rather than like a sentence, which is `claims._NAME_SHAPE` and is what | |
| #: makes this a residual rather than a reopening of the field ADR 0024 | |
| #: closed. | |
| free_value_reason: str | None = None | |
| def carries_number(self) -> bool: | |
| return self.unit is not None | |
| def carries_word(self) -> bool: | |
| """Whether a string is a legal value for this claim at all.""" | |
| return bool(self.values) or self.free_value_reason is not None | |
| def enforced_bounds(self) -> tuple[float | None, float | None, float | None]: | |
| """`(minimum, maximum, step)` as `check_numeric_bounds` will read them. | |
| For a claim that carries no quantity this is an interval containing no | |
| number, because the caller's three slots have no way to say *"none"* and | |
| returning nothing in all three means *permitted*. See the class | |
| docstring. | |
| The refusal a reader sees says the value is below a minimum of 1 and | |
| above a maximum of 0, which is confusing prose for a correct answer. It | |
| is the price of expressing a fourth state through three slots, and the | |
| wording is in `app/adapters/claims.py`, not here. | |
| """ | |
| if not self.carries_number: | |
| return (_NO_NUMBER_FLOOR, _NO_NUMBER_CEILING, None) | |
| return (self.minimum, self.maximum, self.step) | |
| #: The width below which a `range` is not a range. Exact, because a range whose | |
| #: ends are equal publishes a single figure however wide the units are. | |
| _ZERO_WIDTH = 0.0 | |
| #: An interval no number falls inside, for a claim that carries no quantity. | |
| #: Inverted on purpose — see `ClaimQuantity.enforced_bounds`. | |
| _NO_NUMBER_FLOOR = 1 | |
| _NO_NUMBER_CEILING = 0 | |
| class OutputSpec: | |
| """What the capability returns, and whether it is allowed to look precise. | |
| `show_range` is the flag that stops *"BCS 2.63"*. Directive §7 is blunt | |
| about it: false precision is worse than a broad useful estimate. | |
| ## The bounds are refusals, not accuracy claims | |
| A watchdog validated `respiratory_rate_range` at **0 to 100,000 breaths a | |
| minute** against the generated schema, because only the hand-written | |
| `BCS_SCHEMA` carried `minimum` and `maximum`. Every other numeric output was | |
| unbounded. So the bounds live here, next to the vocabulary, for the same | |
| reason `forbidden_claims` does: the constraint belongs where the capability | |
| is declared, not in whichever adapter happens to run it. | |
| **`plausible_min` and `plausible_max` mark the impossible, not the | |
| unusual.** Outside them a value is not a poor estimate, it is a broken one — | |
| a cow that weighs four grams, a bird counted 1.4 times. They are deliberately | |
| wide, because a bound that refuses a true outlier is worse than no bound: it | |
| silently deletes the one capture that mattered. Nothing here should be read | |
| as a statement about where a real answer usually falls. That is | |
| `stated_uncertainty`'s job, in `app/dispositions.py`, where the measured | |
| figures are. | |
| Each bound is marked with a `Basis` — scale, directive or guardrail — and | |
| they are not equally strong. A guardrail is an engineering judgement with no | |
| measurement behind it, and it says so rather than being left to look like a | |
| finding. | |
| ## The headline bounds cover two fields, and `quantities` covers the rest | |
| `plausible_min`, `plausible_max` and `step` bound the capability's own | |
| quantity, which is what `range` and `best_estimate` report and nothing else. | |
| They were the whole of the numeric contract for one commit, and that left | |
| every observation on a claim outside `measured_claims` unbounded — 73 of the | |
| registry's 93 allowed claims, in 12 capabilities that declared none at all. | |
| A figure laundered through one of those was then treated as corroborated by | |
| the prose gate, so §24's forbidden *"Lameness score 3"* published as | |
| `{"type": "gait_asymmetry", "value": 3}`. | |
| `quantities` closes that by declaring, per claim, what number the claim may | |
| carry — or that it carries none. See `ClaimQuantity`, which also records | |
| where its enforcement is weaker than its declaration. | |
| `step` is the same idea applied to granularity, and it is what catches | |
| §7's own forbidden example. `BCS_SCHEMA` had to be repaired once because | |
| `best_estimate: 2.6347` validated against a bare `{"type": "number"}`; a | |
| half-point rubric cannot make a two-decimal claim, so `step` says 0.5 and | |
| the schema refuses the rest. On a count it says 1, because 327.4 birds is | |
| not a more careful answer than 327. | |
| ## These were declared here and read by nothing, for one commit | |
| Worth writing down, because the failure is easy to repeat and it was not | |
| visible from this file. Every field above was correct, published by | |
| `GET /capabilities`, and **wired to no check at all**: | |
| `app/adapters/claims.py::schema_for` built `range`, `best_estimate` and | |
| `observations[].value` as bare unbounded numbers, so a watchdog published | |
| 100,000 breaths a minute, a cow weighing 0.004 kg and — §7's own named | |
| forbidden example — `best_estimate: 2.63` on `cattle_bcs`. Impossible-high | |
| in 16 of 16, negative in 16 of 16, off-step in 10 of 10. | |
| The tests could not see it, and said so in their own docstring: they | |
| asserted that `step` was `0.5` and that `2.63 % 0.5 != 0`, which is a fact | |
| about Python arithmetic rather than about this system. **A declaration | |
| nothing reads is not a control**, and a test that reads the declaration | |
| cannot tell the difference. `tests/test_dispositions.py` now publishes | |
| payloads and expects refusals instead. | |
| """ | |
| type: str | |
| unit: str | None = None | |
| show_range: bool = False | |
| #: Inclusive. `None` for a categorical output, which has no numeric value to | |
| #: bound — twelve of the 28 capabilities return no number at all. | |
| plausible_min: float | None = None | |
| plausible_max: float | None = None | |
| #: The granularity the rubric can actually support, as JSON Schema's | |
| #: `multipleOf`. `None` means continuous. | |
| step: float | None = None | |
| #: Which of `allowed_claims` carry **this** quantity, so an observation's | |
| #: `value` can be bounded by the right numbers. | |
| #: | |
| #: **A capability's vocabulary is not all one unit, and that is why the | |
| #: bound cannot simply be applied to every observation.** §18's footpad | |
| #: capability allows `approximate_grade` on a 0-4 rubric *and* | |
| #: `sampled_prevalence`, whose own worked example is "20 birds sampled, 4 | |
| #: moderate or worse, **20%**". A blanket `maximum: 4` over that vocabulary | |
| #: would refuse the directive's own example. §14 pairs a respiratory rate of | |
| #: 5-200 with `capture_quality`; both wound capabilities pair an area in cm² | |
| #: with `change_since_last_scan`, which is legitimately negative when a | |
| #: wound is healing. | |
| #: | |
| #: So the registry names the claims the unit belongs to, and every other | |
| #: claim's value stays unbounded — which is the honest state, because | |
| #: nothing here knows what unit those carry. Refusing a real reading is the | |
| #: worse failure of the two, and this field is what stops the fix causing it. | |
| #: | |
| #: **It is no longer the whole answer.** Naming the claims that carry *this* | |
| #: unit left every other claim unbounded, which is the hole `quantities` | |
| #: closes. This field survives because it is what a client reads to know | |
| #: which observations the headline bounds apply to, and because `range` and | |
| #: `best_estimate` still carry the headline quantity and nothing else. | |
| measured_claims: tuple[str, ...] = () | |
| #: Every claim in `allowed_claims`, and what number each may carry. Spliced | |
| #: in from `_QUANTITIES` by `_cap`, so the table can be read against the | |
| #: vocabulary in one pass and a claim added without one is caught by a test | |
| #: rather than by a watchdog. | |
| quantities: tuple[ClaimQuantity, ...] = () | |
| def is_numeric(self) -> bool: | |
| """Whether this output carries a number a schema should bound. | |
| Keyed to `unit` rather than to `plausible_min`, so that an output which | |
| gains a unit and no bounds fails a test rather than passing silently. | |
| """ | |
| return self.unit is not None | |
| def quantity_for(self, claim: str) -> ClaimQuantity | None: | |
| """What this capability says `claim` may carry, or `None` if it is silent.""" | |
| for quantity in self.quantities: | |
| if quantity.claim == claim: | |
| return quantity | |
| return None | |
| def bounds_for(self, claim: str) -> tuple[float | None, float | None, float | None]: | |
| """`(minimum, maximum, step)` for one claim's value, any of them `None`. | |
| The hook `app/adapters/claims.py::check_numeric_bounds` calls for every | |
| observation, so what this returns is what the product will publish. | |
| `quantities` answers first and answers for every claim in the | |
| vocabulary. The fall-back below is what the registry used to do and what | |
| left 73 claims open: it applies the headline bounds to the claims that | |
| carry the headline unit and returns nothing for the rest. It stays only | |
| for a claim `_QUANTITIES` has not been told about, and | |
| `test_dispositions.py` refuses that state — so reaching it means a test | |
| is missing, not that a claim is unbounded on purpose. | |
| """ | |
| quantity = self.quantity_for(claim) | |
| if quantity is not None: | |
| return quantity.enforced_bounds | |
| if not self.is_numeric or claim not in self.measured_claims: | |
| return (None, None, None) | |
| return (self.plausible_min, self.plausible_max, self.step) | |
| def words_for(self, claim: str) -> tuple[tuple[str, ...], str | None]: | |
| """What word this claim's `value` may be, as `(permitted, free reason)`. | |
| `([], None)` — the default and the answer for most of the vocabulary — | |
| means *no string at all*. See `ClaimQuantity.values` for why that is a | |
| decision rather than a gap. | |
| A claim `quantities` has not been told about also gets `([], None)`, | |
| which is the strict answer. `test_dispositions.py` refuses that state | |
| for any claim in the registry, so reaching it means a table entry is | |
| missing rather than that a claim is open. | |
| """ | |
| quantity = self.quantity_for(claim) | |
| if quantity is None: | |
| return ((), None) | |
| return (quantity.values, quantity.free_value_reason) | |
| def range_violations(self, values: object) -> list[str]: | |
| """Why this `range` may not be published, or an empty list. | |
| **A zero-width range publishes an exact figure.** `[412, 412]` satisfies | |
| every check a range has ever had here — two items, ascending, both ends | |
| inside the declared bounds — while saying 412 kg exactly, which is the | |
| claim §30 rejects and `show_range` exists to prevent. §22 prints | |
| *"Experimental weight estimate, 350-430 kg"* and calls the initial range | |
| wide; §37 contrasts that band with *"327 birds exactly"*; §38 asks for | |
| broad outputs before precise ones. A capability that declares | |
| `show_range` has said it cannot support a point value, so it may not | |
| emit one through the field that exists to stop it. | |
| **This is not on the publication path yet**, and pretending otherwise | |
| would repeat the failure this registry has already had once: the bounds | |
| were declared, published and read by nothing for a commit. The only | |
| caller that sees a `range` is `claims.enforce`, in `app/adapters/`. Until | |
| it calls this, the rule is enforced by `tests/test_dispositions.py` and | |
| by nothing else. ADR 0022 carries the one-call change and why it was not | |
| made here. | |
| """ | |
| if not self.show_range or not isinstance(values, list) or len(values) != 2: | |
| return [] | |
| low, high = values | |
| if not all( | |
| isinstance(v, (int, float)) and not isinstance(v, bool) for v in (low, high) | |
| ): | |
| return [] | |
| if high - low > _ZERO_WIDTH: | |
| return [] | |
| return [ | |
| f"$.range is [{low}, {high}], which states an exact " | |
| f"{self.unit or 'value'} rather than a band. This capability " | |
| f"declares show_range because it cannot support a point value, and " | |
| f"§37 rejects an exact figure the signal does not carry" | |
| ] | |
| class AcquisitionProtocol: | |
| """Directive §34: each capability declares how its signal is acquired. | |
| This is the contract the Android capture flow reads and the inference | |
| provider validates against. The two claim lists at the bottom are the | |
| mechanism that stops the product ever saying *"Lumpy skin disease | |
| confirmed"* — a claim not in `allowed_claims` has nowhere to be emitted | |
| from, and one in `forbidden_claims` is asserted against. | |
| """ | |
| #: A short name for the capture flow, shared with the Android app. | |
| protocol: str | |
| modality: tuple[Modality, ...] | |
| output: OutputSpec | |
| minimum_capture_seconds: int | None = None | |
| preferred_capture_seconds: int | None = None | |
| #: Directive §34's `cattle_gait` example. Walking distance, not camera range. | |
| minimum_distance_m: float | None = None | |
| #: Samples, for the capabilities whose unit of work is a bird or a dropping | |
| #: rather than a second of video (§13, §18, §23). | |
| minimum_samples: int | None = None | |
| preferred_samples: int | None = None | |
| required_views: tuple[str, ...] = () | |
| optional_inputs: tuple[str, ...] = () | |
| #: The buttons the user is offered. Empty unless `HUMAN_CONFIRMATION` is set. | |
| confirmation_options: tuple[str, ...] = () | |
| #: Where a result goes when it is not a farmer's decision to make (§10). | |
| escalation: str | None = None | |
| #: Workflows this capability must never hold up, whatever it returns or | |
| #: fails to return. §6.6 is explicit that a sex suggestion must not block | |
| #: registration, and that is a property of the capability rather than of a | |
| #: screen — so it is declared here, where a client reads it, instead of | |
| #: being remembered by whoever builds the form. | |
| never_blocks: tuple[str, ...] = () | |
| #: Conditions under which no result may be produced. `resolution`, | |
| #: `illumination` and `motion_blur` are the checks `app/quality.py` actually | |
| #: implements; the rest are named by the directive and not yet implemented. | |
| reject_if: tuple[str, ...] = () | |
| #: What a **model** may emit for this capability. This is the enum that | |
| #: `app/adapters/claims.py::schema_for` hands a reasoner, so anything listed | |
| #: here is a field a model can fill in. A number the app works out from | |
| #: records the model cannot see does not belong here — see `derived_claims`. | |
| allowed_claims: tuple[str, ...] = () | |
| forbidden_claims: tuple[str, ...] = () | |
| #: What a model may say **in words a farmer reads**, drawn from this | |
| #: capability's own rubric. Spliced in from `_EVIDENCE` by `_cap`. | |
| #: | |
| #: **A skin finding is not a footpad finding**, so this is per capability | |
| #: exactly as `allowed_claims` is. Sharing happens only where the directive | |
| #: prints the same line for two captures — §9 and §19 give `cattle_wound` | |
| #: and `poultry_wound` the same wording, and they get the same phrase | |
| #: because the directive gave it to them, not because the table was | |
| #: convenient to write. | |
| #: | |
| #: Empty for `poultry_uniformity`, whose `allowed_claims` is also empty: | |
| #: a capability that may claim nothing may say nothing (ADR 0023, ADR 0024). | |
| allowed_evidence: tuple[EvidenceItem, ...] = () | |
| #: What a model may say about the **capture** rather than about the animal. | |
| #: `_SHARED_LIMITS` plus one phrase per condition in `reject_if`, spliced by | |
| #: `_cap` so a rejection condition and the sentence a farmer reads for it | |
| #: cannot drift apart. | |
| allowed_limits: tuple[EvidenceItem, ...] = () | |
| def evidence_phrases(self) -> tuple[str, ...]: | |
| """The `evidence` enum, in declaration order.""" | |
| return tuple(item.phrase for item in self.allowed_evidence) | |
| def limit_phrases(self) -> tuple[str, ...]: | |
| """The `limits` enum, in declaration order.""" | |
| return tuple(item.phrase for item in self.allowed_limits) | |
| #: Quantities this capability's result screen may show that **no model | |
| #: produces**. The app computes them, from a capability output plus what the | |
| #: farm already knows. | |
| #: | |
| #: This exists because §6.3 requires three poultry quantities to stay | |
| #: distinct and only two of them are observations. Putting the third in | |
| #: `allowed_claims` handed a hosted model a field called | |
| #: `reconciled_flock_population` and let it write a whole-house number into | |
| #: it — which a watchdog duly did, with `value: 3200` from a partial pan. | |
| #: That is §30's rejected claim arriving through the vocabulary that was | |
| #: supposed to prevent it. | |
| #: | |
| #: **Deliberately not `forbidden_claims`.** A forbidden claim joins the | |
| #: registry-wide `FORBIDDEN_CLAIMS`, which `app/counting.py` refuses | |
| #: outright — and a reconciled population is a legitimate number for the | |
| #: product to show. It is illegitimate only as a *model's* claim. This field | |
| #: keeps the concept visible, as §6.3 demands, without giving a reasoner | |
| #: somewhere to put it. | |
| derived_claims: tuple[str, ...] = () | |
| class Capability: | |
| key: str | |
| species: str | |
| state: CapabilityState | |
| requirements: frozenset[Requirement] | |
| connectivity: Connectivity | |
| save_target: str | |
| acquisition: AcquisitionProtocol | |
| #: What the model reports. A measurement carries no interpretation and needs | |
| #: no confidence tile; a judgement carries both. Five of the prototype's | |
| #: thirteen result screens omit confidence for exactly this reason. | |
| output_kind: str = "judgement" | |
| #: Capabilities that must ship first. | |
| #: | |
| #: **No capability declares one today, and the empty state is a result | |
| #: rather than an oversight.** The only member was `poultry_uniformity`, | |
| #: which waited on `poultry_weight` because §23 derives a flock spread from | |
| #: per-bird weights. ADR 0023 removed it: the dependency was not merely | |
| #: unmet, it was *unsuitable*, and a pointer that resolves into a refusal | |
| #: tells a reader the blocker is sequencing when the blocker is suitability. | |
| #: | |
| #: The field stays because the relation is real and will recur — a | |
| #: capability computed from another capability's output is the shape §23 | |
| #: describes and §6.3 nearly needs. What the one member proved is that | |
| #: `depends_on` says *"this needs that input"* and never *"that input will | |
| #: be good enough"*, and only the second question decides whether a claim | |
| #: survives. | |
| depends_on: tuple[str, ...] = () | |
| #: Whether this could plausibly run on the phone later — the difference | |
| #: between a feature that works in a shed with no signal and one that works | |
| #: in the yard. **This is an engineering property, never a reason for a | |
| #: lower state**: a capability that needs a GPU is hosted where GPUs are. | |
| #: | |
| #: Set only on the strict test: the directive's own method is deterministic | |
| #: signal processing (§4 — optical flow, FFT, geometry, arithmetic), or an | |
| #: on-device model that already ships. Everything gated on a SAM mask, a | |
| #: DINOv3 embedding or a hosted multimodal call is false until the adapters | |
| #: agent reports which legs are small enough, which is their measurement to | |
| #: make and not this file's to guess. | |
| on_device_candidate: bool = False | |
| #: The runtimes this capability's declared stack leans on, named as | |
| #: `app/adapters/licences.py` names them. **An attribute, never a gate**: a | |
| #: non-commercial or gated runtime here does not lower `state`, it shows up | |
| #: in `licence_exposure()`. A name absent from that ledger means nobody has | |
| #: checked its terms yet, which is itself the useful answer. | |
| model_stack: tuple[str, ...] = () | |
| model_provider: str | None = None | |
| model_version: str | None = None | |
| geography_validated: tuple[str, ...] = field(default_factory=tuple) | |
| notes: str = "" | |
| def modality(self) -> str: | |
| """The primary modality, in the single-word spelling the API's | |
| `capabilities` table stores. | |
| Kept so that widening `modality` to a list does not force a column | |
| change at the same time as a vocabulary change. | |
| **The first entry in `acquisition.modality` wins, not the richest | |
| one.** Preferring video would report `poultry_count` as a video | |
| capability because a controlled pan is one valid capture, when a single | |
| photograph is the other and is what the shipped adapter reads. The | |
| declared order is the capability's own answer to "what is this mostly". | |
| Four values change anyway, because the capture changed: `cattle_weight` | |
| photo → video and `cattle_respiratory` audio → video (§14 is flank | |
| video, not cough audio); `poultry_weight` and `poultry_uniformity` | |
| video → photo (§23 is a held bird, not a flock scan). The API's | |
| transcription still holds the old values (ADR 0021). | |
| """ | |
| first = self.acquisition.modality[0] | |
| if first == Modality.RGB_VIDEO: | |
| return "video" | |
| if first == Modality.AUDIO: | |
| return "audio" | |
| return "photo" | |
| def duration_seconds(self) -> int | None: | |
| return self.acquisition.preferred_capture_seconds | |
| def frames_required(self) -> int: | |
| return max(1, len(self.acquisition.required_views)) | |
| def is_runnable(self) -> bool: | |
| """Whether this capability can produce a result **today**. | |
| Unchanged in substance from the four-state registry, and deliberately | |
| so: it is what `app/providers.py` calls, and the property that stops a | |
| status edit from unlocking a capability. A state alone still unlocks | |
| nothing — a checksummed artefact and a version have to be there too. | |
| """ | |
| return ( | |
| self.state in (CapabilityState.PRODUCTION, CapabilityState.EXPERIMENTAL) | |
| and self.model_provider is not None | |
| and self.model_version is not None | |
| ) | |
| def may_be_promoted_to_production(self) -> bool: | |
| """Whether this capability could legitimately reach `production`. | |
| Was `may_be_enabled` before ADR 0021; `enabled` is no longer a state. | |
| The rule is the same one: `production` means the product acts on the | |
| result without a human reading it first, and a model whose only recorded | |
| validation is the distribution it was pretrained on has not earned that. | |
| The sentinel geography blocks the promotion mechanically instead of | |
| relying on someone remembering. | |
| """ | |
| return ( | |
| self.is_runnable | |
| and bool(self.geography_validated) | |
| and UNVALIDATED_GEOGRAPHY not in self.geography_validated | |
| ) | |
| def needs_human_confirmation(self) -> bool: | |
| return Requirement.HUMAN_CONFIRMATION in self.requirements | |
| def needs_guided_capture(self) -> bool: | |
| return Requirement.GUIDED_CAPTURE in self.requirements | |
| #: Recorded in `geography_validated` when a model's only provenance is its | |
| #: pretraining distribution. COCO is web-scraped photographs from everywhere and | |
| #: nowhere; a Holstein in a Dutch field and a White Fulani in Kaduna are not the | |
| #: same test. | |
| UNVALIDATED_GEOGRAPHY = "global-coco" | |
| #: The quality checks `app/quality.py` implements, on every visual capture. | |
| _GATE = ("resolution", "illumination", "motion_blur") | |
| #: Every `reject_if` condition in the registry, and the sentence a farmer reads | |
| #: when it fires. | |
| #: | |
| #: **Derived rather than written twice.** `_cap` turns a capability's own | |
| #: `reject_if` tuple into its `limits` vocabulary, so a rejection condition and | |
| #: the wording for it cannot drift apart, and a condition added without a phrase | |
| #: fails a test rather than silently leaving a capability unable to explain | |
| #: itself. That is the same relation `FORBIDDEN_CLAIMS` has to | |
| #: `forbidden_claims`. | |
| #: | |
| #: The wording is imperative where the farmer can fix it in the yard and | |
| #: descriptive where they cannot, because a limit that reads as an instruction | |
| #: the farmer cannot follow is worse than one that reads as a fact. | |
| _LIMIT_PHRASES: dict[str, str] = { | |
| # `app/quality.py` implements these three. | |
| "resolution": "Too low a resolution to assess — move closer, or use a " | |
| "better camera", | |
| "illumination": "Too dark or too bright to assess", | |
| "motion_blur": "Too much motion blur to assess", | |
| # Framing and pose. | |
| "move_closer": "Move closer", | |
| "wrong_angle": "Wrong angle — follow the capture guide", | |
| "wrong_pose": "The animal is not standing in the pose this capture needs", | |
| "animal_heavily_occluded": "The animal is heavily occluded", | |
| "animal_not_walking": "The animal is not walking", | |
| "animal_moving": "The animal moved during the recording", | |
| "camera_moving": "The camera moved during the recording", | |
| "subject_partly_out_of_frame": "Part of the subject is outside the frame", | |
| # Body parts a protocol requires to be presented. | |
| "muzzle_not_visible": "The muzzle is not visible", | |
| "hooks_and_pins_not_visible": "The hooks and pins are not visible", | |
| "teeth_obscured": "The lower front teeth are obscured", | |
| "hoof_not_lifted": "The hoof was not lifted", | |
| "sole_not_clean": "The sole was not cleaned before the photograph", | |
| "foot_not_presented": "The underside of the foot was not presented", | |
| "hock_not_presented": "The hock was not presented", | |
| "head_not_presented": "The head was not presented", | |
| "flank_not_in_frame": "The flank is not in frame", | |
| "region_not_visible": "One of the guided regions was not visible", | |
| # Scene and background. | |
| "bird_not_isolated": "The bird is not isolated against a clear background", | |
| "cluttered_background": "The background is too cluttered to separate the " | |
| "bird from it", | |
| "sample_not_isolated": "The sample is not isolated from the ground around it", | |
| "floor_not_visible": "The floor is not visible", | |
| "birds_not_visible": "The birds are not visible", | |
| "eggs_overlapping": "The eggs overlap, so they cannot be counted separately", | |
| # Capture protocol not followed. | |
| "pan_too_fast": "The pan was too fast to track birds between frames", | |
| "density_beyond_measured_regime": "This scene is denser than anything this " | |
| "capability has been measured on", | |
| "insufficient_geometry": "The sweep did not carry enough geometry to " | |
| "estimate from", | |
| "insufficient_sample": "Fewer birds were sampled than the protocol asks for", | |
| "recording_too_short": "The recording is shorter than the 30 seconds §26 " | |
| "asks for", | |
| # Audio. | |
| "machinery_dominates": "Fans or machinery dominate the recording", | |
| "speech_dominates": "Speech dominates the recording", | |
| } | |
| #: Capability key → the runtimes its declared stack leans on. | |
| #: | |
| #: Held as one table rather than a field on each entry so that it can be read | |
| #: against `app/adapters/licences.py` in a single pass. Every name is either in | |
| #: that ledger or deliberately not: `vggt`, `deeplabcut-superanimal` and | |
| #: `sam-audio` are named by directive §3, §4 and §26 and have no ledger entry | |
| #: yet, so they report as unchecked rather than as cleared. | |
| #: | |
| #: The models are the directive's own, section by section. Where a capability | |
| #: runs today the shipped runtime is listed alongside the target stack, because | |
| #: both are true and the difference matters. | |
| _STACK: dict[str, tuple[str, ...]] = { | |
| # cattle | |
| "cattle_detection": ("sam3", "grounding-dino-hf", "yolox-onnx"), # §6.1 | |
| "cattle_identity": ("megadescriptor-timm", "dinov3-onnx"), # §6.4 | |
| "cattle_breed": ("hosted-multimodal", "dinov3-onnx"), # §6.5 | |
| "cattle_sex": ("hosted-multimodal",), # §6.6 | |
| "cattle_weight": ("sam3", "vggt"), # §22 | |
| "cattle_bcs": ("hosted-multimodal", "dinov3-onnx"), # §7 | |
| "cattle_age_dentition": ("hosted-multimodal",), # §8 | |
| "cattle_gait": ("deeplabcut-superanimal", "sam3"), # §24 | |
| "cattle_ticks": ("sam3", "grounding-dino-hf", "hosted-multimodal"), # §25 | |
| "cattle_wound": ("sam3", "hosted-multimodal", "opencv-numpy"), # §9 | |
| "cattle_skin": ("sam3", "hosted-multimodal", "dinov3-onnx"), # §10 | |
| "cattle_hoof": ("sam3", "hosted-multimodal", "dinov3-onnx"), # §11 | |
| "cattle_feces": ("sam3", "hosted-multimodal", "dinov3-onnx"), # §12 | |
| "cattle_respiratory": ("sam3", "opencv-numpy"), # §14 | |
| # poultry | |
| "poultry_count": ("countgd", "grounding-dino-hf", "sam3", "yolox-onnx"), # §6.3 | |
| "poultry_fecal": ("dinov3-onnx", "hosted-multimodal"), # §13 | |
| "poultry_inactive_birds": ("sam3", "opencv-numpy"), # §15 | |
| "poultry_weight": ("sam3", "vggt"), # §23 | |
| # §23 again: no model at all. The empty tuple is the point — a coefficient | |
| # of variation over twenty numbers has no licence exposure of any kind. | |
| # | |
| # **It is also why this capability's refusal could not have been found by | |
| # reading this table.** An empty stack looks like the safest row here: no | |
| # weights, no terms, no gated download, nothing to check. The thing that | |
| # ended the capability was the accuracy of the numbers going *in*, which no | |
| # licence ledger and no model inventory can see (ADR 0023). | |
| "poultry_uniformity": (), | |
| "poultry_footpad": ("hosted-multimodal", "dinov3-onnx", "sam3"), # §18 | |
| "poultry_heat_stress": ("hosted-multimodal", "opencv-numpy"), # §17 | |
| "poultry_litter": ("sam3", "hosted-multimodal", "dinov3-onnx"), # §16 | |
| "poultry_respiratory": ("sam-audio", "hosted-multimodal"), # §26 | |
| "poultry_hock": ("sam3", "hosted-multimodal", "dinov3-onnx"), # §19 | |
| "poultry_feather": ("sam3", "hosted-multimodal", "dinov3-onnx"), # §19 | |
| "poultry_wound": ("sam3", "hosted-multimodal", "dinov3-onnx"), # §19 | |
| "poultry_eye_head": ("sam3", "hosted-multimodal", "dinov3-onnx"), # §19 | |
| "egg_quality": ("sam3", "opencv-numpy", "hosted-multimodal"), # §20 | |
| } | |
| def licence_exposure() -> dict[str, list[str]]: | |
| """Which capabilities lean on a model Animap may not currently serve. | |
| The question the founder asked this registry to be able to answer without | |
| anyone going digging, and the reason `model_stack` exists. It reports three | |
| kinds of exposure, which need different responses: | |
| - **refused** — the ledger has read the terms and Animap may not serve from | |
| them. `megadescriptor-timm` is CC-BY-NC-4.0; `miewid` grants nothing at | |
| all. A capability leaning on one of these needs a substitute before it is | |
| sold, not before it is built. | |
| - **gated** — servable, but behind an accepted-terms wall, so | |
| `install_models.py` cannot fetch it unattended. A deployment problem. | |
| - **unchecked** — the runtime is not in the ledger, so nobody has read its | |
| terms. This is not the same as a problem, and it is not the same as fine. | |
| **Nothing here changes a capability's state**, and a test asserts that. | |
| Imported lazily because `app/adapters/` imports this module, and a | |
| module-level import would close the cycle. | |
| """ | |
| from app.adapters.licences import RUNTIME_LICENCES | |
| exposure: dict[str, list[str]] = {} | |
| for key, capability in REGISTRY.items(): | |
| flags = [] | |
| for runtime in capability.model_stack: | |
| licence = RUNTIME_LICENCES.get(runtime) | |
| if licence is None: | |
| flags.append(f"{runtime}: unchecked — not in the licence ledger") | |
| elif not licence.servable: | |
| flags.append(f"{runtime}: refused — {licence.licence}") | |
| elif licence.gated: | |
| flags.append(f"{runtime}: gated — {licence.licence}") | |
| if flags: | |
| exposure[key] = flags | |
| return exposure | |
| def _number(claim, unit, low, high, basis, why, step=None, values=()) -> ClaimQuantity: | |
| """A claim carrying a quantity, and sometimes a band word as well. | |
| **A number and a word are not alternatives.** §38 asks for broad outputs | |
| before precise ones and gives several capabilities both — a body condition | |
| is *"thin; appropriate; heavy"* **or** a half-point band, and §16 prints | |
| *"Caking: High"* beside *"18% of scanned region"*. A claim that could only | |
| answer in figures refused the coarser half of §38, which is the half the | |
| directive says to build first. | |
| """ | |
| return ClaimQuantity(claim=claim, unit=unit, minimum=low, maximum=high, | |
| step=step, basis=basis, why=why, values=tuple(values)) | |
| def _no_number(claim, why) -> ClaimQuantity: | |
| """A claim that carries no quantity and no word. `why` says what it is.""" | |
| return ClaimQuantity(claim=claim, why=why) | |
| def _words(claim, values, why) -> ClaimQuantity: | |
| """A claim whose `observations[].value` is a word from a closed list. | |
| The list is the whole of what this claim can say, exactly as | |
| `allowed_claims` is the whole of what a capability can claim. See | |
| `ClaimQuantity.values`. | |
| """ | |
| return ClaimQuantity(claim=claim, values=tuple(values), why=why) | |
| #: What a severity word may be, where the directive prints one and gives no | |
| #: rubric for it. §9's *"Moderate surrounding swelling"* and §19's *"Moderate | |
| #: visible hock lesion"* are the two printed cases. | |
| #: | |
| #: **Four words and no number**, which is the distinction §18 and §19 turn on: | |
| #: §18 gives the footpad an explicit 0-to-4 rubric and gets `approximate_grade`, | |
| #: and §19 gives the hock none, so a grade here would be a scale this file | |
| #: invented. A word is a description; a number would be a score. | |
| _SEVERITY = ("none", "mild", "moderate", "marked") | |
| #: Presence, for a finding whose only honest reading is whether it is there. | |
| _PRESENCE = ("none", "present") | |
| #: Normal or abnormal, which is §11's and §19's own summary finding. | |
| _NORMALITY = ("normal", "abnormal") | |
| #: Where on a bird a wound is, for §19's *"Open wound visible on left flank"*. | |
| #: | |
| #: **A site is enumerable and a breed is not**, which is why one is a list here | |
| #: and the other is `free_value_reason`. A bird has a fixed anatomy; a farm's | |
| #: animals and a region's breeds do not. | |
| _BIRD_SITES = ( | |
| "head", "comb", "wattle", "cheek", "eye", "beak", "neck", "breast", "keel", | |
| "back", "abdomen", "left_wing", "right_wing", "left_flank", "right_flank", | |
| "vent", "left_thigh", "right_thigh", "left_shank", "right_shank", | |
| "left_hock", "right_hock", "left_foot", "right_foot", "toe", "tail", | |
| ) | |
| #: §16's band words, for a litter condition reported coarsely. | |
| #: | |
| #: §16's own printed line is *"Caking: High"*, and the four condition claims | |
| #: were declared as percentages only — so the directive's own example was | |
| #: refused as *"'caked' carries no word at all"* while the percentage beside it | |
| #: published. §38's *"build broad outputs before precise outputs"* is the | |
| #: general form of that mistake. | |
| _LITTER_BANDS = ("none", "low", "moderate", "high") | |
| #: Where on a cow a wound is, for §38's *"location"* bullet. | |
| #: | |
| #: Longer than a bird's list because a cow is bigger and because §25's guided | |
| #: regions and §11's hoof are already named parts of this product's vocabulary. | |
| _CATTLE_SITES = ( | |
| "head", "left_eye", "right_eye", "muzzle", "left_ear", "right_ear", "neck", | |
| "dewlap", "brisket", "shoulder", "left_flank", "right_flank", "back", | |
| "rump", "tail_head", "udder", "groin", "left_fore_leg", "right_fore_leg", | |
| "left_hind_leg", "right_hind_leg", "left_fore_hoof", "right_fore_hoof", | |
| "left_hind_hoof", "right_hind_hoof", | |
| ) | |
| #: Where feather coverage is reduced, for §19's *"on back and tail"*. The | |
| #: combination the directive prints is a member in its own right, because a | |
| #: single-valued field cannot say two places at once and §19 says two. | |
| _FEATHER_SITES = ( | |
| "head", "neck", "back", "tail", "back_and_tail", "breast", "vent", "wings", | |
| ) | |
| #: Where discharge is, for §19's *"around left eye"*. | |
| _HEAD_SITES = ( | |
| "left_eye", "right_eye", "both_eyes", "nostrils", "sinus", "beak", "comb", | |
| "wattle", | |
| ) | |
| _SCALE = Basis.SCALE | |
| _DIRECTIVE = Basis.DIRECTIVE | |
| _GUARD = Basis.GUARDRAIL | |
| #: What a photograph can resolve, for a count of discrete marks in one close-up. | |
| #: The ceiling is the frame's rather than the body part's, on the same reasoning | |
| #: `cattle_detection` uses for animals: a bound that clipped a badly affected | |
| #: animal would delete the capture that mattered most. | |
| _LESIONS_IN_FRAME = "A count of separate marks in one close-up. The ceiling is " \ | |
| "what a frame can resolve, not what a hoof or a flank " \ | |
| "usually carries — the worst animal on the farm is the one " \ | |
| "this must not refuse." | |
| #: Capability key → what each of its allowed claims may carry. | |
| #: | |
| #: Held as one table rather than inline on 28 output specs for the same reason | |
| #: `_STACK` is: it can be read against `allowed_claims` in a single pass, and | |
| #: `test_dispositions.py` asserts that every claim in the registry appears here | |
| #: exactly once. A claim added to a vocabulary without an entry fails a test | |
| #: instead of quietly becoming unbounded, which is how the last 73 happened. | |
| #: | |
| #: **The bias is deliberately towards declaring a number.** A claim wrongly | |
| #: marked as carrying none refuses a legitimate reading, and a refused reading is | |
| #: invisible to everyone except the farmer who lost it. A claim wrongly given a | |
| #: wide bound refuses less than it should, which a watchdog can find. So a number | |
| #: is declared wherever one is meaningful, and `carries_number = False` is | |
| #: reserved for claims where a figure would be an assertion the directive | |
| #: refuses — a lameness score, a disease probability, an exact physiology. | |
| _QUANTITIES: dict[str, tuple[ClaimQuantity, ...]] = { | |
| # ---- cattle --------------------------------------------------------- | |
| "cattle_detection": ( | |
| _no_number("cattle_detected", | |
| "Whether an animal is in frame. The count is `visible_count`."), | |
| _number("visible_count", "animals", 0, 500, _GUARD, step=1, | |
| why="Animals in one frame, so the ceiling is what a frame can " | |
| "physically hold rather than what a herd can."), | |
| _no_number("subject_framed", | |
| "Whether one animal is framed well enough for the per-animal " | |
| "capabilities. A yes-or-no finding, not a count."), | |
| ), | |
| "cattle_identity": ( | |
| replace( | |
| _no_number("identity_candidate", | |
| "An animal, named. A match score is not a property of " | |
| "the animal and belongs in `confidence`, which the " | |
| "schema already bounds 0 to 1 — §37 is explicit that a " | |
| "bare percentage beside a result is the wrong thing to " | |
| "show."), | |
| free_value_reason="The animal's own name, from this farm's " | |
| "register. §6.4 prints 'This looks like Kofi', " | |
| "and a registry cannot enumerate a farm's " | |
| "animals. Confirmed by the user before it " | |
| "becomes a record.", | |
| ), | |
| _no_number("no_confident_match", | |
| "The absence of a match. There is nothing to count."), | |
| replace( | |
| _no_number("closest_candidate", | |
| "An animal on this farm's register, named but not " | |
| "asserted. It carries no number for the same reason " | |
| "`identity_candidate` carries none, and for one more: a " | |
| "similarity beside a name Animap has just declined to " | |
| "settle on would be read as how nearly it settled. The " | |
| "run's own score travels once, under " | |
| "`top_candidate_similarity`, uncalibrated and named so."), | |
| free_value_reason="The animal's own name, from this farm's " | |
| "register — the same value `identity_candidate` " | |
| "carries, under the type that does not assert. " | |
| "§6.4's 'Also considered', and picking one is a " | |
| "correction rather than a confirmation.", | |
| ), | |
| ), | |
| "cattle_breed": ( | |
| replace( | |
| _no_number("likely_breed", | |
| "A breed name (§6.5). A photograph carries the " | |
| "phenotype, and a number beside a breed would be the " | |
| "pedigree share no camera can see."), | |
| free_value_reason="A breed name. An enum of Nigerian cattle breeds " | |
| "would refuse the animal §6.5 exists to protect " | |
| "— 'Do not force breed when crossbred/uncertain' " | |
| "— by refusing the breed nobody listed instead. " | |
| "Confirmed by the user.", | |
| ), | |
| replace( | |
| _no_number("breed_like_phenotype", | |
| "A phenotype description. A percentage of breed " | |
| "composition is a pedigree claim no photograph " | |
| "carries."), | |
| free_value_reason="The breed a phenotype resembles, for the same " | |
| "reason as `likely_breed`. §6.5's " | |
| "'White-Fulani-like phenotype'.", | |
| ), | |
| _no_number("crossbred_or_uncertain", | |
| "§6.5's first-class answer, and a word. A crossbreeding " | |
| "fraction would be the forced breed §6.5 forbids, written as " | |
| "arithmetic."), | |
| ), | |
| "cattle_sex": ( | |
| _words("likely_sex", ("male", "female"), | |
| "Male or female (§6.6). Two words, which is the whole of what " | |
| "§6.6 asks for."), | |
| _no_number("not_determinable_from_view", | |
| "The honest failure §6.6 asks for."), | |
| ), | |
| "cattle_weight": ( | |
| _number("weight_range_estimate", "kg", 20, 1_200, _GUARD, | |
| why="A newborn calf to an oversized bull. Wide on purpose: §22's " | |
| "whole point is that the estimate is broad, and a bound that " | |
| "clipped a real animal would delete the capture rather than " | |
| "flag it."), | |
| ), | |
| "cattle_bcs": ( | |
| _number("body_condition_band", "bcs_1_5", 1.0, 5.0, _SCALE, step=0.5, | |
| # **§38's coarse band, which this claim could not say.** "Start: | |
| # thin; appropriate; heavy — or broad score band. Later: | |
| # half-point BCS." Declaring only the half-point band answered | |
| # the *later* half of that sentence and refused the *start*, | |
| # which is §38 run backwards and is what §38 is written against. | |
| values=("thin", "appropriate", "heavy"), | |
| why="The 1-to-5 scale, at the half-point granularity §7's own " | |
| "example uses. `step` is what refuses \"BCS 2.63\". §38's " | |
| "three words are the coarse form the same claim may take " | |
| "instead, and the section asks for them first."), | |
| _number("condition_trend", "bcs_1_5_change", -4.0, 4.0, _SCALE, step=0.5, | |
| why="A direction against the previous band, and §7 prints it as " | |
| "words. Where a model puts the arithmetic instead, the " | |
| "difference of two points on a 1-to-5 scale cannot exceed " | |
| "four and cannot be finer than the scale it came from."), | |
| # **A registry gap, closed.** §7's UI prints "Previous: 3.0-3.5" beside | |
| # this scan's band, and until now no claim carried it — so the | |
| # directive's own screen line had no structured field and the quantity | |
| # gate refused it. The previous score is already `optional_inputs`, so | |
| # this restates an input the capability was handed rather than reading | |
| # a second animal off the photograph. | |
| _number("previous_band", "bcs_1_5", 1.0, 5.0, _SCALE, step=0.5, | |
| why="The band the last scan produced, restated for comparison. " | |
| "Same scale and same granularity as the reading itself, " | |
| "because it is a reading itself. §7 prints it."), | |
| ), | |
| "cattle_age_dentition": ( | |
| _number("age_band", "years", 0, 30, _GUARD, step=0.5, | |
| why="Cattle do not reach 30 years. Half years rather than " | |
| "continuous because the teeth resolve five states across " | |
| "five years and nothing finer — a two-decimal age from a " | |
| "tooth photograph is the 0.06-years-RMSE claim its " | |
| "disposition calls label leakage."), | |
| _number("permanent_incisor_count", "permanent_incisors", 0, 8, _SCALE, | |
| step=1, | |
| why="Cattle carry eight permanent incisors, all on the lower " | |
| "jaw. The unit defines the ceiling; a ninth is not a " | |
| "surprising reading, it is a broken one."), | |
| ), | |
| "cattle_gait": ( | |
| _no_number("gait_asymmetry", | |
| "§24's whole point. 'Possible gait asymmetry' has no " | |
| "magnitude, and the number a model reaches for is the " | |
| "'Lameness score 3' the section names as the thing not to " | |
| "output. This is the claim a watchdog laundered a 3 through."), | |
| _no_number("possible_movement_issue", | |
| "A screen, for the same reason. §24 refuses a score, and a " | |
| "score is what a number here would be."), | |
| ), | |
| "cattle_ticks": ( | |
| # **The count stays, and it is a queue rather than a total.** §25 prints | |
| # it, `HUMAN_CONFIRMATION` is set, and every detection is shown for | |
| # confirmation or removal — so the number a farm ends up with is the | |
| # number a person kept, not the number a detector produced. | |
| # | |
| # `experiments/cattle_ticks/` is what makes that framing load-bearing | |
| # rather than decorative. On 24 composite frames holding 322 real ticks, | |
| # with an exemplar bank sharing no photograph with the pasted ticks: | |
| # **precision 0.798, recall 0.258**, F1 0.390, count MAE 9.2 and a bias | |
| # of -9.08. Four detections in five are ticks; roughly three ticks in | |
| # four are missed. That supports a confirmation queue and supports | |
| # nothing a farm subtracts from. | |
| _number("probable_tick_count_sampled", "probable_ticks", 0, 1_000, _GUARD, | |
| step=1, | |
| why="A sampled count across four guided regions, so the ceiling " | |
| "is what those regions can hold rather than a whole animal. " | |
| "§25's own example is 17. The measured support is a " | |
| "confirmation queue and not a tally: precision 0.798 at " | |
| "recall 0.258 on a holdout exemplar bank, so the number " | |
| "under-reports and every entry is shown for a person to " | |
| "keep or drop."), | |
| _words("tick_burden_band", ("none_seen", "low", "moderate", "high"), | |
| "§38's four words — none seen, low, moderate, high. An " | |
| "ordinal index would invite a farm to read a level as a " | |
| "count, which is the conflation §25 forbids. **The band is " | |
| "the weaker of the two claims here, not the safer one**: at " | |
| "recall 0.258 the published arm returned zero detections on " | |
| "two of five real cattle photographs holding 25-45 and " | |
| "70-160 ticks, and 'none seen' is the one wording a farm " | |
| "could act on without confirming anything. §38's word is " | |
| "*seen*, and it has to keep carrying that weight."), | |
| ), | |
| "cattle_wound": ( | |
| _number("visible_wound", "wounds", 0, 500, _GUARD, step=1, | |
| why=_LESIONS_IN_FRAME), | |
| _words("surrounding_swelling", _SEVERITY, | |
| "§9 prints 'Moderate surrounding swelling'. The directive " | |
| "gives no rubric for severity here, so a graded number would " | |
| "be a scale this file invented — §38's broad-before-precise " | |
| "run backwards. Four words is what §9's own wording supports."), | |
| _words("visible_discharge", _PRESENCE, | |
| "§9 prints 'No obvious visible discharge'. Presence, not " | |
| "volume; RGB carries no volume."), | |
| _number("approximate_visible_area", "cm2", 0, 2_000, _GUARD, | |
| why="An area on one animal. The ceiling is a large flank lesion, " | |
| "not a plausible one. §9's own example is 12-16 cm²."), | |
| _number("change_since_last_scan", "cm2", -2_000, 2_000, _GUARD, | |
| why="The difference between two areas, so it is legitimately " | |
| "negative when a wound heals and cannot exceed the larger " | |
| "area in either direction."), | |
| # **§38 lists four things a wound result should start with — visible, | |
| # severity, location, change — and this capability had three of them.** | |
| # `poultry_wound` carried a location and `cattle_wound` did not, which | |
| # was an omission rather than a decision: §19 prints *"Open wound | |
| # visible on left flank"* for a bird and §9 gives the cow the same | |
| # shape, reference marker included. | |
| _words("wound_location", _CATTLE_SITES, | |
| "§38's 'location'. Where the wound is, from a list, because a " | |
| "cow has a finite number of places and a free-text site is a " | |
| "sentence in a field a farmer reads."), | |
| ), | |
| "cattle_skin": ( | |
| _number("nodular_lesions_visible", "lesions", 0, 500, _GUARD, step=1, | |
| why=_LESIONS_IN_FRAME + " §10 itself prints 'Multiple raised " | |
| "nodular lesions visible' and carries no digit, so the " | |
| "count comes from the product rather than the section: the " | |
| "prototype's skin screen shows '12 visible lesions', and " | |
| "the API stores it as `lesion_count`."), | |
| _no_number("abnormal_skin_pattern", | |
| "§10's second preferred output, and a pattern. An extent " | |
| "belongs on a lesion count or an area, neither of which this " | |
| "claim is."), | |
| _no_number("veterinary_review_recommended", | |
| "§10's escalation. A recommendation has no magnitude."), | |
| ), | |
| "cattle_hoof": ( | |
| _number("visible_crack", "cracks", 0, 500, _GUARD, step=1, | |
| why=_LESIONS_IN_FRAME), | |
| _number("visible_lesion", "lesions", 0, 500, _GUARD, step=1, | |
| why=_LESIONS_IN_FRAME), | |
| _words("swelling", _SEVERITY, | |
| "A condition of the foot, in §11's own list. No rubric is given " | |
| "for it and none is invented, so it carries a severity word and " | |
| "no number."), | |
| _words("erosion", _PRESENCE, | |
| "A condition of the sole, not a countable object."), | |
| _words("overgrowth", _PRESENCE, | |
| "A condition of the horn. A length in millimetres would need " | |
| "the reference marker §11 does not ask for."), | |
| _words("normal_or_abnormal", _NORMALITY, | |
| "§11's summary finding. Two states, and they are the two."), | |
| ), | |
| "cattle_feces": ( | |
| _no_number("normal", "§12's six appearance states, and only appearances. " | |
| "None of them is a quantity."), | |
| _no_number("loose", | |
| "One of §12's six appearance states. Consistency is what is " | |
| "visible; a volume or a frequency is not."), | |
| _no_number("watery", | |
| "An appearance, as §12 lists it. Nothing in a single dropping " | |
| "photograph carries a rate or a quantity."), | |
| _no_number("visible_blood", | |
| "Presence, worth surfacing on its own. A proportion would be " | |
| "the strong disease claim §12 refuses from feces alone."), | |
| _no_number("visible_mucus", "Presence, for the same reason as blood."), | |
| _no_number("unusual_colour", | |
| "A colour, as §12 lists it. A number here would be a colour " | |
| "index nobody declared and no farmer could read."), | |
| ), | |
| "cattle_respiratory": ( | |
| _number("respiratory_rate_range", "breaths_per_minute", 5, 200, _GUARD, | |
| step=1, | |
| why="§14's own worked example is 44-50 breaths a minute. The " | |
| "bounds sit far outside it in both directions so that severe " | |
| "distress still reports, while the 100,000 a watchdog " | |
| "validated against the unbounded schema cannot."), | |
| _number("capture_quality", "fraction", 0, 1, _SCALE, | |
| why="§14 asks for the capture quality beside the rate. A share " | |
| "of a capture that was usable cannot exceed the whole."), | |
| ), | |
| # ---- poultry -------------------------------------------------------- | |
| "poultry_count": ( | |
| _number("visible_count", "birds", 0, 50_000, _GUARD, step=1, | |
| why="A visible count from one frame or one pan. The densest " | |
| "measured frame in this file is DFCCNet's ~166 birds; a " | |
| "whole-house pan legitimately sees thousands, so the ceiling " | |
| "sits where a visible count stops being physically possible " | |
| "rather than where it stops being usual. 327.4 birds is not " | |
| "a more careful answer than 327."), | |
| _number("unique_birds_observed_during_scan", "birds", 0, 50_000, _GUARD, | |
| step=1, | |
| why="§6.3's second quantity, over the same pan, so the same " | |
| "ceiling bounds it. It is not the house, which is why " | |
| "`reconciled_flock_population` is a derived claim."), | |
| ), | |
| "poultry_fecal": ( | |
| _no_number("normal", "§13's flock-level wording, over 4-6 samples."), | |
| _no_number("abnormal", "§13's flock-level wording."), | |
| _no_number("gi_health_watch", | |
| "§13's middle band, over 4-6 samples. A figure beside a band " | |
| "is the disease probability the section defers until local " | |
| "validation exists."), | |
| _no_number("elevated_gi_health_risk", | |
| "§13's band. A number here would be the disease probability " | |
| "the section says to avoid until local validation exists."), | |
| _no_number("coccidiosis_associated_visual_pattern", | |
| "§13 permits the pattern named and clearly qualified. A " | |
| "probability beside a disease name is the claim the " | |
| "qualification exists to prevent, and Newcastle's 62.7% " | |
| "recall is why that matters here more than anywhere."), | |
| ), | |
| "poultry_inactive_birds": ( | |
| _number("inactive_candidate", "birds", 0, 10_000, _GUARD, step=1, | |
| why="A review queue from one section scan, not a mortality count " | |
| "for a house. §15's example is 5 birds."), | |
| _number("needs_review", "birds", 0, 10_000, _GUARD, step=1, | |
| why="The same queue, counted the same way."), | |
| ), | |
| "poultry_weight": ( | |
| _number("weight_range_estimate", "kg", 0.02, 10, _GUARD, | |
| why="A day-old chick to an oversized cock or turkey."), | |
| _number("sample_mean", "kg", 0.02, 10, _GUARD, | |
| why="A mean over 10-20 held birds sits inside the same range as " | |
| "any one of them."), | |
| _number("sample_range", "kg", 0.02, 10, _GUARD, | |
| why="An end of the sample's own spread, in the same unit."), | |
| # The same registry gap as `poultry_footpad`'s, from the same shape of | |
| # sentence: §23 says "Sample 10-20 birds" and then asks for statistics | |
| # over them, so the count of birds behind the mean is a figure a farm | |
| # reads and nothing carried it. | |
| _number("birds_sampled", "birds", 0, 10_000, _GUARD, step=1, | |
| why="§23's 'Sample 10-20 birds'. How many held birds the mean " | |
| "and the range are computed over, which is what makes them " | |
| "readable as a sample statistic rather than as a flock " | |
| "figure. **It qualifies a mean and a range, never a " | |
| "spread** — ADR 0023 refuses the spread outright."), | |
| ), | |
| # `poultry_uniformity` has no entry, and the absence is the decision. | |
| # | |
| # It used to declare a coefficient of variation, a sample size and a | |
| # uniformity band. All three are gone because the capability's vocabulary is | |
| # now empty: `experiments/poultry_weight/` measured what a vision weight does | |
| # to a spread, and the answer is that the spread is unrecoverable. ADR 0023 | |
| # carries the argument, and the entry itself carries the numbers. | |
| # | |
| # A bound is a statement that a number is publishable inside it. There is no | |
| # such number here, so declaring one would be the wrong shape of answer. | |
| "poultry_footpad": ( | |
| _number("approximate_grade", "grade_0_4", 0, 4, _SCALE, step=1, | |
| why="§18's 'Approximate grade: 2 / 4'. A whole-number grade on a " | |
| "0-to-4 rubric; 2.4 is not a finer reading of it."), | |
| _number("sampled_prevalence", "percent", 0, 100, _SCALE, | |
| why="§18's own aggregate — '20 birds sampled, 4 moderate or " | |
| "worse, 20%'. A share cannot exceed the whole, and a " | |
| "blanket 0-to-4 ceiling over this vocabulary would refuse " | |
| "the directive's own example."), | |
| # **Two registry gaps, closed.** §18 prints its aggregate as three | |
| # figures — "20 birds sampled", "4 moderate or worse", "20%" — and only | |
| # the third had a claim. The other two were digits with no structured | |
| # field, so the directive's own sentence was refused by the quantity | |
| # gate and the refusal was recorded rather than fixed twice over. | |
| # | |
| # Named `birds_sampled` rather than `sample_size` deliberately: | |
| # `sample_size` is in `FORBIDDEN_CLAIMS` (ADR 0023), where it was put | |
| # because a sample size is only ever shown to qualify a spread and | |
| # `poultry_uniformity` no longer has one. That reasoning is about a | |
| # spread. §18's sample size qualifies a prevalence, which survives. | |
| _number("birds_sampled", "birds", 0, 10_000, _GUARD, step=1, | |
| why="§18's 'Sample 10-20 birds' and its printed '20 birds " | |
| "sampled'. The ceiling is a guardrail: a farm sampling ten " | |
| "thousand birds by hand is not a reading this should " | |
| "refuse, and nothing measures where the real limit is."), | |
| _number("moderate_or_worse", "birds", 0, 10_000, _GUARD, step=1, | |
| why="§18's '4 moderate or worse' — the numerator its 20% is " | |
| "computed from. Counted in birds, over the same sample, so " | |
| "it shares `birds_sampled`'s ceiling."), | |
| ), | |
| "poultry_hock": ( | |
| _number("visible_hock_lesion", "lesions", 0, 500, _GUARD, step=1, | |
| why=_LESIONS_IN_FRAME), | |
| _words("lesion_severity", _SEVERITY, | |
| "§19 prints 'Moderate visible hock lesion' and gives no " | |
| "rubric. §18 does give one for the footpad, and the " | |
| "difference is the point: a grade here would be a scale this " | |
| "file invented, which is what its disposition means by " | |
| "'a severity word is a description and not a score'."), | |
| _words("normal_or_abnormal", _NORMALITY, | |
| "§19's summary finding, and two states. A number " | |
| "between them would be a severity scale §19 declines " | |
| "to give."), | |
| ), | |
| "poultry_feather": ( | |
| _number("reduced_feather_coverage", "percent", 0, 100, _SCALE, | |
| why="Coverage is an area on a surface, which is what a " | |
| "segmentation mask measures directly, and a share of a bird " | |
| "cannot exceed the bird. §19 prints the location rather than " | |
| "a figure; this bounds the figure where a model gives one."), | |
| _words("coverage_location", _FEATHER_SITES, | |
| "§19's 'on back and tail'. A place, not an extent — and the " | |
| "places are a bird's, so they are listed rather than left as a " | |
| "string a model composes."), | |
| _words("normal_or_abnormal", _NORMALITY, | |
| "§19's summary finding, and two states. A number " | |
| "between them would be a severity scale §19 declines " | |
| "to give."), | |
| ), | |
| "poultry_wound": ( | |
| _number("visible_wound", "wounds", 0, 500, _GUARD, step=1, | |
| why=_LESIONS_IN_FRAME), | |
| _words("wound_location", _BIRD_SITES, | |
| "§19's 'on left flank'. A place, not an extent, and a bird has " | |
| "a finite number of them."), | |
| # The other half of §38's four bullets, which this capability was | |
| # missing exactly as `cattle_wound` was missing the location. §9 gives | |
| # the shape and the registry's own note already says §19's bird wound | |
| # is "the same shape as `cattle_wound`". | |
| _words("surrounding_swelling", _SEVERITY, | |
| "§38's 'severity', in §9's own words. No rubric is given for a " | |
| "bird any more than for a cow, so a word and not a grade."), | |
| _words("visible_discharge", _PRESENCE, | |
| "§9's 'No obvious visible discharge', on a bird. Presence; RGB " | |
| "carries no volume."), | |
| _number("approximate_visible_area", "cm2", 0, 500, _GUARD, | |
| why="The same claim as `cattle_wound` on a much smaller animal, " | |
| "so the ceiling is much smaller too."), | |
| _number("change_since_last_scan", "cm2", -500, 500, _GUARD, | |
| why="The difference between two areas, legitimately negative " | |
| "when a wound heals."), | |
| ), | |
| "poultry_eye_head": ( | |
| _words("visible_discharge", _PRESENCE, | |
| "§19's 'Visible discharge around left eye'. Presence. RGB " | |
| "carries no volume, and a graded number would edge towards " | |
| "the Newcastle-adjacent naming this capability exists to " | |
| "avoid."), | |
| _words("discharge_location", _HEAD_SITES, | |
| "§19's 'around left eye'. A place, and a head has few."), | |
| _words("swelling", _SEVERITY, | |
| "Presence and how much of it, with no rubric given for it — so " | |
| "a word rather than a grade."), | |
| _words("normal_or_abnormal", _NORMALITY, | |
| "§19's summary finding, and two states. A number " | |
| "between them would be a severity scale §19 declines " | |
| "to give."), | |
| ), | |
| "poultry_heat_stress": ( | |
| _no_number("no_obvious_visual_signs", | |
| "§17's first band. Three bands are what the section asks " | |
| "for, and it closes by refusing exact physiology."), | |
| _no_number("some_heat_associated_behaviours", | |
| "§17's second band, and §38 gives the same three. A figure " | |
| "here would be a flock rate read off a partial view."), | |
| _no_number("heat_stress_associated_behaviour_elevated", | |
| "§17's third band, and the wording it prints. The house is " | |
| "what a probe measures; this measures the birds."), | |
| # §17 and §38 both give this capability three bands and no figure, and | |
| # §17 closes with "Do not claim exact physiology". A share of the birds | |
| # panting is the number a reasoner reaches for, and it is a flock rate | |
| # read off a partial view — the shape §30 rejects for house population. | |
| # This is the widest judgement in the table and the one to challenge. | |
| _no_number("panting", | |
| "A behaviour §17 lists and prints as a band. A share of birds " | |
| "panting is a flock rate from a partial view, which is the " | |
| "shape §30 rejects for house population."), | |
| _no_number("wing_spreading", "A behaviour, banded (§17)."), | |
| _no_number("reduced_activity", "A behaviour, banded (§17)."), | |
| _no_number("clustering_at_drinkers", "A behaviour, banded (§17)."), | |
| _no_number("abnormal_distribution", "A behaviour, banded (§17)."), | |
| ), | |
| "poultry_litter": ( | |
| _number("loose", "percent_of_scanned_region", 0, 100, _SCALE, | |
| values=_LITTER_BANDS, | |
| why="§16 lists the conditions and the abnormal-area percentage " | |
| "together, and its example reads '18% of scanned region'. A " | |
| "share of what was scanned cannot exceed the whole, whichever " | |
| "condition the share is of."), | |
| _number("caked", "percent_of_scanned_region", 0, 100, _SCALE, | |
| values=_LITTER_BANDS, | |
| why="A share of the scanned region (§16)."), | |
| _number("heavily_soiled", "percent_of_scanned_region", 0, 100, _SCALE, | |
| values=_LITTER_BANDS, | |
| why="A share of the scanned region (§16)."), | |
| _number("wet_looking", "percent_of_scanned_region", 0, 100, _SCALE, | |
| values=_LITTER_BANDS, | |
| why="§16's own worked figure — 'Wet-looking areas: 18% of " | |
| "scanned region'."), | |
| _number("abnormal_area_percentage", "percent_of_scanned_region", 0, 100, | |
| _SCALE, | |
| why="§16 names this output. A share cannot exceed the whole, and " | |
| "it is a share of what was scanned rather than of the house."), | |
| # **An identifier, not a measurement, and it is declared anyway.** This | |
| # said `carries_number = False` on the argument that a name belongs in | |
| # the string `Observation.value` also accepts. A watchdog showed what | |
| # that cost: §16's own printed output is "Worst near drinker line 3", | |
| # the string form is refused by the quantity gate in | |
| # `app/adapters/claims.py` because nothing structured carries the 3, and | |
| # refusing the numeric form as well left the directive's own line | |
| # unpublishable in both. It published before this registry change, so | |
| # that was a regression rather than a tightening. | |
| # | |
| # The cost of declaring it is small and worth naming: `poultry_litter` | |
| # already carries five continuous 0-to-100 claims, so a bounded line | |
| # number widens the window a figure can be laundered through by a few | |
| # integers rather than opening a new one. | |
| _number("worst_area_location", "drinker_line", 0, 200, _GUARD, step=1, | |
| why="§16's 'Worst near drinker line 3'. The number names a " | |
| "line, so this bounds an identifier rather than a " | |
| "measurement — the ceiling is more lines than a house has, " | |
| "because refusing a real one would refuse the directive's " | |
| "own output."), | |
| ), | |
| "poultry_respiratory": ( | |
| # **This carried a count of 0 to 1,000 and now carries no number**, and | |
| # the reason is a measurement rather than a caution. | |
| # | |
| # `experiments/poultry_respiratory/` ran the shipped detector over 6,346 | |
| # clips from two commercial-farm datasets and scored **AUC 0.4141** | |
| # against a chance line of 0.5. It is not merely weak, it runs backwards: | |
| # healthy clips average **6.90 events a minute** and sick clips **1.16**, | |
| # because a spectral-flux onset detector measures activity and a sick | |
| # flock is quiet. No threshold repairs that — `ONSET_K` moves the rate | |
| # and the ordering between the classes is what is wrong. | |
| # | |
| # So the count is not a poor estimate of the events; it is a number whose | |
| # direction is inverted. §26's own printed line — "Cough/sneeze-like | |
| # events detected. Spot respiratory screen only." — carries no digit, so | |
| # refusing the digit costs the directive's wording nothing. | |
| # | |
| # **The claim itself survives**, because the signal does: frozen CLAP | |
| # embeddings on the identical clips, on a node-disjoint split, reach | |
| # 0.5651 against a 0.5182 majority baseline. That is a thin 4.7-point | |
| # margin and it is enough to fail §36's bar for calling the capability | |
| # impossible. The method is wrong; the capability is not. | |
| _no_number("cough_or_sneeze_like_events_detected", | |
| "Whether cough- or sneeze-like events were heard, which is " | |
| "what §26 prints and all it prints. The count is refused " | |
| "because the only implementation scores AUC 0.4141 against " | |
| "chance and fires more often on healthy flocks (6.90 " | |
| "events/min) than on sick ones (1.16) — an inverted number is " | |
| "worse than none, and a farm cannot tell them apart."), | |
| _no_number("spot_respiratory_screen", | |
| "§26's own disclaimer — 'Spot respiratory screen only'. It " | |
| "qualifies the result; it is not one."), | |
| ), | |
| "egg_quality": ( | |
| _number("count", "eggs", 0, 1_000, _GUARD, step=1, | |
| why="Eggs on a photographed tray or trays. A standard tray holds " | |
| "30; the ceiling allows a stack of them."), | |
| _number("shape", "eggs", 0, 1_000, _GUARD, step=1, | |
| why="§20's immediate capabilities are per-tray tallies, so each " | |
| "reports how many of the photographed eggs it applies to and " | |
| "shares `count`'s ceiling."), | |
| _number("obvious_dirt", "eggs", 0, 1_000, _GUARD, step=1, | |
| why="A tally over the same tray (§20)."), | |
| _number("external_discolouration", "eggs", 0, 1_000, _GUARD, step=1, | |
| why="A tally over the same tray (§20)."), | |
| _number("obvious_visible_damage", "eggs", 0, 1_000, _GUARD, step=1, | |
| why="A tally over the same tray (§20). Obvious damage only — a " | |
| "hairline crack needs candling and is forbidden here."), | |
| ), | |
| } | |
| def _ev(phrase: str, source: str, note: str = "") -> EvidenceItem: | |
| return EvidenceItem(phrase=phrase, source=source, note=note) | |
| #: Capability key → every phrase that capability may put in `evidence`. | |
| #: | |
| #: **This is the change ADR 0024 makes, and it is a table rather than a rule.** | |
| #: `evidence` used to be `{"type": "string", "maxLength": 200}`, which is the | |
| #: field every successful attack on this service landed in. It is now an enum, | |
| #: per capability, exactly as `claims` already was — and the argument that it | |
| #: could not be is answered by reading what the directive actually prints. §7's | |
| #: worked output is three observable features from a rubric. §10 prints one | |
| #: sentence, §19 prints four, §11 lists six hoof findings, §12 lists six faecal | |
| #: appearances and §38 lists the bands. Not one of those is prose a model has to | |
| #: compose. | |
| #: | |
| #: ## Three rules the table follows | |
| #: | |
| #: **The directive's wording is kept character for character where it prints | |
| #: one.** §7's *"prominent hooks"* is here as `prominent hooks`, not as | |
| #: `Prominent hooks are visible`. A vocabulary that paraphrases the source it | |
| #: claims to implement is a vocabulary nobody can check against it. | |
| #: | |
| #: **Every finding has its negative.** *"No obvious visible discharge"* is §9's | |
| #: own printed output and it is the shape the whole table needs: a control that | |
| #: can only say the abnormal thing is a control that manufactures abnormal | |
| #: results. Where the directive prints only the positive, the negative is | |
| #: marked `product` and is this file's judgement. | |
| #: | |
| #: **No phrase carries a per-capture number.** §25's 17 ticks, §18's grade of 2 | |
| #: and §6.3's 327 birds all live in `observations[].value`, `range` or | |
| #: `best_estimate`, where `OutputSpec` bounds them on the unit the registry | |
| #: declared. A constant may appear — a rubric's endpoints, §6.3's uncertainty | |
| #: footnote, the seconds §26 asks for — and | |
| #: `tests/test_directive_payloads.py` asserts the exact set of phrases that | |
| #: carry a digit, so adding one costs a diff. | |
| _EVIDENCE: dict[str, tuple[EvidenceItem, ...]] = { | |
| # ---- cattle --------------------------------------------------------- | |
| "cattle_detection": ( | |
| _ev("Cattle detected", "§6.1"), | |
| _ev("No cattle detected in this frame", "product", | |
| note="§6.1 prints only the positive. A detector that cannot report " | |
| "an empty frame reports a cow in every one."), | |
| _ev("Number of cattle visible in this frame", "§6.1"), | |
| _ev("One animal is framed well enough for the per-animal capabilities", | |
| "§6.1"), | |
| _ev("No single animal is framed well enough to assess on its own", | |
| "product"), | |
| ), | |
| "cattle_identity": ( | |
| _ev("This looks like a registered animal", "§6.4", | |
| note="§6.4 prints 'This looks like Kofi'. The name is the farm's, " | |
| "not this registry's, so it rides in the observation's value " | |
| "and the phrase carries the hedge."), | |
| _ev("No confident match against the animals already registered", "§6.4"), | |
| _ev("Matched on the muzzle", "§6.4"), | |
| _ev("Matched on the face", "§6.4"), | |
| _ev("Matched on the side of the body", "§6.4"), | |
| _ev("A candidate for you to confirm, never a record", "§6.4"), | |
| ), | |
| "cattle_breed": ( | |
| _ev("Likely breed, from the visible phenotype", "§6.5"), | |
| _ev("The phenotype resembles a breed without matching it cleanly", | |
| "§6.5", note="§6.5's 'White-Fulani-like phenotype'."), | |
| _ev("Crossbred or uncertain", "§6.5", | |
| note="§6.5 makes this a first-class answer, not a failure."), | |
| _ev("A suggestion for you to confirm or correct", "§6.5"), | |
| ), | |
| "cattle_sex": ( | |
| _ev("Likely male", "§6.6"), | |
| _ev("Likely female", "§6.6"), | |
| _ev("Not determinable from this view", "§6.6"), | |
| _ev("A suggestion for you to confirm or correct", "§6.6"), | |
| ), | |
| "cattle_weight": ( | |
| _ev("Experimental weight estimate", "§22"), | |
| _ev("Estimated from the guided side sweep and the camera's own pose", | |
| "§22"), | |
| _ev("Estimated against the reference marker", "§22", | |
| note="§22's fallback where metric depth is unreliable."), | |
| _ev("The initial range can be wide", "§22"), | |
| _ev("Add a scale weight to verify this", "§22"), | |
| _ev("A broad estimate, never an exact weight", "§30"), | |
| ), | |
| "cattle_bcs": ( | |
| # §7's worked output, verbatim. These three are the reason this change | |
| # is a reading of the directive rather than a departure from it. | |
| _ev("prominent hooks", "§7"), | |
| _ev("visible pins", "§7"), | |
| _ev("limited fat around tail head", "§7"), | |
| _ev("hooks not prominent", "product", | |
| note="The negative of §7's first feature. Without it the rubric can " | |
| "only describe a thin animal."), | |
| _ev("pins not visible", "product"), | |
| _ev("fat cover around the tail head", "product"), | |
| _ev("Condition appears to be declining", "§7"), | |
| _ev("Condition appears to be improving", "product", | |
| note="§7 prints the declining case; a trend that only ever falls is " | |
| "not a trend."), | |
| _ev("Condition appears unchanged", "product"), | |
| # §38's coarse band, which the section asks the product to build | |
| # *before* the half-point one. The words are §38's own. | |
| _ev("Thin", "§38"), | |
| _ev("Appropriate", "§38"), | |
| _ev("Heavy", "§38"), | |
| _ev("Previous band, for comparison", "§7", | |
| note="§7's UI line 'Previous: 3.0-3.5'. The band itself is the " | |
| "`previous_band` observation; this is the words around it."), | |
| ), | |
| "cattle_age_dentition": ( | |
| _ev("Permanent incisors visible", "§8", | |
| note="§8 prints 'Four permanent incisors visible'. The four is " | |
| "`permanent_incisor_count`."), | |
| _ev("Deciduous teeth still present", "§8"), | |
| _ev("Wear on the incisors", "§8"), | |
| _ev("Estimated age band from the dentition rule table", "§8"), | |
| _ev("A known birth date was supplied and is used instead", "§8"), | |
| _ev("An age band, never an age to the month", "§8"), | |
| ), | |
| "cattle_gait": ( | |
| _ev("Possible gait asymmetry", "§24"), | |
| _ev("No obvious gait asymmetry", "product", | |
| note="§24 prints only the positive."), | |
| _ev("Stride timing is uneven between left and right", "§24"), | |
| _ev("Hoof trajectories differ between left and right", "§24"), | |
| _ev("Back-line movement while walking", "§24"), | |
| _ev("Head movement while walking", "§24"), | |
| _ev("Stance duration differs between limbs", "§24"), | |
| _ev("A screen, not a lameness score", "§24"), | |
| ), | |
| "cattle_ticks": ( | |
| _ev("Probable ticks visible across the sampled regions", "§25"), | |
| _ev("None seen in the sampled regions", "§38", | |
| note="§38's word is *seen*. `experiments/cattle_ticks/` measured " | |
| "recall at 0.258, so this phrase carries the weight of a miss " | |
| "and has to keep saying 'seen' rather than 'none'."), | |
| _ev("Low tick burden", "§38"), | |
| _ev("Moderate tick burden", "§38"), | |
| _ev("High tick burden", "§38"), | |
| _ev("Sampled burden across the guided regions, not a whole-animal count", | |
| "§25"), | |
| _ev("Each detection is shown for you to confirm or remove", "§25"), | |
| ), | |
| "cattle_wound": ( | |
| _ev("Open wound visible", "§9"), | |
| _ev("No open wound visible", "product"), | |
| _ev("Moderate surrounding swelling", "§9"), | |
| _ev("No obvious surrounding swelling", "product", | |
| note="§9 prints the moderate case and the negative discharge; this " | |
| "is the missing corner of the pair."), | |
| _ev("No obvious visible discharge", "§9"), | |
| _ev("Visible discharge", "§9"), | |
| _ev("Approximate visible area, measured against the reference marker", | |
| "§9"), | |
| _ev("Smaller than at the last scan", "§9"), | |
| _ev("Larger than at the last scan", "§9"), | |
| _ev("Unchanged since the last scan", "§9"), | |
| ), | |
| "cattle_skin": ( | |
| _ev("Multiple raised nodular lesions visible", "§10"), | |
| _ev("Abnormal skin pattern", "§10"), | |
| _ev("Veterinary review recommended", "§10"), | |
| _ev("No abnormal skin pattern visible", "product"), | |
| _ev("Lesions are raised and nodular", "§10"), | |
| _ev("Lesions are spread over more than one region", "product", | |
| note="§10's 'scan additional lesions' action needs a phrase that " | |
| "says why."), | |
| ), | |
| "cattle_hoof": ( | |
| # §11's own output list, one phrase each. | |
| _ev("Visible crack", "§11"), | |
| _ev("Visible lesion", "§11"), | |
| _ev("Swelling", "§11"), | |
| _ev("Erosion", "§11"), | |
| _ev("Overgrowth", "§11"), | |
| _ev("Appears normal", "§11"), | |
| _ev("Appears abnormal", "§11"), | |
| _ev("What is visible on the sole, not a named condition", "§11"), | |
| ), | |
| "cattle_feces": ( | |
| # §12's six initial outputs, verbatim. | |
| _ev("Normal", "§12"), | |
| _ev("Loose", "§12"), | |
| _ev("Watery", "§12"), | |
| _ev("Visible blood", "§12"), | |
| _ev("Visible mucus", "§12"), | |
| _ev("Unusual colour", "§12"), | |
| _ev("An appearance, not a diagnosis", "§12"), | |
| ), | |
| "cattle_respiratory": ( | |
| _ev("Estimated respiratory rate from flank movement", "§14"), | |
| _ev("The flank was held in frame for the whole recording", "§14"), | |
| _ev("Capture quality for this recording", "§14"), | |
| _ev("A spot measurement, not continuous cough surveillance", "§14"), | |
| ), | |
| # ---- poultry -------------------------------------------------------- | |
| "poultry_count": ( | |
| _ev("Birds visible in this frame", "§6.3"), | |
| _ev("Unique birds observed during the scan", "§6.3"), | |
| _ev("A visible count, never the whole flock", "§6.3"), | |
| _ev("Experimental · dense scenes can undercount", "§37"), | |
| # **A digit in a phrase, and one of only three in the registry.** §6.3 | |
| # prints this footnote with its numbers in it, and the quantity gate | |
| # refused it for as long as `evidence` was free text, because | |
| # `poultry_count` declares no claim in percent and no capture produces | |
| # a 10 or a 20. They are not a reading — they are a product-uncertainty | |
| # constant, which is exactly the kind of number an enumerated phrase can | |
| # hold safely: a model cannot choose the digits without choosing the | |
| # sentence, and a person wrote the sentence. | |
| # | |
| # §6.3's own next line is *"Use measured benchmark when available"*, so | |
| # this phrase has an expiry. When `experiments/poultry_count/` produces | |
| # a farm-specific figure, this is the string that has to change. | |
| _ev("Experimental · dense scenes may vary by approximately 10-20% " | |
| "until farm-specific benchmarks are complete", "§6.3", | |
| note="Replace with the measured benchmark when one exists — §6.3 " | |
| "says so in the line under it."), | |
| ), | |
| "poultry_fecal": ( | |
| _ev("Normal", "§13"), | |
| _ev("Abnormal", "§13"), | |
| _ev("GI-health Watch", "§13"), | |
| _ev("Elevated GI-health risk", "§13"), | |
| # §13's own words, and the qualification is part of the phrase rather | |
| # than left to the model: "coccidiosis-associated visual pattern only | |
| # when appropriate and clearly qualified". | |
| _ev("Coccidiosis-associated visual pattern", "§13"), | |
| _ev("A flock-level reading over several samples, not one dropping", | |
| "§13"), | |
| ), | |
| "poultry_inactive_birds": ( | |
| _ev("Birds needing review", "§15"), | |
| _ev("No inactive birds seen in this scan", "product"), | |
| _ev("Inactivity measured across the scan, not from one frame", "§15"), | |
| _ev("Each candidate is for a person to answer", "§15", | |
| note="§15's four buttons are `confirmation_options`; naming them " | |
| "here would put the word *dead* into a phrase a model can " | |
| "choose, which is the claim §30 rejects."), | |
| ), | |
| "poultry_weight": ( | |
| _ev("Sample-bird weight estimate from geometry", "§23"), | |
| _ev("Average over the sampled birds", "§23"), | |
| _ev("Range across the sampled birds", "§23"), | |
| _ev("Each bird was held and photographed on its own", "§23"), | |
| _ev("A sample statistic, not every bird in the flock", "§23"), | |
| _ev("Add a scale weight to verify this", "§22", | |
| note="§22's confirmation, which §23's workflow shares."), | |
| ), | |
| "poultry_footpad": ( | |
| _ev("Approximate grade on the 0-4 rubric", "§18", | |
| note="§18 prints 'Approximate grade: 2 / 4'. The 2 is the " | |
| "observation; the 0 and the 4 are the rubric's own ends and " | |
| "are a constant a person wrote here."), | |
| _ev("Birds sampled", "§18"), | |
| _ev("Moderate or worse", "§18"), | |
| _ev("Share of the sampled birds that are moderate or worse", "§18"), | |
| _ev("A farm sample, not a whole-flock grade", "§18"), | |
| _ev("Correct the grade if it looks wrong", "§18"), | |
| ), | |
| "poultry_hock": ( | |
| _ev("Moderate visible hock lesion", "§19"), | |
| _ev("Visible hock lesion", "§19"), | |
| _ev("No visible hock lesion", "product"), | |
| _ev("Appears normal", "§19"), | |
| _ev("Appears abnormal", "§19"), | |
| _ev("One bird, described, not a flock rate", "§13", | |
| note="§19 gives no sampling rule; §13's principle that one sample " | |
| "does not diagnose a flock is what this says out loud."), | |
| ), | |
| "poultry_feather": ( | |
| _ev("Reduced feather coverage on back and tail", "§19"), | |
| _ev("Reduced feather coverage", "§19"), | |
| _ev("Feather coverage appears normal", "product"), | |
| _ev("Share of the visible surface with reduced coverage", "§19"), | |
| _ev("Appears abnormal", "§19"), | |
| _ev("One bird, described, not a flock rate", "§13"), | |
| ), | |
| "poultry_wound": ( | |
| _ev("Open wound visible", "§19"), | |
| _ev("No open wound visible", "product"), | |
| # §38 asks a wound result for visible, severity, location and change. | |
| # §19 prints the first and the third for a bird; §9 gives the wording | |
| # for the second, and the registry already says this capability is the | |
| # same shape as `cattle_wound`. | |
| _ev("Moderate surrounding swelling", "§9"), | |
| _ev("No obvious surrounding swelling", "product"), | |
| _ev("No obvious visible discharge", "§9"), | |
| _ev("Visible discharge", "§9"), | |
| _ev("Approximate visible area, measured against the reference marker", | |
| "§9", note="§19 gives the wound the same shape §9 gives it on " | |
| "cattle, reference marker included."), | |
| _ev("Smaller than at the last scan", "§9"), | |
| _ev("Larger than at the last scan", "§9"), | |
| _ev("Unchanged since the last scan", "§9"), | |
| _ev("One bird, described, not a flock rate", "§13"), | |
| ), | |
| "poultry_eye_head": ( | |
| _ev("Visible discharge around the eye", "§19"), | |
| _ev("No visible discharge around the eye", "product"), | |
| _ev("Swelling around the head", "§19"), | |
| _ev("Appears normal", "§19"), | |
| _ev("Appears abnormal", "§19"), | |
| _ev("One bird, described, not a flock rate", "§13"), | |
| ), | |
| "poultry_heat_stress": ( | |
| # §38's three bands, and §17's five behaviours. | |
| _ev("No obvious visual signs", "§38"), | |
| _ev("Some heat-associated behaviours", "§38"), | |
| _ev("Heat-stress-associated behaviour elevated", "§17"), | |
| _ev("Panting", "§17"), | |
| _ev("Wing spreading", "§17"), | |
| _ev("Reduced activity", "§17"), | |
| _ev("Clustering at drinkers", "§17"), | |
| _ev("Abnormal distribution across the floor", "§17"), | |
| _ev("A behaviour screen — this does not measure the birds themselves", | |
| "§17", note="§17 closes with 'Do not claim exact physiology'."), | |
| ), | |
| "poultry_litter": ( | |
| # §16's own output list. | |
| _ev("Loose", "§16"), | |
| _ev("Caked", "§16"), | |
| _ev("Heavily soiled", "§16"), | |
| _ev("Wet-looking", "§16"), | |
| _ev("Share of the scanned region that is abnormal", "§16"), | |
| _ev("Worst near a drinker line", "§16", | |
| note="§16 prints 'Worst near drinker line 3'. The 3 names a line " | |
| "and rides in `worst_area_location`."), | |
| _ev("Litter condition, never a moisture percentage", "§16"), | |
| ), | |
| "poultry_respiratory": ( | |
| _ev("Cough/sneeze-like events detected", "§26"), | |
| _ev("No cough- or sneeze-like events detected", "product"), | |
| _ev("Spot respiratory screen only", "§26"), | |
| ), | |
| "egg_quality": ( | |
| # §20's immediate capabilities. | |
| _ev("Eggs counted on the tray", "§20"), | |
| _ev("Abnormal shape", "§20"), | |
| _ev("Obvious dirt", "§20"), | |
| _ev("External discolouration", "§20"), | |
| _ev("Obvious visible damage", "§20"), | |
| _ev("Nothing obviously wrong with the eggs in this tray", "product"), | |
| _ev("Fine cracks need candling and are not assessed here", "§20"), | |
| ), | |
| # `poultry_uniformity` has no entry, and the absence is the decision — the | |
| # same decision `_QUANTITIES` records for it. Its `allowed_claims` is empty | |
| # (ADR 0023), so `schema_for` generates an enum of zero claims; an evidence | |
| # vocabulary would be the one place left to write the refused figure in | |
| # words, which is precisely what ADR 0023's watchdog did through free text. | |
| } | |
| def _cap(key, species, save_target, state, requirements, acquisition, **kw) -> Capability: | |
| """Register a capability. | |
| Connectivity defaults to `required`, which inverts the old default. Every | |
| capability in the directive's stack opens with a hosted model — SAM 3.1, | |
| DINOv3, CountGD, VGGT, a multimodal reasoner — so the phone contributes | |
| nothing to the answer and *"Saves now, analysis runs when you reconnect"* is | |
| what a farmer should be told. The two capabilities that really do compute | |
| part of their answer on the phone say `deferred` explicitly, which is | |
| exactly the ratio a default should have. | |
| The per-claim quantities are spliced in here rather than written on each | |
| `OutputSpec`, so `_QUANTITIES` stays readable as one table against | |
| `allowed_claims`. A capability missing from it keeps the empty tuple, which | |
| `bounds_for` falls back from and a test refuses. | |
| """ | |
| quantities = _QUANTITIES.get(key, ()) | |
| if quantities: | |
| acquisition = replace( | |
| acquisition, output=replace(acquisition.output, quantities=quantities) | |
| ) | |
| evidence = _EVIDENCE.get(key, ()) | |
| # **A capability that may claim nothing may say nothing.** `poultry_uniformity` | |
| # declares no claims (ADR 0023), and giving it capture limits would leave one | |
| # array of reader-facing strings on a capability whose whole point is that it | |
| # publishes none. The shared limits are earned by having a vocabulary. | |
| limits = ( | |
| _SHARED_LIMITS | |
| + tuple( | |
| EvidenceItem(_LIMIT_PHRASES[condition], "reject_if") | |
| for condition in acquisition.reject_if | |
| if condition in _LIMIT_PHRASES | |
| ) | |
| if evidence else () | |
| ) | |
| acquisition = replace( | |
| acquisition, allowed_evidence=evidence, allowed_limits=limits | |
| ) | |
| return Capability( | |
| key=key, | |
| species=species, | |
| state=state, | |
| requirements=frozenset(requirements), | |
| connectivity=kw.pop("connectivity", Connectivity.REQUIRED), | |
| save_target=save_target, | |
| acquisition=acquisition, | |
| model_stack=_STACK.get(key, ()), | |
| **kw, | |
| ) | |
| _EXPERIMENTAL = CapabilityState.EXPERIMENTAL | |
| _CONFIRM = Requirement.HUMAN_CONFIRMATION | |
| _GUIDED = Requirement.GUIDED_CAPTURE | |
| #: The registry. 28 capabilities, classified against directive §6 to §30. | |
| #: | |
| #: It was 21 until §31's Group A was checked item by item and four of its twenty | |
| #: entries turned out to have no key at all — breed (§6.5), sex (§6.6), cattle | |
| #: feces (§12), and §19's four visual observations, which are four keys rather | |
| #: than one. Their absence cost more than four features: three claims the | |
| #: directive rejects had no capability to hang on, so `FORBIDDEN_CLAIMS` never | |
| #: saw them. | |
| #: | |
| #: The section reference on each entry is the directive's own verdict for it. | |
| #: Where the directive and the earlier measurement disagreed, the directive | |
| #: decides the classification and the measurement becomes the stated uncertainty | |
| #: in `app/dispositions.py` — which is what the founder asked for and is also | |
| #: the only reading that survives §36. | |
| REGISTRY: dict[str, Capability] = { | |
| c.key: c | |
| for c in [ | |
| # ---- cattle ----------------------------------------------------- | |
| # §6.1 "Build now". Runs today, and stays experimental rather than | |
| # production because §37 separates measured accuracy from a demo: the | |
| # only measurement is 31 frames labelled by one non-expert (ADR 0018). | |
| _cap("cattle_detection", "cattle", "animal", _EXPERIMENTAL, (), | |
| AcquisitionProtocol( | |
| protocol="single_frame", | |
| modality=(Modality.RGB_PHOTO,), | |
| # guardrail. Animals in one frame, so the ceiling is what a | |
| # frame can physically hold, not what a herd can. | |
| output=OutputSpec(type="detection", unit="animals", | |
| plausible_min=0, plausible_max=500, step=1, | |
| # `cattle_detected` and `subject_framed` are | |
| # yes-or-no findings, not counts of animals. | |
| measured_claims=("visible_count",)), | |
| required_views=("whole_animal",), | |
| reject_if=_GATE, | |
| allowed_claims=("cattle_detected", "visible_count", "subject_framed"), | |
| forbidden_claims=("herd_size",), | |
| ), | |
| output_kind="measurement", | |
| # `deferred`, not `required`: the Android app ships a MediaPipe | |
| # COCO detector over `efficientdet_lite0.tflite` that answers | |
| # "cattle detected" offline | |
| # (`apps/android/.../capture/SubjectDetector.kt`). The service's | |
| # YOLOX-m produces the count. Part of the answer really is on the | |
| # phone, which is what this tier means. | |
| connectivity=Connectivity.DEFERRED, | |
| on_device_candidate=True, | |
| model_provider="yolox-m-coco", model_version="0.1.1rc0", | |
| geography_validated=(UNVALIDATED_GEOGRAPHY,), | |
| notes="Counts the cattle visible in one frame, and reports whether " | |
| "a single animal is framed well enough for the per-animal " | |
| "capabilities. Never the herd size."), | |
| # §6.4 "Build immediately — high priority". MegaDescriptor or DINOv3 | |
| # frozen embeddings, nearest neighbour, and the user confirms. No custom | |
| # muzzle model is waited for. | |
| _cap("cattle_identity", "cattle", "animal", _EXPERIMENTAL, (_GUIDED, _CONFIRM), | |
| AcquisitionProtocol( | |
| protocol="enrolment_and_match", | |
| modality=(Modality.RGB_PHOTO,), | |
| output=OutputSpec(type="identity_candidates"), | |
| required_views=("front_face", "left_face", "right_face", | |
| "muzzle", "side_body"), | |
| optional_inputs=("ear_tag_photo",), | |
| confirmation_options=("confirm", "not_this_animal", | |
| "choose_another_animal", "register_new_animal"), | |
| reject_if=_GATE + ("muzzle_not_visible",), | |
| # **`closest_candidate` is here because the refusal path is the | |
| # common path.** At the measured operating point three quarters | |
| # of correct matches are refused, so *"not a confident match, | |
| # and these are the nearest on your register"* is what a farm | |
| # sees most days — and `Claims.kt::claimOf` denies by default, | |
| # so a type absent from this tuple is refused as `NOT_ALLOWED` | |
| # and the names render as an empty list. Omitting it discarded | |
| # both the answer and `evidence_correction`'s highest-value | |
| # training signal, which is a person picking rank 2. | |
| # | |
| # It is a ranked list and never a settled name. The two are | |
| # separate types precisely so the device does not have to | |
| # decide which sentence to show from a field that does not say; | |
| # `deploy/semantics/cattle_identity.json` writes one sentence | |
| # for each, and `identity_without_confirmation` below still | |
| # forbids turning either into a record without a person. | |
| allowed_claims=("identity_candidate", "no_confident_match", | |
| "closest_candidate"), | |
| forbidden_claims=("identity_without_confirmation",), | |
| ), | |
| # Named from `models/cattle_identity/model_card.json`, whose | |
| # `model_id` and `version` these two must equal — the release row | |
| # records the same pair, and a result that cannot be matched back to | |
| # an artefact is not evidence (`app/schemas.py`). | |
| # | |
| # **Measured before it was named.** Run `8db9e0bd1b30`, | |
| # `experiments/cattle_identity/metrics.json`: 169 enrolled animals | |
| # from the muzzle268 database, closed-set top-1 0.9772, top-3 0.9937, | |
| # MRR 0.9853, against a 0.005917 chance rate. | |
| model_provider="dinov3-vits16-onnx", model_version="1", | |
| # **The open-set result is why this is `experimental` and why the | |
| # sentinel is here rather than a country code.** With no threshold | |
| # the matcher names an unenrolled animal 100% of the time, because | |
| # every query has a nearest neighbour; the similarity cutoff that | |
| # admits no impostor accepts 24.08% of the correct matches. The | |
| # measurement is on US beef breeds — no Nigerian and no zebu animal | |
| # has been through it — so `may_be_promoted_to_production` stays | |
| # False mechanically rather than by anybody remembering. | |
| geography_validated=(UNVALIDATED_GEOGRAPHY,), | |
| notes="Directive §6.4. Shows 'This looks like Kofi' and asks. An " | |
| "unconfirmed match is a candidate, never a record."), | |
| # §6.5 "Build now as Human Confirmation". A hosted multimodal model plus | |
| # DINOv3 reference retrieval, and the directive is explicit that a | |
| # crossbred animal must be allowed to stay crossbred. | |
| _cap("cattle_breed", "cattle", "animal", _EXPERIMENTAL, (_CONFIRM,), | |
| AcquisitionProtocol( | |
| protocol="whole_animal_side", | |
| modality=(Modality.RGB_PHOTO,), | |
| output=OutputSpec(type="breed_suggestion"), | |
| required_views=("side",), | |
| optional_inputs=("head", "dam_breed", "sire_breed"), | |
| confirmation_options=("confirm", "correct", "crossbred", | |
| "not_sure"), | |
| reject_if=_GATE + ("animal_heavily_occluded", "move_closer"), | |
| allowed_claims=("likely_breed", "breed_like_phenotype", | |
| "crossbred_or_uncertain"), | |
| forbidden_claims=("forced_breed_when_crossbred",), | |
| ), | |
| # Answered by a hosted visual reasoner, not a file on disk. | |
| # `providers.HostedModelProvider` says why the pair is stable | |
| # here while the model that actually ran travels on the result. | |
| model_provider="hosted-multimodal", model_version="1", | |
| notes="Directive §6.5. 'Likely White Fulani', or " | |
| "'White-Fulani-like phenotype' where the animal does not sit " | |
| "cleanly in one breed. **Not forcing a breed is a required " | |
| "output, not a failure** — `crossbred_or_uncertain` is a " | |
| "first-class answer and the user confirms either way."), | |
| # §6.6 "Build now as Human Confirmation". The shortest entry in the | |
| # directive and the one with the sharpest workflow constraint. | |
| _cap("cattle_sex", "cattle", "animal", _EXPERIMENTAL, (_CONFIRM,), | |
| AcquisitionProtocol( | |
| protocol="whole_animal_side", | |
| modality=(Modality.RGB_PHOTO,), | |
| output=OutputSpec(type="sex_suggestion"), | |
| required_views=("side",), | |
| optional_inputs=("rear_view",), | |
| confirmation_options=("confirm", "correct", "not_sure"), | |
| # §6.6: "Do not make this a blocker for registration." Declared | |
| # rather than left to whoever builds the form, because a | |
| # capability that quietly gates registration is discovered by a | |
| # farmer standing in a paddock with an unregistered animal. | |
| never_blocks=("animal_registration",), | |
| reject_if=_GATE + ("animal_heavily_occluded", "wrong_pose"), | |
| allowed_claims=("likely_sex", "not_determinable_from_view"), | |
| forbidden_claims=("sex_without_confirmation",), | |
| ), | |
| # Answered by a hosted visual reasoner, not a file on disk. | |
| # `providers.HostedModelProvider` says why the pair is stable | |
| # here while the model that actually ran travels on the result. | |
| model_provider="hosted-multimodal", model_version="1", | |
| notes="Directive §6.6. 'Likely male', confirmed by the user, and " | |
| "never in the way of registering an animal."), | |
| # §22 "Engineering spike → Experimental". The §34 schema example is this | |
| # capability, and the fields below follow it. | |
| _cap("cattle_weight", "cattle", "animal", _EXPERIMENTAL, (_GUIDED, _CONFIRM), | |
| AcquisitionProtocol( | |
| protocol="guided_side_sweep", | |
| modality=(Modality.RGB_VIDEO, Modality.CAMERA_POSE, | |
| Modality.DEPTH_OPTIONAL), | |
| # guardrail. A newborn calf to an oversized bull. Wide on | |
| # purpose: §22's whole point is that the estimate is broad, and | |
| # a bound that clipped a real animal would delete the capture | |
| # rather than flag it. | |
| output=OutputSpec(type="weight_estimate", unit="kg", show_range=True, | |
| plausible_min=20, plausible_max=1_200, | |
| measured_claims=("weight_range_estimate",)), | |
| minimum_capture_seconds=2, | |
| preferred_capture_seconds=4, | |
| required_views=("side",), | |
| optional_inputs=("arcore_depth", "reference_marker", "rear_view"), | |
| confirmation_options=("add_scale_weight", "correct", "retake"), | |
| reject_if=_GATE + ("animal_heavily_occluded", "insufficient_geometry", | |
| "wrong_pose"), | |
| allowed_claims=("weight_range_estimate",), | |
| forbidden_claims=("exact_weight_from_single_photo",), | |
| ), | |
| output_kind="measurement", | |
| notes="Directive §22. A guided sweep with camera pose is not an " | |
| "arbitrary photograph, and the range is allowed to be wide. " | |
| "A scale reading outranks it and is what verifies it."), | |
| # §7. The directive rejects the previous 'not viable' outright: human | |
| # disagreement means a broader band and a trend, not abandonment. | |
| _cap("cattle_bcs", "cattle", "animal", _EXPERIMENTAL, (_GUIDED, _CONFIRM), | |
| AcquisitionProtocol( | |
| protocol="side_and_rear_quarter", | |
| modality=(Modality.RGB_PHOTO,), | |
| # scale. The 1-to-5 BCS scale, at the half-point granularity | |
| # §7's own example uses (`2.5`, range `[2.5, 3.0]`). This is the | |
| # bound `BCS_SCHEMA` was hand-written to carry and every other | |
| # capability lacked; `step` is what refuses "BCS 2.63". | |
| output=OutputSpec(type="body_condition_band", unit="bcs_1_5", | |
| show_range=True, | |
| plausible_min=1.0, plausible_max=5.0, step=0.5, | |
| # `condition_trend` is a direction against a | |
| # previous band, not a point on the scale. | |
| measured_claims=("body_condition_band",)), | |
| required_views=("side", "rear_quarter"), | |
| optional_inputs=("previous_score",), | |
| confirmation_options=("looks_right", "correct_score", "retake"), | |
| reject_if=_GATE + ("hooks_and_pins_not_visible", "animal_heavily_occluded"), | |
| allowed_claims=("body_condition_band", "condition_trend", | |
| "previous_band"), | |
| forbidden_claims=("bcs_point_score",), | |
| ), | |
| # Answered by a hosted visual reasoner, not a file on disk. | |
| # `providers.HostedModelProvider` says why the pair is stable | |
| # here while the model that actually ran travels on the result. | |
| model_provider="hosted-multimodal", model_version="1", | |
| notes="Directive §7. '2.5-3.0' with 'Looks right / Correct score', " | |
| "and 'Condition appears to be declining' against the previous " | |
| "band. Never 'BCS 2.63'."), | |
| # §8. An age band, from a deterministic rule table over what the visual | |
| # model can see. Not a chronological age. | |
| _cap("cattle_age_dentition", "cattle", "animal", _EXPERIMENTAL, (_GUIDED, _CONFIRM), | |
| AcquisitionProtocol( | |
| protocol="lower_incisor_close_up", | |
| modality=(Modality.RGB_PHOTO,), | |
| # guardrail. Cattle do not reach 30 years. `step` is 0.5 rather | |
| # than continuous because the disposition is explicit that the | |
| # teeth resolve five states across five years and nothing | |
| # finer — a two-decimal age from a tooth photograph is the | |
| # 0.06-years-RMSE claim that entry calls label leakage. | |
| output=OutputSpec(type="age_band", unit="years", show_range=True, | |
| plausible_min=0, plausible_max=30, step=0.5, | |
| # `permanent_incisor_count` counts teeth, of | |
| # which cattle have eight, not years. | |
| measured_claims=("age_band",)), | |
| required_views=("lower_front_teeth",), | |
| optional_inputs=("known_birth_date", "breed", "sex"), | |
| confirmation_options=("known_dob", "correct", "retake"), | |
| reject_if=_GATE + ("move_closer", "teeth_obscured", "wrong_angle"), | |
| allowed_claims=("age_band", "permanent_incisor_count"), | |
| forbidden_claims=("exact_age_from_teeth",), | |
| ), | |
| # Answered by a hosted visual reasoner, not a file on disk. | |
| # `providers.HostedModelProvider` says why the pair is stable | |
| # here while the model that actually ran travels on the result. | |
| model_provider="hosted-multimodal", model_version="1", | |
| notes="Directive §8. 'Estimated age: 3-4 years. Four permanent " | |
| "incisors visible.' A known birth date always wins."), | |
| # §24 "Engineering spike / Experimental". SuperAnimal-Quadruped pose plus | |
| # motion heuristics; the §34 example gives this capability's claim lists. | |
| _cap("cattle_gait", "cattle", "animal", _EXPERIMENTAL, (_GUIDED,), | |
| AcquisitionProtocol( | |
| protocol="side_walk", | |
| modality=(Modality.RGB_VIDEO,), | |
| output=OutputSpec(type="gait_screen"), | |
| minimum_capture_seconds=5, | |
| preferred_capture_seconds=10, | |
| minimum_distance_m=5.0, | |
| required_views=("side",), | |
| optional_inputs=("rear_view",), | |
| reject_if=_GATE + ("animal_not_walking", "animal_heavily_occluded", | |
| "wrong_pose"), | |
| allowed_claims=("gait_asymmetry", "possible_movement_issue"), | |
| forbidden_claims=("definitive_hoof_disease", "lameness_score"), | |
| ), | |
| notes="Directive §24. 'Possible gait asymmetry', not 'lameness " | |
| "score 3 caused by left rear hoof disease'."), | |
| # §25 "Engineering spike / Human Confirmation". Sampled burden across | |
| # four guided regions, with the detections shown for removal. | |
| _cap("cattle_ticks", "cattle", "animal", _EXPERIMENTAL, (_GUIDED, _CONFIRM), | |
| AcquisitionProtocol( | |
| protocol="guided_region_sequence", | |
| modality=(Modality.RGB_PHOTO,), | |
| # guardrail. A sampled count across four guided regions, so the | |
| # ceiling is what those regions can hold, not a whole animal. | |
| output=OutputSpec(type="tick_burden", unit="probable_ticks", | |
| plausible_min=0, plausible_max=1_000, step=1, | |
| # `tick_burden_band` is none/low/moderate/high. | |
| measured_claims=("probable_tick_count_sampled",)), | |
| required_views=("ears", "neck_dewlap", "tail", "groin_udder"), | |
| optional_inputs=("macro_lens", "zoom"), | |
| confirmation_options=("confirm_detection", "remove_detection"), | |
| reject_if=_GATE + ("move_closer", "region_not_visible"), | |
| allowed_claims=("probable_tick_count_sampled", "tick_burden_band"), | |
| forbidden_claims=("total_body_tick_count",), | |
| ), | |
| notes="Directive §25 and §38. '17 probable ticks visible across " | |
| "sampled regions', plus a none/low/moderate/high band. " | |
| "Sampled burden, never a whole-animal count."), | |
| # §9 "Experimental". Observations and, with a marker in frame, an area. | |
| _cap("cattle_wound", "cattle", "animal", _EXPERIMENTAL, (), | |
| AcquisitionProtocol( | |
| protocol="lesion_close_up", | |
| modality=(Modality.RGB_PHOTO,), | |
| # guardrail. An area on one animal, in cm². The upper bound is | |
| # a large flank lesion, not a plausible one. | |
| output=OutputSpec(type="wound_observation", unit="cm2", | |
| show_range=True, | |
| plausible_min=0, plausible_max=2_000, | |
| # Not `change_since_last_scan`: a healing wound | |
| # shrinks, so that number is legitimately | |
| # negative and this minimum would delete it. | |
| measured_claims=("approximate_visible_area",)), | |
| required_views=("wound",), | |
| optional_inputs=("reference_marker", "previous_capture"), | |
| escalation="veterinary_review", | |
| reject_if=_GATE + ("move_closer",), | |
| allowed_claims=("visible_wound", "surrounding_swelling", | |
| "visible_discharge", "approximate_visible_area", | |
| "change_since_last_scan", "wound_location"), | |
| forbidden_claims=("wound_etiology",), | |
| ), | |
| # Answered by a hosted visual reasoner, not a file on disk. | |
| # `providers.HostedModelProvider` says why the pair is stable | |
| # here while the model that actually ran travels on the result. | |
| model_provider="hosted-multimodal", model_version="1", | |
| notes="Directive §9. Describes what is visible and, where a " | |
| "reference marker is in frame, roughly how large. Follow-up " | |
| "scans compare. Never names a cause."), | |
| # §10 "Experimental / escalation required". The legal exposure changes | |
| # the wording and the workflow, not the computer vision. | |
| _cap("cattle_skin", "cattle", "animal", _EXPERIMENTAL, (), | |
| AcquisitionProtocol( | |
| protocol="lesion_close_up", | |
| modality=(Modality.RGB_PHOTO,), | |
| output=OutputSpec(type="skin_observation"), | |
| required_views=("affected_area",), | |
| optional_inputs=("whole_animal", "additional_lesions"), | |
| escalation="veterinary_review", | |
| reject_if=_GATE + ("move_closer",), | |
| allowed_claims=("nodular_lesions_visible", "abnormal_skin_pattern", | |
| "veterinary_review_recommended"), | |
| forbidden_claims=("lsd_diagnosis", "fmd_diagnosis", "fever_from_rgb"), | |
| ), | |
| # Answered by a hosted visual reasoner, not a file on disk. | |
| # `providers.HostedModelProvider` says why the pair is stable | |
| # here while the model that actually ran travels on the result. | |
| model_provider="hosted-multimodal", model_version="1", | |
| notes="Directive §10. 'Multiple raised nodular lesions visible. " | |
| "Veterinary review recommended.' Creates a case and can " | |
| "isolate the animal. Never 'Lumpy skin disease confirmed' — " | |
| "LSD and FMD are notifiable and a false positive creates a " | |
| "legal duty (see `app/dispositions.py`)."), | |
| # §11 "Experimental / Guided Capture". The sole not being visible while | |
| # the animal stands is a capture requirement, not a reason to drop it. | |
| _cap("cattle_hoof", "cattle", "animal", _EXPERIMENTAL, (_GUIDED,), | |
| AcquisitionProtocol( | |
| protocol="lifted_hoof", | |
| modality=(Modality.RGB_PHOTO,), | |
| output=OutputSpec(type="hoof_observation"), | |
| required_views=("sole",), | |
| optional_inputs=("coronary_band", "interdigital_space"), | |
| escalation="veterinary_review", | |
| reject_if=_GATE + ("hoof_not_lifted", "sole_not_clean", "move_closer"), | |
| allowed_claims=("visible_crack", "visible_lesion", "swelling", | |
| "erosion", "overgrowth", "normal_or_abnormal"), | |
| forbidden_claims=("definitive_hoof_disease",), | |
| ), | |
| # Answered by a hosted visual reasoner, not a file on disk. | |
| # `providers.HostedModelProvider` says why the pair is stable | |
| # here while the model that actually ran travels on the result. | |
| model_provider="hosted-multimodal", model_version="1", | |
| notes="Directive §11. 'Lift and clean the hoof before " | |
| "photographing.' Observations, not a diagnosis."), | |
| # §12 "Experimental". Appearance only. The directive is explicit that | |
| # cattle feces alone does not carry a strong disease claim. | |
| _cap("cattle_feces", "cattle", "animal", _EXPERIMENTAL, (), | |
| AcquisitionProtocol( | |
| protocol="dropping_close_up", | |
| modality=(Modality.RGB_PHOTO,), | |
| output=OutputSpec(type="faecal_observation"), | |
| required_views=("dropping",), | |
| optional_inputs=("reference_marker", "previous_capture"), | |
| escalation="veterinary_review", | |
| reject_if=_GATE + ("move_closer", "sample_not_isolated"), | |
| allowed_claims=("normal", "loose", "watery", "visible_blood", | |
| "visible_mucus", "unusual_colour"), | |
| forbidden_claims=("disease_diagnosis_from_feces",), | |
| ), | |
| # Answered by a hosted visual reasoner, not a file on disk. | |
| # `providers.HostedModelProvider` says why the pair is stable | |
| # here while the model that actually ran travels on the result. | |
| model_provider="hosted-multimodal", model_version="1", | |
| notes="Directive §12. Six visible states and nothing beyond them. " | |
| "Blood and mucus are worth surfacing on their own; a " | |
| "diagnosis from a dropping is not."), | |
| # §14 "Experimental / Guided Capture". Re-scoped from cough audio to | |
| # respiratory *rate* from video: this is optical flow and an FFT, and it | |
| # is a different capability from the continuous cough surveillance in | |
| # §27 that a fixed microphone would provide. | |
| _cap("cattle_respiratory", "cattle", "animal", _EXPERIMENTAL, (_GUIDED,), | |
| AcquisitionProtocol( | |
| protocol="flank_hold", | |
| modality=(Modality.RGB_VIDEO,), | |
| # guardrail, anchored on the directive. §14's own worked example | |
| # is "44-50 breaths/min". The bounds sit far outside it in both | |
| # directions so that severe distress still reports, while the | |
| # 100,000 breaths a minute a watchdog validated against the | |
| # unbounded schema cannot. | |
| output=OutputSpec(type="respiratory_rate", unit="breaths_per_minute", | |
| show_range=True, | |
| plausible_min=5, plausible_max=200, step=1, | |
| # Not `capture_quality`, which is a fraction. | |
| measured_claims=("respiratory_rate_range",)), | |
| minimum_capture_seconds=30, | |
| preferred_capture_seconds=60, | |
| required_views=("flank",), | |
| reject_if=_GATE + ("flank_not_in_frame", "camera_moving", | |
| "animal_moving"), | |
| allowed_claims=("respiratory_rate_range", "capture_quality"), | |
| forbidden_claims=("continuous_surveillance_from_spot_recording",), | |
| ), | |
| output_kind="measurement", | |
| # §14's pipeline is segmentation, optical flow, FFT and peak | |
| # detection. Everything after the mask is arithmetic §4 says to | |
| # prefer over a neural model, so this is the strongest on-device | |
| # candidate in the registry once a small segmenter exists. | |
| on_device_candidate=True, | |
| notes="Directive §14. 'Hold the cow's flank in frame for 30-60 " | |
| "seconds' → segmentation → optical flow → FFT → 'Estimated " | |
| "respiratory rate: 44-50 breaths/min'. A spot measurement, " | |
| "explicitly not cough surveillance."), | |
| # ---- poultry ---------------------------------------------------- | |
| # §6.2 and §6.3 "Build now as Experimental", on CountGD rather than a | |
| # COCO detector. §28 adds the controlled pan for a whole house. | |
| _cap("poultry_count", "poultry", "flock_cycle", _EXPERIMENTAL, (_GUIDED,), | |
| AcquisitionProtocol( | |
| protocol="controlled_pan", | |
| modality=(Modality.RGB_PHOTO, Modality.RGB_VIDEO), | |
| output=OutputSpec(type="visible_count", unit="birds", show_range=True, | |
| # guardrail. A visible count from one frame or | |
| # one pan. The densest measured frame in this | |
| # file is DFCCNet's ~166 birds; a whole-house | |
| # pan legitimately sees thousands, so the | |
| # ceiling is set where a *visible* count stops | |
| # being physically possible rather than where | |
| # it stops being usual. step 1: 327.4 birds is | |
| # not a more careful answer than 327. | |
| plausible_min=0, plausible_max=50_000, step=1, | |
| measured_claims=( | |
| "visible_count", | |
| "unique_birds_observed_during_scan")), | |
| # §6.3's capture is "photo; controlled pan; density-dependent | |
| # mode". The protocol is the density rule, not the duration: | |
| # a single frame for a sparse yard, a controlled pan for a | |
| # house. That is what `GUIDED_CAPTURE` is asserting here — the | |
| # user must capture in the mode the density calls for — so | |
| # there is no minimum duration, and the pan is the preferred | |
| # rather than the only capture. | |
| preferred_capture_seconds=15, | |
| required_views=("floor_section",), | |
| optional_inputs=("exemplar_box", "house_layout"), | |
| reject_if=_GATE + ("pan_too_fast", "density_beyond_measured_regime"), | |
| # Two of §6.3's three quantities, and only two, because only two | |
| # are observations. A model looking at pixels can say how many | |
| # birds are in frame and how many distinct birds it saw during a | |
| # scan. It cannot know what the farm placed, what died, or what | |
| # was counted last week, so it cannot reconcile anything. | |
| allowed_claims=("visible_count", "unique_birds_observed_during_scan"), | |
| # The third quantity, kept where §6.3 wants it — distinct, named | |
| # and impossible to conflate — but out of the model's schema. | |
| derived_claims=("reconciled_flock_population",), | |
| forbidden_claims=("exact_house_population",), | |
| ), | |
| output_kind="measurement", | |
| # `deferred` for the same reason as `cattle_detection`: the phone's | |
| # COCO detector counts visible birds offline. A dense house needs | |
| # the service and, on the directive's stack, CountGD. | |
| connectivity=Connectivity.DEFERRED, | |
| on_device_candidate=True, | |
| model_provider="yolox-m-coco", model_version="0.1.1rc0", | |
| geography_validated=(UNVALIDATED_GEOGRAPHY,), | |
| notes="Directive §6.3. Three numbers that must never be conflated: " | |
| "visible count, unique birds observed during a scan, and " | |
| "reconciled flock population — and **only the first two are " | |
| "the model's to say**. Reconciliation needs the placement " | |
| "count, the mortality log and the last scan, none of which is " | |
| "in the photograph, so it is `derived_claims` and the app " | |
| "computes it. 'Approximately 327 birds " | |
| "visible', never 'the flock is 327'. **What runs today is " | |
| "YOLOX-m on one frame**, which is honest for a yard of fifty " | |
| "layers and withholds a number once the frame is denser than " | |
| "a detector can read. **CountGD is the replacement §6.3 asks " | |
| "for and it has been benchmarked** — this sentence read 'has " | |
| "not' while the run sat in the repository. " | |
| "`experiments/poultry_house_count/`, run 70dbab213a4c, 452 " | |
| "frames of a commercial broiler house: MAE 14.84 birds, MAPE " | |
| "11.2%, per-frame recall 0.9555, and it found nothing on 0 " | |
| "frames. That is the dense case YOLOX-m refuses outright. It " | |
| "is not registered because there is no CountGD model card, no " | |
| "adapter inside `app/`, and its checkpoint is not in the " | |
| "repository — so the ±11% belongs to a run and not to " | |
| "anything a farm can call today."), | |
| # §13 "Experimental / Human Confirmation". DINOv3 retrieval against | |
| # labelled reference sets, then a structured multimodal review, over | |
| # 4-6 samples so the output is flock-level. | |
| _cap("poultry_fecal", "poultry", "flock_cycle", _EXPERIMENTAL, (_GUIDED, _CONFIRM), | |
| AcquisitionProtocol( | |
| protocol="multi_sample_dropping", | |
| modality=(Modality.RGB_PHOTO,), | |
| output=OutputSpec(type="gi_health_screen"), | |
| minimum_samples=4, | |
| preferred_samples=6, | |
| required_views=("dropping",), | |
| optional_inputs=("house_location", "bird_age_days"), | |
| confirmation_options=("looks_right", "correct", "retake"), | |
| escalation="veterinary_review", | |
| reject_if=_GATE + ("move_closer", "sample_not_isolated"), | |
| # §13's own preferred list, verbatim, plus the flock-level | |
| # wording it asks for. The coccidiosis entry is the directive's: | |
| # "coccidiosis-associated visual pattern only when appropriate | |
| # and clearly qualified". | |
| allowed_claims=("normal", "abnormal", "gi_health_watch", | |
| "elevated_gi_health_risk", | |
| "coccidiosis_associated_visual_pattern"), | |
| forbidden_claims=("newcastle_diagnosis", "coccidiosis_diagnosis", | |
| "disease_probability_without_local_validation"), | |
| ), | |
| # Answered by a hosted visual reasoner, not a file on disk. | |
| # `providers.HostedModelProvider` says why the pair is stable | |
| # here while the model that actually ran travels on the result. | |
| model_provider="hosted-multimodal", model_version="1", | |
| notes="Directive §13. One dropping does not diagnose a flock, so " | |
| "the output is flock-level over 4-6 samples.\n\n" | |
| "**Why one disease is named and the other is not.** An " | |
| "auditor found this file refusing `newcastle_diagnosis` while " | |
| "allowing `coccidiosis_associated_visual_pattern`, with no " | |
| "reason recorded. The reason is real and it is now written " | |
| "down; the asymmetry is also narrower than it looked.\n\n" | |
| "It is not coccidiosis against Newcastle. It is a **visual " | |
| "pattern** against a **diagnosis**: `coccidiosis_diagnosis` " | |
| "is forbidden too, and was added when this was checked. " | |
| "Neither disease may be diagnosed. One of them may be named " | |
| "as an appearance, and three things decide which:\n" | |
| "1. §13 names it — 'coccidiosis-associated visual pattern " | |
| "only when appropriate and clearly qualified' — and names " | |
| "nothing comparable for Newcastle. The directive draws the " | |
| "line; this file follows it.\n" | |
| "2. The evidence is not comparable. Newcastle is 376 of " | |
| "6,812 images in the largest public set and its reported " | |
| "recall is 62.7%, so more than a third of cases are missed; " | |
| "coccidiosis is one of the three common classes that carry " | |
| "that set's headline accuracy. See `app/dispositions.py`.\n" | |
| "3. **Newcastle is notifiable in Nigeria and coccidiosis is " | |
| "not.** A false positive on a notifiable disease creates a " | |
| "legal duty a farmer never asked a phone to create. That is " | |
| "the same constraint as `cattle_skin` and it is what makes " | |
| "the cost of the two errors different in kind, not degree."), | |
| # §15 "Experimental / Human Confirmation". Time is the signal; a dead | |
| # bird and a sleeping bird are the same photograph. | |
| _cap("poultry_inactive_birds", "poultry", "flock_cycle", _EXPERIMENTAL, | |
| (_GUIDED, _CONFIRM), | |
| AcquisitionProtocol( | |
| protocol="section_scan", | |
| modality=(Modality.RGB_VIDEO,), | |
| # guardrail. A review queue from one section scan, not a | |
| # mortality count for a house. | |
| output=OutputSpec(type="inactive_candidates", unit="birds", | |
| plausible_min=0, plausible_max=10_000, step=1, | |
| measured_claims=("inactive_candidate", | |
| "needs_review")), | |
| minimum_capture_seconds=15, | |
| preferred_capture_seconds=30, | |
| required_views=("floor_section",), | |
| confirmation_options=("dead", "sick", "resting", "fine"), | |
| reject_if=_GATE + ("pan_too_fast", "camera_moving"), | |
| allowed_claims=("inactive_candidate", "needs_review"), | |
| forbidden_claims=("dead_versus_sleeping_certainty",), | |
| ), | |
| notes="Directive §15. '5 birds need review', each answered " | |
| "Dead / Sick / Resting / Fine. Useful immediately, and every " | |
| "answer is a training label."), | |
| # §23 "Engineering spike / Experimental". A sample-bird workflow, not | |
| # every bird in a crowded flock from arbitrary video. | |
| _cap("poultry_weight", "poultry", "flock_cycle", _EXPERIMENTAL, (_GUIDED, _CONFIRM), | |
| AcquisitionProtocol( | |
| protocol="held_sample_bird", | |
| modality=(Modality.RGB_PHOTO, Modality.CAMERA_POSE, | |
| Modality.DEPTH_OPTIONAL), | |
| # guardrail. A day-old chick to an oversized cock or turkey. | |
| output=OutputSpec(type="weight_estimate", unit="kg", show_range=True, | |
| plausible_min=0.02, plausible_max=10, | |
| measured_claims=("weight_range_estimate", | |
| "sample_mean", | |
| "sample_range")), | |
| minimum_samples=10, | |
| preferred_samples=20, | |
| required_views=("side", "top"), | |
| optional_inputs=("arcore_depth", "reference_marker"), | |
| confirmation_options=("add_scale_weight", "correct", "retake"), | |
| reject_if=_GATE + ("bird_not_isolated", "cluttered_background", | |
| "insufficient_geometry"), | |
| # **The mean and the range survive; the spread does not.** | |
| # `experiments/poultry_weight/` measured the mean out by | |
| # 0.33% +/- 3.5 with camera weights against 0.18% +/- 3.2 with | |
| # exact ones at fifteen birds — statistically | |
| # indistinguishable, because measurement error averages out of | |
| # a mean. It squares into a variance, which is why the two | |
| # claims below it are refused. | |
| allowed_claims=("weight_range_estimate", "sample_mean", | |
| "sample_range", "birds_sampled"), | |
| # The two uniformity rejections are attached here as well as to | |
| # `poultry_uniformity`, and that is deliberate: this is the | |
| # capability that holds the per-bird weights, so this is where a | |
| # reasoner is standing when it reaches for a spread. | |
| forbidden_claims=("exact_poultry_weight", "whole_flock_weight_from_video", | |
| "flock_uniformity_from_vision_weights", | |
| "coefficient_of_variation_from_vision_weights"), | |
| ), | |
| output_kind="measurement", | |
| notes="Directive §23. 10-20 held birds, each isolated against a " | |
| "clear background. The flock number is a sample statistic " | |
| "and says so.\n\n" | |
| "**What §23's four statistics come to.** The section asks for " | |
| "an average, a range, a coefficient of variation and a " | |
| "uniformity. The first two survive a vision weight and the " | |
| "last two do not — measurement error averages out of a mean " | |
| "and squares into a variance — so `sample_mean` and " | |
| "`sample_range` are allowed here and the spread is refused, " | |
| "on this capability and on `poultry_uniformity` both. " | |
| "`sample_range` is a band in kilograms shown as a band, which " | |
| "is what §38 asks for; it is not a uniformity percentage a " | |
| "farm culls on. **Its own inflation was not measured** — the " | |
| "experiment covered the mean and the CV — so that it survives " | |
| "is a decision about the shape of the claim rather than a " | |
| "measurement of the claim."), | |
| # §23 again, and **the one capability in this registry whose claim is | |
| # refused rather than corrected.** ADR 0023 is the decision; this is what | |
| # it does to the entry. | |
| # | |
| # The history is worth keeping because the same argument was made twice | |
| # and was wrong both times. It was `coming_soon`, on the reasoning that | |
| # nobody had built it. An auditor called that the weakest classification | |
| # in the file — `state` is a claim ceiling, and availability is | |
| # `is_runnable`'s to report — so it moved to `experimental` with an unmet | |
| # `depends_on`. That was the right correction to the wrong question. | |
| # Nobody had asked what the input would be worth when it arrived. | |
| # | |
| # **`experiments/poultry_weight/` asked, and the answer ends the | |
| # capability.** A coefficient of variation over measured weights carries | |
| # the measurement error as well as the flock's own spread, because error | |
| # adds variance. At the best published per-bird error for this method — | |
| # 7.8%, Mortensen et al. 2016, from a *fixed depth camera* over a | |
| # commercial house, so a floor on a phone's error rather than an estimate | |
| # of it — against a commercial flock's own 11-18% spread: | |
| # | |
| # * 15 birds, true CV 12%: estimated CV 14.38%, **bias +2.40 points**, | |
| # within two points of the truth 40.0% of the time. | |
| # * The derived uniformity reads **6.96 points low**. A flock at 60% | |
| # uniformity reports as 53%. | |
| # * **More birds does not fix it.** +1.91 at n=10, +2.40 at n=15, | |
| # +2.24 at n=20, +2.16 at n=30. The exact-weight control converges | |
| # towards zero over the same sweep (-0.50 to -0.05); this does not, | |
| # because the bias is systematic and not sampling noise. | |
| # * Across true CVs of 8-18% at fifteen birds the CV bias runs +1.46 | |
| # to +2.90 and the uniformity error -1.46 to -13.94 points. **It is | |
| # worst where the flock is most uniform**, which is the opposite of | |
| # useful. | |
| # | |
| # Two things follow, and the second is why `experimental` is not | |
| # available as a softer landing. | |
| # | |
| # **The error is a bias, not a width.** `experimental` means the output | |
| # carries broad uncertainty the user can see, and a visible band is that | |
| # state's whole remedy. A band does not move a centre. A flock at 60% | |
| # still reads 53% with error bars drawn round it, and it reads low in the | |
| # direction that makes a bad flock look acceptable — the direction a farm | |
| # does not investigate. | |
| # | |
| # **§36's bar is met, and it is met by the signal rather than by a failed | |
| # model.** §36 refuses to call a capability unavailable on one model's | |
| # failure. This is not one model's failure: it is error propagation, it | |
| # binds any vision method at that error level, and the error level is a | |
| # published floor. `experiments/poultry_weight/` also prices the escape — | |
| # a per-bird error of 4% still leaves about -3 uniformity points from | |
| # measurement bias alone, and the small figure at n=15 is two biases | |
| # cancelling rather than an error small enough to ignore. | |
| # | |
| # **The simulation has a closed form and the two agree**, which is what | |
| # takes this from one experiment's result to a property of the | |
| # estimator. Measuring a weight with independent relative error `e` | |
| # gives `CV_obs = sqrt(CV^2 + e^2 + CV^2 e^2)`; at a true CV of 12% and | |
| # e = 7.8% that is +2.34 points against the simulation's +2.40, and at | |
| # e = 6.6% it is +1.72 against +1.74. **There is no `n` in the closed | |
| # form**, which is the sharpest statement of why more birds does not | |
| # help. ADR 0023 carries the full comparison. | |
| # | |
| # `unsupported_claim` is then the only one of the four states left. | |
| # `production` and `experimental` both assert a publishable output; | |
| # `coming_soon` means an engineering path nobody has walked, and this one | |
| # was walked. The state is a claim ceiling and this ceiling is zero. | |
| # | |
| # **What it does not say.** Uniformity from a *scale* is exact and always | |
| # was — §23's "requires no ML once weights exist" is true of scale | |
| # weights and was never true of camera weights. That route survives, in | |
| # `derived_claims`, and it is not an inference capability: a farmer | |
| # typing ten numbers is not a capture, has no modality and runs no model. | |
| # It is the `reconciled_flock_population` shape exactly — a legitimate | |
| # number for the product to show, illegitimate as a *model's* claim. | |
| # | |
| # The declared protocol below is kept although nothing may be published | |
| # from it. §36 asks for the attempt to be documented, and an entry that | |
| # deleted its own capture would invite the next reader to propose it | |
| # again. | |
| _cap("poultry_uniformity", "poultry", "flock_cycle", | |
| CapabilityState.UNSUPPORTED_CLAIM, (_GUIDED,), | |
| AcquisitionProtocol( | |
| protocol="held_sample_bird", | |
| modality=(Modality.RGB_PHOTO,), | |
| # No unit and no bounds. The capability publishes no number, and | |
| # a declared bound would say a number is publishable inside it. | |
| output=OutputSpec(type="uniformity"), | |
| minimum_samples=10, | |
| preferred_samples=20, | |
| required_views=("side", "top"), | |
| reject_if=_GATE + ("insufficient_sample",), | |
| # **Empty, and that is the control.** `schema_for` builds the | |
| # reasoner's enum from this list, and `check_vocabulary` refuses | |
| # anything absent from it, so an empty vocabulary is the | |
| # strictest state available rather than an unguarded one: there | |
| # is no field a figure can be written into. | |
| # | |
| # **It closes the schema and not the output**, and saying so | |
| # here is the point. A watchdog took a uniformity percentage | |
| # into free text on 14 of the 28 capabilities, because | |
| # `check_quantities` corroborates a prose digit against any | |
| # structured field rather than one in the same unit — 72 beside | |
| # `sampled_prevalence: 72` publishes "Flock uniformity: 72%". | |
| # `poultry_weight` carries its own forbidden CV the same way: a | |
| # bird's weight in kilograms is bounded 0.02-10 and a broiler CV | |
| # is 8-14. So this removes the claim from the model's vocabulary | |
| # and does not remove it from the product's prose. The | |
| # difference is real and is not the whole job; ADR 0023 and | |
| # `tests/test_claims.py::TestTheQuantityGateCorroboratesANumberAndNotAClaim` | |
| # carry the measurement and the one-call fix. | |
| allowed_claims=(), | |
| # Six entries doing three jobs. | |
| # | |
| # The **first two** are the descriptive rejections, which is | |
| # what `REJECTED_CLAIMS` renders to a client with a reason and | |
| # a corrected form. The **middle three** are the identifiers | |
| # this capability used to allow and a reasoner would actually | |
| # reach for — forbidding a descriptive name nobody emits while | |
| # leaving the emitted one allowed would be theatre. | |
| # `sample_size` is in that group because a sample size is only | |
| # ever shown to qualify a spread, and there is no longer a | |
| # spread for it to qualify. The **last** is `exact_poultry_weight`, | |
| # which was already here and is §23's own rejection. | |
| forbidden_claims=("flock_uniformity_from_vision_weights", | |
| "coefficient_of_variation_from_vision_weights", | |
| "coefficient_of_variation", "uniformity_band", | |
| "sample_size", "exact_poultry_weight"), | |
| # The surviving route, kept visible and kept out of every | |
| # model's schema. §23's arithmetic over numbers a farmer read | |
| # off a scale is exact, and `sampling-exact` measures what it | |
| # delivers: unbiased, and within two points of the true CV 63.5% | |
| # of the time at fifteen birds. | |
| derived_claims=("uniformity_from_scale_weights", | |
| "coefficient_of_variation_from_scale_weights"), | |
| ), | |
| output_kind="measurement", | |
| # **`depends_on` is gone, and its absence is the finding.** It said | |
| # `("poultry_weight",)`, which reads as *ship that and this | |
| # unlocks*. Shipping `poultry_weight` does not unlock this; it | |
| # supplies the exact input that makes the answer wrong. A dependency | |
| # that resolves into a refusal is a worse pointer than none, because | |
| # it tells a reader the blocker is sequencing when the blocker is | |
| # suitability. | |
| depends_on=(), | |
| # Still true, and now it describes only the surviving arithmetic: a | |
| # coefficient of variation over twenty typed numbers runs anywhere. | |
| on_device_candidate=True, | |
| notes="Directive §23. **Refused as a vision claim.** A coefficient " | |
| "of variation or a uniformity percentage computed from " | |
| "camera weights reads 6.96 points low at the best published " | |
| "per-bird error (7.8%), on a fifteen-bird sample against a " | |
| "commercial flock's own 11-18% spread — a flock at 60% " | |
| "uniformity reports as 53%, and thirty birds does not fix it " | |
| "because the bias is systematic. Uniformity is a decision " | |
| "variable: a farm culls, re-feeds or delays a harvest on it, " | |
| "and the error runs in the direction that makes a bad flock " | |
| "look acceptable. **The sample mean and the per-bird range " | |
| "are untouched and survive on `poultry_weight`** — " | |
| "measurement error averages out of a mean and squares into a " | |
| "variance, which is the whole of it. Uniformity from scale " | |
| "weights is exact, is what §23's 'no ML once weights exist' " | |
| "was always true of, and is a derived claim the app computes " | |
| "rather than a capability that runs."), | |
| # §18 "Experimental / Guided Capture / Human Confirmation". The three | |
| # requirements at once — the case that decides the state-plus-flags shape. | |
| _cap("poultry_footpad", "poultry", "flock_cycle", _EXPERIMENTAL, (_GUIDED, _CONFIRM), | |
| AcquisitionProtocol( | |
| protocol="held_bird_footpad", | |
| modality=(Modality.RGB_PHOTO,), | |
| # scale. §18's "Approximate grade: 2 / 4". A whole-number grade | |
| # on a 0-4 rubric; 2.4 is not a finer reading of it. | |
| output=OutputSpec(type="footpad_grade", unit="grade_0_4", | |
| plausible_min=0, plausible_max=4, step=1, | |
| # Not `sampled_prevalence`. §18's own example | |
| # is "20 birds sampled, 4 moderate or worse, | |
| # 20%", and a 0-4 ceiling would refuse it. | |
| measured_claims=("approximate_grade",)), | |
| minimum_samples=10, | |
| preferred_samples=20, | |
| required_views=("underside_of_foot",), | |
| confirmation_options=("looks_right", "correct_grade", "retake"), | |
| reject_if=_GATE + ("foot_not_presented", "move_closer"), | |
| allowed_claims=("approximate_grade", "sampled_prevalence", | |
| "birds_sampled", "moderate_or_worse"), | |
| forbidden_claims=("whole_flock_grade_from_sample",), | |
| ), | |
| # Answered by a hosted visual reasoner, not a file on disk. | |
| # `providers.HostedModelProvider` says why the pair is stable | |
| # here while the model that actually ran travels on the result. | |
| model_provider="hosted-multimodal", model_version="1", | |
| notes="Directive §18. 'Approximate grade: 2 / 4', correctable, then " | |
| "aggregated: '20 birds sampled, 4 moderate or worse, 20%'. " | |
| "A slaughter line does this better; a farm cannot use one."), | |
| # §19 "Hock / feather / poultry wound / eye-head" — **four keys, not | |
| # one.** | |
| # | |
| # §19 writes them as one section with one model stack and four output | |
| # examples, which is an argument for one key only if the stack is what | |
| # distinguishes a capability. It is not. SAM 3.1 plus a multimodal model | |
| # plus DINOv3 retrieval is also the stack for `cattle_wound`, | |
| # `cattle_skin`, `cattle_hoof`, `cattle_feces` and `poultry_litter`, and | |
| # nobody would merge those. | |
| # | |
| # What distinguishes a capability in this registry is the acquisition | |
| # protocol, and these are four different captures on a farm: a held | |
| # bird's leg joint, a whole bird's back and tail, an affected area | |
| # anywhere on the body, and a head close-up. One key would need a | |
| # `required_views` that fits none of them, which is exactly the failure | |
| # the schema exists to prevent. | |
| # | |
| # None of the four claims a flock number from one bird. §19 gives no | |
| # sampling rule, so none is invented here — but §13's principle, that | |
| # one dropping does not diagnose a flock, is the reason | |
| # `flock_prevalence_from_one_bird` is forbidden rather than left open. | |
| _cap("poultry_hock", "poultry", "flock_cycle", _EXPERIMENTAL, (_GUIDED,), | |
| AcquisitionProtocol( | |
| protocol="held_bird_hock", | |
| modality=(Modality.RGB_PHOTO,), | |
| output=OutputSpec(type="hock_observation"), | |
| required_views=("hock",), | |
| confirmation_options=(), | |
| reject_if=_GATE + ("hock_not_presented", "move_closer"), | |
| allowed_claims=("visible_hock_lesion", "lesion_severity", | |
| "normal_or_abnormal"), | |
| forbidden_claims=("named_disease_diagnosis", | |
| "flock_prevalence_from_one_bird"), | |
| ), | |
| # Answered by a hosted visual reasoner, not a file on disk. | |
| # `providers.HostedModelProvider` says why the pair is stable | |
| # here while the model that actually ran travels on the result. | |
| model_provider="hosted-multimodal", model_version="1", | |
| notes="Directive §19. 'Moderate visible hock lesion.' One bird, " | |
| "described, not a flock rate."), | |
| _cap("poultry_feather", "poultry", "flock_cycle", _EXPERIMENTAL, (_GUIDED,), | |
| AcquisitionProtocol( | |
| protocol="whole_bird_dorsal", | |
| modality=(Modality.RGB_PHOTO,), | |
| output=OutputSpec(type="feather_observation"), | |
| required_views=("back", "tail"), | |
| optional_inputs=("vent", "bird_age_days"), | |
| reject_if=_GATE + ("bird_not_isolated", "move_closer"), | |
| allowed_claims=("reduced_feather_coverage", "coverage_location", | |
| "normal_or_abnormal"), | |
| forbidden_claims=("named_disease_diagnosis", | |
| "flock_prevalence_from_one_bird"), | |
| ), | |
| # Answered by a hosted visual reasoner, not a file on disk. | |
| # `providers.HostedModelProvider` says why the pair is stable | |
| # here while the model that actually ran travels on the result. | |
| model_provider="hosted-multimodal", model_version="1", | |
| notes="Directive §19. 'Reduced feather coverage on back and tail.' " | |
| "Where the coverage is missing is the observation; why it is " | |
| "missing is not claimed."), | |
| _cap("poultry_wound", "poultry", "flock_cycle", _EXPERIMENTAL, (), | |
| AcquisitionProtocol( | |
| protocol="lesion_close_up", | |
| modality=(Modality.RGB_PHOTO,), | |
| # guardrail. The same claim as `cattle_wound` on a much smaller | |
| # animal, so the ceiling is much smaller too. | |
| output=OutputSpec(type="wound_observation", unit="cm2", | |
| show_range=True, | |
| plausible_min=0, plausible_max=500, | |
| measured_claims=("approximate_visible_area",)), | |
| required_views=("wound",), | |
| optional_inputs=("reference_marker", "previous_capture"), | |
| escalation="veterinary_review", | |
| reject_if=_GATE + ("move_closer",), | |
| allowed_claims=("visible_wound", "wound_location", | |
| "approximate_visible_area", | |
| "change_since_last_scan", | |
| "surrounding_swelling", "visible_discharge"), | |
| forbidden_claims=("wound_etiology", "named_disease_diagnosis", | |
| "flock_prevalence_from_one_bird"), | |
| ), | |
| # Answered by a hosted visual reasoner, not a file on disk. | |
| # `providers.HostedModelProvider` says why the pair is stable | |
| # here while the model that actually ran travels on the result. | |
| model_provider="hosted-multimodal", model_version="1", | |
| notes="Directive §19. 'Open wound visible on left flank.' The same " | |
| "shape as `cattle_wound`, including the reference marker and " | |
| "the refusal to name a cause."), | |
| _cap("poultry_eye_head", "poultry", "flock_cycle", _EXPERIMENTAL, (_GUIDED,), | |
| AcquisitionProtocol( | |
| protocol="held_bird_head", | |
| modality=(Modality.RGB_PHOTO,), | |
| output=OutputSpec(type="eye_head_observation"), | |
| required_views=("head",), | |
| optional_inputs=("second_eye",), | |
| escalation="veterinary_review", | |
| reject_if=_GATE + ("head_not_presented", "move_closer"), | |
| allowed_claims=("visible_discharge", "discharge_location", | |
| "swelling", "normal_or_abnormal"), | |
| forbidden_claims=("named_disease_diagnosis", | |
| "flock_prevalence_from_one_bird"), | |
| ), | |
| # Answered by a hosted visual reasoner, not a file on disk. | |
| # `providers.HostedModelProvider` says why the pair is stable | |
| # here while the model that actually ran travels on the result. | |
| model_provider="hosted-multimodal", model_version="1", | |
| notes="Directive §19. 'Visible discharge around left eye.' " | |
| "Respiratory and Newcastle-adjacent signs show up here, " | |
| "which is exactly why nothing is named — see `poultry_fecal` " | |
| "for the notifiable-disease constraint."), | |
| # §17 "Experimental". Vision measures the birds' response; a probe | |
| # measures the house. They are complementary, not competing. | |
| _cap("poultry_heat_stress", "poultry", "house", _EXPERIMENTAL, (_GUIDED,), | |
| AcquisitionProtocol( | |
| protocol="flock_behaviour_scan", | |
| modality=(Modality.RGB_VIDEO,), | |
| output=OutputSpec(type="heat_stress_screen"), | |
| minimum_capture_seconds=15, | |
| preferred_capture_seconds=30, | |
| required_views=("floor_section",), | |
| optional_inputs=("house_temperature", "house_humidity"), | |
| reject_if=_GATE + ("camera_moving", "birds_not_visible"), | |
| allowed_claims=("no_obvious_visual_signs", | |
| "some_heat_associated_behaviours", | |
| "heat_stress_associated_behaviour_elevated", | |
| "panting", "wing_spreading", "reduced_activity", | |
| "clustering_at_drinkers", "abnormal_distribution"), | |
| forbidden_claims=("exact_physiology", "fever_from_rgb"), | |
| ), | |
| notes="Directive §17 and §38. Three bands, from the behaviours " | |
| "actually visible. A temperature and humidity probe measures " | |
| "the environment; this measures the birds."), | |
| # §16 "Experimental", with the claim renamed. Litter *condition* is | |
| # visible; litter *moisture* is not (§29). | |
| _cap("poultry_litter", "poultry", "house", _EXPERIMENTAL, (), | |
| AcquisitionProtocol( | |
| protocol="floor_area_scan", | |
| modality=(Modality.RGB_PHOTO, Modality.RGB_VIDEO), | |
| # scale. A share of the region that was scanned. §16's example | |
| # is "wet-looking areas: 18% of scanned region", and a share | |
| # cannot exceed the whole. | |
| output=OutputSpec(type="litter_condition", | |
| unit="percent_of_scanned_region", | |
| plausible_min=0, plausible_max=100, | |
| measured_claims=("abnormal_area_percentage",)), | |
| required_views=("floor_area",), | |
| optional_inputs=("drinker_line_id", "moisture_probe_reading"), | |
| reject_if=_GATE + ("floor_not_visible",), | |
| allowed_claims=("loose", "caked", "heavily_soiled", "wet_looking", | |
| "abnormal_area_percentage", "worst_area_location"), | |
| forbidden_claims=("exact_litter_moisture",), | |
| ), | |
| # Answered by a hosted visual reasoner, not a file on disk. | |
| # `providers.HostedModelProvider` says why the pair is stable | |
| # here while the model that actually ran travels on the result. | |
| model_provider="hosted-multimodal", model_version="1", | |
| notes="Directive §16. 'Caking: High. Wet-looking areas: 18% of " | |
| "scanned region. Worst near drinker line 3.' Never a moisture " | |
| "percentage without a probe."), | |
| # §26 "Engineering spike / Experimental". A spot screen, and §27 is | |
| # explicit that needing continuous monitoring must not block it. | |
| _cap("poultry_respiratory", "poultry", "flock_cycle", _EXPERIMENTAL, (_GUIDED,), | |
| AcquisitionProtocol( | |
| protocol="quiet_spot_recording", | |
| modality=(Modality.AUDIO,), | |
| # **This declared a count of 0 to 1,000 events and now declares | |
| # no number at all.** The shipped detector scores AUC 0.4141 on | |
| # 6,346 real poultry-house clips — below the 0.5 chance line, | |
| # and inverted rather than merely weak: 6.90 events a minute on | |
| # healthy clips against 1.16 on sick ones. A capability whose | |
| # only implementation runs backwards may not publish that | |
| # implementation's count, and §26's own printed line carries no | |
| # digit, so nothing the directive asks for is lost. | |
| output=OutputSpec(type="respiratory_events"), | |
| minimum_capture_seconds=30, | |
| preferred_capture_seconds=60, | |
| optional_inputs=("house_id", "bird_age_days"), | |
| reject_if=("recording_too_short", "machinery_dominates", | |
| "speech_dominates"), | |
| allowed_claims=("cough_or_sneeze_like_events_detected", | |
| "spot_respiratory_screen"), | |
| forbidden_claims=("continuous_surveillance_from_spot_recording",), | |
| ), | |
| notes="Directive §26. 'Stand quietly in the house and record 30 " | |
| "seconds.' Reports cough- or sneeze-like events and says " | |
| "plainly that it is a spot screen."), | |
| # §20 "Experimental" for external quality. Fine cracks are a separate, | |
| # accessory-gated claim — see `REJECTED_CLAIMS`. | |
| _cap("egg_quality", "poultry", "flock_cycle", _EXPERIMENTAL, (), | |
| AcquisitionProtocol( | |
| protocol="tray_photo", | |
| modality=(Modality.RGB_PHOTO,), | |
| # guardrail. Eggs on a photographed tray or trays. A standard | |
| # tray holds 30; the ceiling allows a stack of them. | |
| output=OutputSpec(type="egg_external_quality", unit="eggs", | |
| plausible_min=0, plausible_max=1_000, step=1, | |
| measured_claims=("count",)), | |
| required_views=("tray",), | |
| optional_inputs=("reference_marker", "backlight_accessory"), | |
| reject_if=_GATE + ("eggs_overlapping", "move_closer"), | |
| allowed_claims=("count", "shape", "obvious_dirt", | |
| "external_discolouration", "obvious_visible_damage"), | |
| forbidden_claims=("hairline_crack_from_ambient_photo",), | |
| ), | |
| output_kind="measurement", | |
| # Answered by a hosted visual reasoner, not a file on disk. | |
| # `providers.HostedModelProvider` says why the pair is stable | |
| # here while the model that actually ran travels on the result. | |
| model_provider="hosted-multimodal", model_version="1", | |
| notes="Directive §20. Count, shape, dirt, discolouration and " | |
| "obvious damage from an ordinary photograph. Hairline cracks " | |
| "need candling and are not claimed without it."), | |
| ] | |
| } | |
| class RejectedClaim: | |
| """A claim from directive §30 that Animap must never make. | |
| §30's own instruction is the important half: *"This does not mean the | |
| underlying Animap capability should be removed. Change the acquisition | |
| protocol or the claim."* So each entry names the capability that survives | |
| and what it says instead — and, where one exists, the hardware or | |
| installation that would make the stronger claim supportable. | |
| `survives_as` is `None` for exactly one entry, and that is not an oversight. | |
| """ | |
| claim: str | |
| #: The directive's words for why the claim fails. | |
| why: str | |
| #: The registry key that carries the corrected form, if any. | |
| survives_as: str | None | |
| corrected_form: str | |
| #: Directive §29: the accessory or installation that would change the answer. | |
| available_with: str | None = None | |
| #: Directive §30, in full. Eleven claims, plus `exact_poultry_weight` from §23. | |
| REJECTED_CLAIMS: dict[str, RejectedClaim] = { | |
| r.claim: r | |
| for r in [ | |
| RejectedClaim( | |
| "exact_weight_from_single_photo", | |
| "Exact cattle weight from an arbitrary freehand single RGB " | |
| "photograph. An arbitrary photograph carries no scale.", | |
| "cattle_weight", | |
| "A guided 2-4 second side sweep with camera pose, and a range: " | |
| "'Experimental weight estimate, 350-430 kg'.", | |
| available_with="Animap reference marker, or a known-size calibration " | |
| "object, when metric depth is unreliable (§22).", | |
| ), | |
| RejectedClaim( | |
| "exact_house_population", | |
| "Exact entire-house bird population from a casual partial pan.", | |
| "poultry_count", | |
| "Visible count, unique birds observed during a scan, and reconciled " | |
| "flock population — three distinct numbers, never conflated.", | |
| available_with="A fixed multi-camera installation for stronger " | |
| "whole-house counting (§28).", | |
| ), | |
| RejectedClaim( | |
| "exact_age_from_teeth", | |
| "Exact chronological age to the month from teeth.", | |
| "cattle_age_dentition", | |
| "An age band from a deterministic rule table: 'Estimated age: 3-4 " | |
| "years. Four permanent incisors visible.'", | |
| ), | |
| RejectedClaim( | |
| "fmd_diagnosis", | |
| "Definitive foot-and-mouth diagnosis from one image.", | |
| "cattle_skin", | |
| "'Abnormal skin pattern. Veterinary review recommended.' A case, an " | |
| "isolation prompt where appropriate, and room for a lab result.", | |
| ), | |
| RejectedClaim( | |
| "lsd_diagnosis", | |
| "Definitive lumpy skin disease diagnosis from one image.", | |
| "cattle_skin", | |
| "'Multiple raised nodular lesions visible. Veterinary review " | |
| "recommended.'", | |
| ), | |
| RejectedClaim( | |
| "fever_from_rgb", | |
| "Fever measurement from a normal RGB image.", | |
| "poultry_heat_stress", | |
| "Heat-stress-associated behaviour, described from what is visible: " | |
| "panting, wing spreading, clustering at drinkers.", | |
| available_with="A thermal accessory or camera, for surface " | |
| "temperature screening (§29).", | |
| ), | |
| RejectedClaim( | |
| "pregnancy_from_rgb", | |
| "Pregnancy diagnosis from an ordinary cattle photograph.", | |
| None, | |
| "Nothing. No capability in this registry claims it and none is " | |
| "proposed: pregnancy is diagnosed by palpation or ultrasound, and " | |
| "the corrected form is a recorded veterinary result rather than an " | |
| "inference capability. This is the one §30 claim with no surviving " | |
| "vision feature behind it.", | |
| ), | |
| RejectedClaim( | |
| "exact_litter_moisture", | |
| "Exact litter moisture percentage from RGB. Moisture has no " | |
| "photometric signature.", | |
| "poultry_litter", | |
| "Litter condition: loose, caked, heavily soiled, wet-looking, the " | |
| "share of the scanned region affected, and where it is worst.", | |
| available_with="A moisture probe, radar, or other validated sensing " | |
| "(§29).", | |
| ), | |
| RejectedClaim( | |
| "dead_versus_sleeping_certainty", | |
| "Dead-versus-sleeping certainty from one poultry image.", | |
| "poultry_inactive_birds", | |
| "Inactivity over a 15-30 second scan, surfaced as '5 birds need " | |
| "review' with Dead / Sick / Resting / Fine.", | |
| ), | |
| RejectedClaim( | |
| "hairline_crack_from_ambient_photo", | |
| "Subtle hairline crack detection from arbitrary ambient egg photos.", | |
| "egg_quality", | |
| "Count, shape, obvious dirt, external discolouration and obvious " | |
| "visible damage.", | |
| available_with="Candling: controlled backlighting, or an inexpensive " | |
| "backlight cradle (§20, §29).", | |
| ), | |
| RejectedClaim( | |
| "continuous_surveillance_from_spot_recording", | |
| "24/7 respiratory surveillance from one 30-second recording.", | |
| "poultry_respiratory", | |
| "'Cough/sneeze-like events detected. Spot respiratory screen only.' " | |
| "The cattle equivalent is `cattle_respiratory`, a spot rate.", | |
| available_with="A permanently placed microphone or camera, for " | |
| "hourly cough rate against a house's own baseline " | |
| "(§27).", | |
| ), | |
| RejectedClaim( | |
| "exact_poultry_weight", | |
| "The weight of every bird in a crowded flock from arbitrary video " | |
| "(§23).", | |
| "poultry_weight", | |
| # **This sentence used to end "a mean, a range and a coefficient of " | |
| # variation", and the last third of it is now rejected in its own | |
| # right two entries below.** Left corrected rather than left | |
| # standing: a rejection whose corrected form contains another | |
| # rejection is how a refused claim gets back in. | |
| "A 10-20 bird sample of held, isolated birds, reported as a mean and " | |
| "a range. Not a coefficient of variation — see " | |
| "`coefficient_of_variation_from_vision_weights`.", | |
| ), | |
| # The two below are **not from §30**. Every other entry in this table is | |
| # a claim the directive rejected in advance; these two are claims §23 | |
| # asked for and a measurement refused. They are here because this is | |
| # where the product's refusals live and a client renders them, and | |
| # because §36 is explicit that a capability may only be called | |
| # unavailable once the observable signal has been shown impractical — | |
| # which is a thing that can happen after the directive was written. | |
| # ADR 0023 carries the evidence. | |
| RejectedClaim( | |
| "coefficient_of_variation_from_vision_weights", | |
| "A flock's coefficient of variation computed from camera-estimated " | |
| "bird weights. Measurement error adds variance the flock does not " | |
| "have, so the CV is inflated by +2.40 points at the best published " | |
| "per-bird error (7.8%) on a fifteen-bird sample against a true CV of " | |
| "12% — and the bias is systematic, so it survives the sample size: " | |
| "+1.91 at ten birds, +2.16 at thirty. The exact-weight control " | |
| "converges towards zero over the same sweep and this does not.", | |
| "poultry_weight", | |
| "A sample mean and a per-bird range over 10-20 held birds, which a " | |
| "vision weight does support: the mean is out by 0.33% +/- 3.5 " | |
| "against 0.18% +/- 3.2 with exact weights, because error averages " | |
| "out of a mean and squares into a variance. The coefficient of " | |
| "variation itself is computed from scale weights or not at all.", | |
| available_with="A scale. §23's own `add_scale_weight` confirmation " | |
| "already exists on `poultry_weight`, and over exact " | |
| "weights the arithmetic is unbiased and lands within " | |
| "two points of the true CV 63.5% of the time at " | |
| "fifteen birds.", | |
| ), | |
| RejectedClaim( | |
| "flock_uniformity_from_vision_weights", | |
| "A flock uniformity percentage derived from camera-estimated bird " | |
| "weights. It inherits the CV's inflation through " | |
| "`2*Phi(10/CV) - 1` and reads **6.96 points low** at a true CV of " | |
| "12% — a flock at 60% uniformity reports as 53%. Across true CVs of " | |
| "8-18% the error runs -1.46 to -13.94 points, worst where the flock " | |
| "is most uniform. Uniformity is a decision variable a farm culls, " | |
| "re-feeds or delays a harvest on, and the error runs in the " | |
| "direction that makes a bad flock look acceptable.", | |
| "poultry_weight", | |
| "A sample mean and a per-bird range. The uniformity band itself is " | |
| "withheld rather than widened: the failure is a bias and not a " | |
| "width, so a visible uncertainty band would leave the centre exactly " | |
| "where it is.", | |
| available_with="A scale, as above. The conversion arithmetic is not " | |
| "in doubt — it matches Aviagen's twelve published " | |
| "rows to within 0.4985 points — and it is the weights " | |
| "underneath it that fail.", | |
| ), | |
| ] | |
| } | |
| #: Claims the product must never make, whatever a model reports. | |
| #: | |
| #: Derived rather than hand-maintained, so a capability cannot declare a | |
| #: forbidden claim that the global guard does not know about. `app/counting.py` | |
| #: asserts every emitted observation type against this set. | |
| FORBIDDEN_CLAIMS: frozenset[str] = frozenset(REJECTED_CLAIMS) | frozenset( | |
| claim | |
| for capability in REGISTRY.values() | |
| for claim in capability.acquisition.forbidden_claims | |
| ) | |
| def get(key: str) -> Capability | None: | |
| return REGISTRY.get(key) | |