mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
Let a deterministic guard override the fallback classifier per description 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
fe663deef5
commit
4396e5fb22
3 changed files with 76 additions and 0 deletions
31
domain/data_transformation/guarded_column_classifier.py
Normal file
31
domain/data_transformation/guarded_column_classifier.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
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]:
|
||||
raise NotImplementedError
|
||||
0
tests/domain/data_transformation/__init__.py
Normal file
0
tests/domain/data_transformation/__init__.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
from domain.data_transformation.column_classifier import ColumnClassifier
|
||||
from domain.data_transformation.guarded_column_classifier import (
|
||||
GuardedColumnClassifier,
|
||||
)
|
||||
|
||||
|
||||
class _Category(Enum):
|
||||
GUARDED = "guarded"
|
||||
FALLBACK = "fallback"
|
||||
UNKNOWN = "unknown"
|
||||
|
||||
|
||||
class _RecordingFallback(ColumnClassifier[_Category]):
|
||||
"""Stands in for the LLM: records what it was asked and maps everything it
|
||||
sees to FALLBACK, so the test can see which descriptions reached it."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.asked: set[str] = set()
|
||||
|
||||
def classify(self, descriptions: set[str]) -> dict[str, _Category]:
|
||||
self.asked = set(descriptions)
|
||||
return {d: _Category.FALLBACK for d in descriptions}
|
||||
|
||||
|
||||
def _guard(description: str) -> Optional[_Category]:
|
||||
return _Category.GUARDED if description == "marker" else None
|
||||
|
||||
|
||||
def test_guard_hits_win_and_misses_fall_through_to_the_fallback() -> None:
|
||||
# Arrange
|
||||
fallback = _RecordingFallback()
|
||||
classifier = GuardedColumnClassifier(guard=_guard, fallback=fallback)
|
||||
|
||||
# Act
|
||||
result = classifier.classify({"marker", "other"})
|
||||
|
||||
# Assert — the guarded description takes the guard's member and never reaches
|
||||
# the fallback; the unrecognised one is resolved by the fallback.
|
||||
assert result == {"marker": _Category.GUARDED, "other": _Category.FALLBACK}
|
||||
assert fallback.asked == {"other"}
|
||||
Loading…
Add table
Reference in a new issue