mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
42 lines
1.5 KiB
Python
42 lines
1.5 KiB
Python
from __future__ import annotations
|
|
|
|
from enum import Enum
|
|
from typing import Callable, Optional, TypeVar
|
|
|
|
from domain.data_transformation.column_classifier import ColumnClassifier
|
|
|
|
E = TypeVar("E", bound=Enum)
|
|
|
|
|
|
class GuardedColumnClassifier(ColumnClassifier[E]):
|
|
"""A ``ColumnClassifier`` that resolves the descriptions a deterministic guard
|
|
is certain about, and delegates the rest to a fallback classifier.
|
|
|
|
The ``guard`` maps a raw description to a category member when it recognises it
|
|
deterministically (e.g. a party-ceiling roof marker — #1376), else ``None``.
|
|
Guard hits never reach the fallback, so an unreliable classifier (the LLM)
|
|
cannot override a description the guard is sure of — and the LLM is not billed
|
|
for it. Every description still appears in the result (guarded or fallen-back).
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
guard: Callable[[str], Optional[E]],
|
|
fallback: ColumnClassifier[E],
|
|
) -> None:
|
|
self._guard = guard
|
|
self._fallback = fallback
|
|
|
|
def classify(self, descriptions: set[str]) -> dict[str, E]:
|
|
guarded: dict[str, E] = {}
|
|
misses: set[str] = set()
|
|
for description in descriptions:
|
|
member = self._guard(description)
|
|
if member is not None:
|
|
guarded[description] = member
|
|
else:
|
|
misses.add(description)
|
|
# Only the misses reach the fallback — a fully-guarded batch never calls it.
|
|
if misses:
|
|
guarded.update(self._fallback.classify(misses))
|
|
return guarded
|