Source code for qfa.domain.ports

"""Port interfaces (protocols) for the feedback analysis backend.

Driven ports declared here use ``typing.Protocol`` for structural
subtyping per ADR-002. The orchestrator is exposed as the concrete
``Orchestrator`` class per ADR-011 (no driving port).
"""

import datetime as dt
from typing import Protocol

from qfa.domain.models import (
    LLMCallRecord,
    LLMResponse,
    T_Response,
    UsageStats,
)


[docs] class LLMPort(Protocol): """Port for interacting with a large-language-model provider. Implementations must translate provider-specific details into the domain ``LLMResponse`` model. """
[docs] async def complete( self, system_message: str, user_message: str, tenant_id: str, response_model: type[T_Response], timeout: float = 20.0, ) -> LLMResponse[T_Response]: """Send a completion request to the LLM provider. Parameters ---------- system_message : str The system-level instruction for the model. user_message : str The user-level message to complete. tenant_id : str Tenant identifier for tracking and billing. response_model : type[T_Response] The Pydantic model to parse the response into. timeout : float Maximum time in seconds to wait for a response. Returns ------- LLMResponse The model's response including token usage. """ ...
[docs] class UsageRepositoryPort(Protocol): """Port for recording and querying LLM usage data."""
[docs] async def record_call(self, record: LLMCallRecord) -> None: """Record a single LLM call attempt. Parameters ---------- record : LLMCallRecord The call record to persist. """ ...
[docs] async def get_usage_stats( self, tenant_id: str, from_: dt.datetime | None = None, to: dt.datetime | None = None, ) -> UsageStats: """Get aggregated usage stats for a single tenant. Parameters ---------- tenant_id : str The tenant to query. from_ : datetime | None Inclusive lower bound (UTC tz-aware), or None. to : datetime | None Exclusive upper bound (UTC tz-aware), or None. Returns ------- UsageStats | None Stats for the tenant, or None if no calls in window. """ ...
[docs] async def get_all_usage_stats( self, from_: dt.datetime | None = None, to: dt.datetime | None = None, ) -> list[UsageStats]: """Get per-tenant stats plus a grand total entry (tenant_id=None). Parameters ---------- from_ : datetime | None Inclusive lower bound (UTC tz-aware), or None. to : datetime | None Exclusive upper bound (UTC tz-aware), or None. Returns ------- list[UsageStats] Per-tenant stats followed by a grand total entry. """ ...
[docs] class AnonymizationPort(Protocol): """Port for anonymising and de-anonymising user-supplied text. Implementations replace named entities (people, locations, phone numbers, etc.) in ``text`` with stable placeholders, returning the redacted text together with a mapping that can be used to restore the original values via ``deanonymize``. Implementations must be deterministic for a given input within a single call (same entity replaced by the same placeholder). """
[docs] def anonymize(self, text: str) -> tuple[str, dict[str, str]]: """Replace sensitive entities in ``text`` with placeholders. Parameters ---------- text : str The text to anonymise. Returns ------- tuple[str, dict[str, str]] The anonymised text and a mapping from placeholder to original value, suitable for passing to ``deanonymize``. """ ...
[docs] def deanonymize(self, text: str, mapping: dict[str, str]) -> str: """Restore original values in ``text`` using ``mapping``. Parameters ---------- text : str The anonymised text, possibly containing placeholders. mapping : dict[str, str] Placeholder-to-original mapping returned by ``anonymize``. Returns ------- str The text with placeholders replaced by original values. """ ...