Model/domain/epc_prediction/epc_prediction.py
Khalim Conn-Kowlessar a88b550234 Predict ventilation kind from the cohort mode 🟩
Add _apply_ventilation_mode: set the predicted mechanical_ventilation_kind to
the recency/geo-weighted cohort mode (mirrors _apply_glazing_mode — MEV/MVHR is
a new-build/retrofit feature clustering by era + street). Only the kind moves;
the template's sheltered_sides etc. stay. Natural cohorts mode to None and stay
natural (§2 default), so this only moves genuine MEV/MVHR neighbourhoods.
Display-only for the calc gate: component-accuracy (26) + corpus (6) + e2e (1)
all green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 10:54:11 +00:00

629 lines
28 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""EPC Prediction synthesis (ADR-0029).
`EpcPrediction.predict` turns the selected `ComparableProperties` into a
predicted `EpcPropertyData`: copy a coherent representative template's structure
(building parts, windows, geometry), set the homogeneous categoricals to the
recency-weighted cohort mode, then apply Landlord Overrides on top. Pure domain
logic — deterministic neighbour synthesis, not ML.
"""
from __future__ import annotations
import copy
import math
import statistics
from collections import Counter, defaultdict
from dataclasses import dataclass
from datetime import date
from typing import Callable, Iterable, Optional, Union
from datatypes.epc.domain.epc_property_data import (
BuildingPartIdentifier,
EpcPropertyData,
MainHeatingDetail,
SapBuildingPart,
)
from domain.epc_prediction.comparable_properties import (
ComparableProperty,
ComparableProperties,
)
from domain.epc_prediction.prediction_target import PredictionTarget
from domain.geospatial.coordinates import Coordinates
@dataclass(frozen=True)
class PredictionConfidence:
"""A compute-only confidence signal for a prediction (ADR-0029 open item).
`cohort_size` is the number of ComparableProperty Properties the prediction drew on;
`component_agreement` maps a homogeneous component to the cohort's *agreement*
— the modal value's share (0..1) of the neighbours that lodge one. A small or
split cohort flags a component downstream may want to treat cautiously (e.g.
the per-dwelling fields with a low accuracy ceiling). Surfacing / persisting
this is a separate HITL follow-up; here it is computed only.
"""
cohort_size: int
component_agreement: dict[str, float]
def agreement(self, component: str) -> Optional[float]:
"""The cohort's modal-value share for a component, or None when no
neighbour lodges one (it was not applicable)."""
return self.component_agreement.get(component)
class EpcPrediction:
"""Synthesises a predicted `EpcPropertyData` from ComparableProperty Properties."""
def predict(
self, target: PredictionTarget, comparables: ComparableProperties
) -> EpcPropertyData:
"""Predict the target's EPC picture: copy a representative template's
structure (coherent for the calculator), set the predicted floor area to
the cohort median (the best point estimate of the target's size, decoupled
from the one template's own area), then set the homogeneous categoricals
to the cohort mode."""
template: ComparableProperty = self._template(comparables)
predicted: EpcPropertyData = copy.deepcopy(template.epc)
predicted.total_floor_area_m2 = _geo_weighted_floor_area(
comparables.members, target.coordinates
)
self._apply_categorical_modes(predicted, comparables, target.coordinates)
self._apply_glazing_mode(predicted, comparables, target.coordinates)
self._apply_ventilation_mode(predicted, comparables, target.coordinates)
self._apply_heating_donor(predicted, comparables)
self._apply_overrides(predicted, target)
return predicted
@staticmethod
def _apply_heating_donor(
predicted: EpcPropertyData, comparables: ComparableProperties
) -> None:
"""Replace the structural template's heating with a coherent donor's whole
`SapHeating` cluster (ADR-0029; issue #1225). Heating sub-fields can't be
field-moded without breaking system coherence (e.g. a fuel that doesn't
match the emitter), so the cluster is copied as a unit from a single
neighbour: the one matching the cohort's modal heating *signature* (main
fuel + category + cylinder presence), the most recent among those matches
(a recent cert reflects the current system). This makes the predicted
heating both representative and internally coherent, rather than whatever
the size-representative template happened to carry. No donor (no neighbour
lodges a main heating system) leaves the template's heating in place.
The coherent heating system spans more than `sap_heating` (ADR-0035): its
electricity tariff (`sap_energy_source.meter_type`) and hot-water flags
live on loose top-level fields. Carry the donor's whole set, not a subset
— otherwise a donated storage system lands on the template's single-rate
meter and the SAP score collapses (off-peak heat billed at the peak rate).
The system also has a DISPLAY face — the building-passport "Main Heating"
and "Heating Control" rows (`main_heating` / `main_heating_controls`
EnergyElements). These describe the same system as the calc cluster, so
they travel with the donor too; left on the structural template they are
incoherent with the donated calc heating, and `main_heating_controls`
shows "Unknown" whenever the size-template lodged no control row but the
donor does (predicted property 721167, ADR-0029 follow-up)."""
donor = _heating_donor(comparables.members)
if donor is None:
return
predicted.sap_heating = copy.deepcopy(donor.epc.sap_heating)
predicted.has_hot_water_cylinder = donor.epc.has_hot_water_cylinder
predicted.solar_water_heating = donor.epc.solar_water_heating
predicted.sap_energy_source.meter_type = donor.epc.sap_energy_source.meter_type
predicted.main_heating = copy.deepcopy(donor.epc.main_heating)
predicted.main_heating_controls = copy.deepcopy(
donor.epc.main_heating_controls
)
@staticmethod
def _apply_glazing_mode(
predicted: EpcPropertyData,
comparables: ComparableProperties,
target_coordinates: Optional[Coordinates],
) -> None:
"""Set every window's glazing type to the recency- and geo-weighted cohort
mode. Glazing is retrofitted over a dwelling's life (single → double), so
a recent neighbour reflects the current state (recency, like roof
insulation); it also varies geographically (retrofit waves by street), so
a nearer neighbour counts for more. NOT the plain mode (which regressed)
or the template copy. The window geometry (size, count) is left on the
template; only the glazing categorical moves."""
members = comparables.members
weights = _combine(
_recency_weights(members), _geo_weights(target_coordinates, members)
)
glazing = _weighted_mode(
(_comparable_modal_glazing(c) for c in members), weights
)
if glazing is None:
return
for window in predicted.sap_windows:
window.glazing_type = glazing
@staticmethod
def _apply_ventilation_mode(
predicted: EpcPropertyData,
comparables: ComparableProperties,
target_coordinates: Optional[Coordinates],
) -> None:
"""Set the predicted mechanical-ventilation kind to the recency- and
geo-weighted cohort mode. A mechanical system (MEV/MVHR) is a new-build /
retrofit feature that clusters by era and street (like glazing), so a
recent, near neighbour is the best signal for the target's system; the
size-representative structural template is not (it just happens to carry
whatever its own cert lodged). Only the kind moves — the rest of the
template's `sap_ventilation` (e.g. `sheltered_sides`, derived from
built_form) stays. A natural-vent cohort modes to None and is left
natural — the §2 cascade default — so this only moves genuine MEV/MVHR
neighbourhoods (ADR-0029 follow-up; predicted property 721167)."""
ventilation = predicted.sap_ventilation
if ventilation is None:
return
members = comparables.members
weights = _combine(
_recency_weights(members), _geo_weights(target_coordinates, members)
)
kind = _weighted_mode(
(_comparable_ventilation_kind(c) for c in members), weights
)
if kind is None:
return
ventilation.mechanical_ventilation_kind = kind
def confidence(
self, comparables: ComparableProperties
) -> PredictionConfidence:
"""Compute the per-prediction confidence from the cohort: its size plus,
for each homogeneous categorical, the modal value's share among the
neighbours that lodge one (ADR-0029). Compute-only — it never alters the
prediction, only annotates how much the cohort agreed."""
members: tuple[ComparableProperty, ...] = comparables.members
agreement: dict[str, float] = {}
for attr in _MAIN_PART_CATEGORICALS:
share: Optional[float] = _modal_share(
_main_part_attr(c, attr) for c in members
)
if share is not None:
agreement[attr] = share
for attr in _FLOOR_DIM_CATEGORICALS:
floor_share: Optional[float] = _modal_share(
_main_floor_attr(c, attr) for c in members
)
if floor_share is not None:
agreement[attr] = floor_share
return PredictionConfidence(
cohort_size=len(members), component_agreement=agreement
)
@staticmethod
def _template(comparables: ComparableProperties) -> ComparableProperty:
"""The representative comparable whose structure seeds the prediction:
the member whose floor area is closest to the cohort median. A single
neighbour's geometry is copied wholesale, so a size-representative
template keeps the prediction off the cohort's size outliers (ADR-0029
decision 4: closest on size).
The template must also present a MAIN building part: its structure is
copied wholesale, so seeding from a member whose only part is OTHER (the
gov API lodged its identifier as null) gives a prediction with no main
dwelling — which the modelling handler then rejects as not-predictable,
discarding an otherwise-rich cohort. Candidates are therefore restricted
to the MAIN-bearing members; the median is still taken over the whole
cohort (the size centre is a property of the cohort, not the template
pool). When no member is MAIN-bearing the whole same-type cohort is
unlabelled, so the closest-on-size fallback is left for that guard to
reject rather than silently relabelling real data."""
members: tuple[ComparableProperty, ...] = comparables.members
median_area: float = statistics.median(
c.epc.total_floor_area_m2 for c in members
)
candidates: list[ComparableProperty] = [
c for c in members if _has_main_part(c)
] or list(members)
return min(
candidates,
key=lambda c: abs(c.epc.total_floor_area_m2 - median_area),
)
@staticmethod
def _apply_categorical_modes(
predicted: EpcPropertyData,
comparables: ComparableProperties,
target_coordinates: Optional[Coordinates],
) -> None:
"""Override the predicted picture's homogeneous categoricals — wall /
roof / floor construction + insulation, age band — with the cohort mode
(robust to an atypical template, per ADR-0029 decision 4). The mode is
physically-similarity-weighted (decision 5): each neighbour's vote decays
with its distance from the cohort's physical centre, so the mode leans on
the most representative neighbours. The components that vary
*geographically* — age band, wall construction, floor construction (homes
built together cluster) — additionally take a geo-proximity weight, so a
nearer neighbour counts for more; the rest (e.g. roof construction, which
showed no geo signal) do not. The template still supplies the geometry;
only the categorical codes move to the mode."""
if not predicted.sap_building_parts:
return
main: SapBuildingPart = predicted.sap_building_parts[0]
members = comparables.members
similarity: list[float] = _similarity_weights(members)
geo: list[float] = _geo_weights(target_coordinates, members)
similarity_geo: list[float] = _combine(similarity, geo)
for attr in _MAIN_PART_CATEGORICALS:
if attr in _RECENCY_WEIGHTED_CATEGORICALS:
mode = _recency_weighted_mode(members, attr)
else:
weights = (
similarity_geo
if attr in _GEO_WEIGHTED_CATEGORICALS
else similarity
)
mode = _weighted_mode(
(_main_part_attr(c, attr) for c in members), weights
)
if mode is not None:
setattr(main, attr, mode)
floor_dims = main.sap_floor_dimensions
if floor_dims:
for attr in _FLOOR_DIM_CATEGORICALS:
floor_weights = (
similarity_geo
if attr in _GEO_WEIGHTED_CATEGORICALS
else similarity
)
floor_mode = _weighted_int_mode(
(_main_floor_attr(c, attr) for c in members), floor_weights
)
if floor_mode is not None:
setattr(floor_dims[0], attr, floor_mode)
@staticmethod
def _apply_overrides(
predicted: EpcPropertyData, target: PredictionTarget
) -> None:
"""Apply the known Landlord Overrides on top of the estimate — a known
value always wins over the cohort mode (ADR-0029)."""
if not predicted.sap_building_parts:
return
if target.wall_construction is not None:
predicted.sap_building_parts[0].wall_construction = (
target.wall_construction
)
# The homogeneous categoricals carried directly on the main building part. Floor
# categoricals live on the main floor dimension and glazing on the windows; both
# are handled separately.
_MAIN_PART_CATEGORICALS: tuple[str, ...] = (
"wall_construction",
"wall_insulation_type",
"construction_age_band",
"roof_construction",
"roof_insulation_thickness",
)
# Integer-coded categoricals on the main building part's ground-floor dimension.
_FLOOR_DIM_CATEGORICALS: tuple[str, ...] = (
"floor_construction",
"floor_insulation",
)
# Categoricals whose physical value CHANGES over time (e.g. loft top-ups), so a
# recent neighbour reflects the current state better than an old one — these take
# a recency-WEIGHTED mode. Permanent categoricals (wall / age) take the plain
# mode: recency-weighting them was net-negative on the validation corpus (it
# discards data that is still valid). `_RECENCY_TAU_YEARS` is the exponential
# decay constant (≈2.8-year half-life), chosen on the corpus (roof insulation
# +4pp / +12pp on the fixture).
_RECENCY_WEIGHTED_CATEGORICALS: frozenset[str] = frozenset(
{"roof_insulation_thickness"}
)
_RECENCY_TAU_YEARS: float = 4.0
_DAYS_PER_YEAR: float = 365.0
# Physical-similarity weighting of the categorical mode (ADR-0029 decision 5): a
# comparable's vote decays exponentially with how far it sits from the cohort's
# physical centre — floor area from the median, construction age from the modal
# band — so an outlier-sized or outlier-era neighbour can't sway the mode. Scales
# chosen on the validation corpus (wall-insulation +2.8pp / roof +1.1pp /
# floor-construction +2.4pp / floor-insulation +1.2pp; gate-safe, no regression).
_SIMILARITY_SIZE_SCALE_M2: float = 20.0
_SIMILARITY_AGE_WEIGHT: float = 0.5
_AGE_BAND_ORDER: str = "ABCDEFGHIJKL"
# Geo-proximity weighting (#1227): a neighbour's vote decays with its haversine
# distance to the target, so a closer neighbour counts for more. Applied only to
# the components that showed a clear distance signal in the corpus — age band,
# wall + floor construction, glazing (homes built / retrofitted together cluster);
# roof construction showed no decay, so it is excluded. `_GEO_SCALE_KM` is the
# kernel length-scale (chosen on the corpus). Off when the target has no
# coordinates; neutral for a neighbour with none (never penalised for missing
# data). floor_construction lives on the floor dimension but shares this set.
_GEO_SCALE_KM: float = 0.1
_GEO_WEIGHTED_CATEGORICALS: frozenset[str] = frozenset(
{"construction_age_band", "wall_construction", "floor_construction"}
)
def _main_part_attr(
comparable: ComparableProperty, attr: str
) -> Optional[Union[int, str]]:
parts: list[SapBuildingPart] = comparable.epc.sap_building_parts
return getattr(parts[0], attr) if parts else None
def _main_floor_attr(comparable: ComparableProperty, attr: str) -> Optional[int]:
parts: list[SapBuildingPart] = comparable.epc.sap_building_parts
if not parts:
return None
dims = parts[0].sap_floor_dimensions
value: Optional[int] = getattr(dims[0], attr) if dims else None
return value
def _has_main_part(comparable: ComparableProperty) -> bool:
"""Whether a comparable carries a MAIN building part — i.e. it can seed a
prediction that presents a main dwelling (see `EpcPrediction._template`)."""
return any(
part.identifier is BuildingPartIdentifier.MAIN
for part in comparable.epc.sap_building_parts
)
def _geo_weighted_floor_area(
members: tuple[ComparableProperty, ...],
target_coordinates: Optional[Coordinates],
) -> float:
"""The cohort's geo-proximity-weighted median floor area — the point estimate
of the target's size. The median minimises mean absolute deviation, so it is
the best single guess for an unknown neighbour's area; geo-weighting it leans
the estimate toward the nearer neighbours, because homes built together share
a footprint (the same street signal that already weights age / wall, #1227).
Reduces exactly to the plain median when geo weighting is off (no target
coordinates ⇒ uniform weights), preserving the MAD-minimising guarantee. Set
independently of the structural template (the calculator derives heat loss
from the building-part geometry, not this scalar, so the two need not agree)."""
weights: list[float] = _geo_weights(target_coordinates, members)
return _weighted_median(
[
(comparable.epc.total_floor_area_m2, weight)
for comparable, weight in zip(members, weights)
]
)
def _weighted_median(values_weights: list[tuple[float, float]]) -> float:
"""The weighted median of (value, weight) pairs: the smallest value at which
the cumulative weight reaches half the total. When a value's weight splits the
total exactly in half, the two straddling values are averaged — so with
uniform weights this reduces exactly to `statistics.median` (including the
even-count midpoint average). Assumes a non-empty input."""
ordered: list[tuple[float, float]] = sorted(values_weights)
half: float = sum(weight for _, weight in ordered) / 2
cumulative: float = 0.0
for index, (value, weight) in enumerate(ordered):
cumulative += weight
if cumulative > half:
return value
if cumulative == half and index + 1 < len(ordered):
return (value + ordered[index + 1][0]) / 2
return ordered[-1][0]
def _age_band_index(comparable: ComparableProperty) -> Optional[int]:
"""The main building part's construction-age-band position (A=0 … L=11), or
None when no recognisable band is lodged."""
band = _main_part_attr(comparable, "construction_age_band")
if isinstance(band, str) and band in _AGE_BAND_ORDER:
return _AGE_BAND_ORDER.index(band)
return None
def _similarity_weights(members: tuple[ComparableProperty, ...]) -> list[float]:
"""A physical-similarity weight per comparable (ADR-0029 decision 5): the
product of an exponential decay in its floor-area distance from the cohort
median and in its age-band distance from the cohort's modal band. A neighbour
missing a size or age contributes a neutral weight on that axis, so it is
never penalised for absent data. Aligned with `members` index-for-index."""
if not members:
return []
median_area: float = statistics.median(
c.epc.total_floor_area_m2 for c in members
)
age_indices: list[Optional[int]] = [_age_band_index(c) for c in members]
present_ages: list[int] = [i for i in age_indices if i is not None]
modal_age: Optional[float] = (
statistics.median(present_ages) if present_ages else None
)
weights: list[float] = []
for comparable, age_index in zip(members, age_indices):
size_term: float = math.exp(
-abs(comparable.epc.total_floor_area_m2 - median_area)
/ _SIMILARITY_SIZE_SCALE_M2
)
age_term: float = (
math.exp(-_SIMILARITY_AGE_WEIGHT * abs(age_index - modal_age))
if modal_age is not None and age_index is not None
else 1.0
)
weights.append(size_term * age_term)
return weights
def _weighted_mode(
values: Iterable[Optional[Union[int, str]]], weights: list[float]
) -> Optional[Union[int, str]]:
"""The value with the greatest total similarity weight (ties broken by first
appearance, matching `_mode`), or None when no non-None value is present."""
totals: dict[Union[int, str], float] = defaultdict(float)
for value, weight in zip(values, weights):
if value is not None:
totals[value] += weight
if not totals:
return None
return max(totals, key=lambda value: totals[value])
def _weighted_int_mode(
values: Iterable[Optional[int]], weights: list[float]
) -> Optional[int]:
"""`_weighted_mode` narrowed to int-coded fields (keeps pyright strict happy
when the target attribute is typed `Optional[int]`)."""
totals: dict[int, float] = defaultdict(float)
for value, weight in zip(values, weights):
if value is not None:
totals[value] += weight
if not totals:
return None
return max(totals, key=lambda value: totals[value])
def _modal_share(
values: Iterable[Optional[Union[int, str]]],
) -> Optional[float]:
"""The most common value's share of the present (non-None) values — a 0..1
measure of how much the cohort agrees — or None when none are present."""
present = [v for v in values if v is not None]
if not present:
return None
modal_count: int = Counter(present).most_common(1)[0][1]
return modal_count / len(present)
def _combine(left: list[float], right: list[float]) -> list[float]:
"""Element-wise product of two aligned weight vectors (compose weighting
factors, e.g. similarity × geo-proximity)."""
return [a * b for a, b in zip(left, right)]
def _haversine_km(origin: Coordinates, point: Coordinates) -> float:
"""Great-circle distance in km between two WGS84 points."""
radius_km = 6371.0
lat1, lat2 = math.radians(origin.latitude), math.radians(point.latitude)
delta_lat = lat2 - lat1
delta_lon = math.radians(point.longitude - origin.longitude)
h = (
math.sin(delta_lat / 2) ** 2
+ math.cos(lat1) * math.cos(lat2) * math.sin(delta_lon / 2) ** 2
)
return 2 * radius_km * math.asin(min(1.0, math.sqrt(h)))
def _geo_weights(
target: Optional[Coordinates], members: tuple[ComparableProperty, ...]
) -> list[float]:
"""A geo-proximity weight per comparable — an exponential decay in haversine
distance to the target. All-neutral (1.0) when the target has no coordinates
(geo weighting off) or a neighbour has none (never penalised for absent
data); aligned with `members` index-for-index."""
if target is None:
return [1.0] * len(members)
weights: list[float] = []
for comparable in members:
coordinates = comparable.coordinates
if coordinates is None:
weights.append(1.0)
else:
weights.append(
math.exp(-_haversine_km(target, coordinates) / _GEO_SCALE_KM)
)
return weights
def _recency_weights(members: tuple[ComparableProperty, ...]) -> list[float]:
"""A recency weight per comparable — exponential decay in the cert's age
relative to the newest in the cohort, so newer neighbours dominate. All-equal
when no registration dates are lodged. Aligned with `members`."""
newest: date = max(
(c.registration_date or date.min for c in members), default=date.min
)
return [
math.exp(
-((newest - (c.registration_date or date.min)).days / _DAYS_PER_YEAR)
/ _RECENCY_TAU_YEARS
)
for c in members
]
def _recency_weighted_choice(
members: tuple[ComparableProperty, ...],
value_of: Callable[[ComparableProperty], Optional[Union[int, str]]],
) -> Optional[Union[int, str]]:
"""The recency-weighted cohort mode of a per-comparable value: each
neighbour's vote decays exponentially with the cert's age relative to the
newest in the cohort, so newer neighbours dominate and a stale majority can't
outvote the current state. Falls back to a plain mode when no registration
dates are lodged (all ages 0 ⇒ equal weight). Returns None when no comparable
supplies a value. Used for the time-varying components — those upgraded over a
dwelling's life (loft top-ups)."""
return _weighted_mode(
(value_of(comparable) for comparable in members),
_recency_weights(members),
)
def _recency_weighted_mode(
members: tuple[ComparableProperty, ...], attr: str
) -> Optional[Union[int, str]]:
"""`_recency_weighted_choice` over a main building-part attribute."""
return _recency_weighted_choice(
members, lambda comparable: _main_part_attr(comparable, attr)
)
def _comparable_modal_glazing(
comparable: ComparableProperty,
) -> Optional[Union[int, str]]:
"""A comparable's modal glazing type — the most common across its windows, or
None when it lodges none. One glazing signal per neighbour, robust to a single
odd window, matching how the harness scores `modal_glazing_type`."""
types = [window.glazing_type for window in comparable.epc.sap_windows]
return Counter(types).most_common(1)[0][0] if types else None
def _comparable_ventilation_kind(
comparable: ComparableProperty,
) -> Optional[str]:
"""A comparable's mechanical-ventilation kind (the `MechanicalVentilationKind`
enum name, e.g. "MVHR"), or None when it lodges no system (natural)."""
ventilation = comparable.epc.sap_ventilation
return ventilation.mechanical_ventilation_kind if ventilation is not None else None
def _main_heating_detail(comparable: ComparableProperty) -> Optional[MainHeatingDetail]:
"""The primary heating system's detail row, or None when none is lodged."""
details = comparable.epc.sap_heating.main_heating_details
return details[0] if details else None
def _heating_signature(
comparable: ComparableProperty,
) -> Optional[tuple[Union[int, str], Optional[int], bool]]:
"""The donor-matching signature — main fuel + heating category + cylinder
presence: the coarse identity of the heating system. None when no main heating
system is lodged, so the comparable is not a donor candidate."""
detail = _main_heating_detail(comparable)
if detail is None:
return None
return (
detail.main_fuel_type,
detail.main_heating_category,
comparable.epc.has_hot_water_cylinder,
)
def _heating_donor(members: tuple[ComparableProperty, ...]) -> Optional[ComparableProperty]:
"""The coherent heating donor: the comparable whose heating signature is the
cohort mode, breaking ties toward the most recent cert (then certificate
number, for determinism). None when no neighbour lodges a heating system."""
signed = [(c, _heating_signature(c)) for c in members]
signatures = [sig for _, sig in signed if sig is not None]
if not signatures:
return None
modal = Counter(signatures).most_common(1)[0][0]
matches = [c for c, sig in signed if sig == modal]
return max(
matches,
key=lambda c: (c.registration_date or date.min, c.certificate_number),
)