qfa.services.coding#
Coding service — assign hierarchical codes to a feedback record.
Backs POST /v1/assign-codes. Picking is one shot: the full coding
framework is flattened into one option per node (at every depth, not just
leaves), and a single LLM call selects the best-fitting path(s) directly —
no recursive per-level picking. Judging is per level: each selected path is
then scored level by level by a separate judge call per level, stopping at
the first level that falls below confidence_threshold. Candidates below
the threshold are dropped, and the ones that survive are ranked and
truncated to max_codes.
This is the only use case whose pick step selects from the whole flattened framework rather than a fixed sequence of calls, which is why it lives in its own module (ADR-017): its private helpers are used by nothing else.
Per ADR-017 CodingService has no base class. The scaffolding
it shares with the other use cases — the token-budget guard and the
deadline→timeout derivation — comes from the injected
LLMCallExecutor, and everything
else it needs (the LLM connection, the anonymiser) is named explicitly in
its constructor.
Both the pick and the per-level judge run on the primary LLM
connection. The judge/primary split introduced by #258 deliberately
excludes this path, so the service takes no judge client at all — see
tests/services/test_orchestrator_judge_routing.py, which pins the
exclusion as a decision rather than an oversight.
Module attributes
Literal first line of every explanation returned when no code is applied. |
|
Explanation for a record whose |
|
Explanation for when the LLM selected nothing at all, or nothing it selected survived judging. |
Classes
|
Assign hierarchical codes to a feedback record via pick/judge calls. |
- qfa.services.coding.NO_CODING_LEAD = 'NO CODING APPLIED.'#
Literal first line of every explanation returned when no code is applied.
EspoCRM surfaces
assigned_codes.0.explanationverbatim asautoCodingExplanation, so this line is what a user reads first when a record comes back uncoded (#256).
- qfa.services.coding.NO_CODING_EMPTY_CONTENT_EXPLANATION = 'NO CODING APPLIED.\nThe feedback text was empty, so there was nothing to code.'#
Explanation for a record whose
contentis empty (issue #138).
- qfa.services.coding.NO_CODING_NOTHING_RELEVANT_EXPLANATION = 'NO CODING APPLIED.\nNo code in the framework was judged relevant to this feedback.'#
Explanation for when the LLM selected nothing at all, or nothing it selected survived judging.
- class qfa.services.coding.CodingService(llm: LLMPort, anonymizer: AnonymizationPort, executor: LLMCallExecutor)[source]#
Bases:
objectAssign hierarchical codes to a feedback record via pick/judge calls.
- Parameters:
llm (LLMPort) – The LLM provider adapter used for every call this service makes — both the one-shot pick and the per-level judge. There is no second connection: #258 scoped the judge/primary split to the quality-score judges on analyse and summarise.
anonymizer (AnonymizationPort) – The anonymisation adapter used to redact PII from each assembled prompt before it leaves the process.
executor (LLMCallExecutor) – The shared LLM-call scaffolding (ADR-017), used here for the pre-flight token-budget guard and deadline-derived timeout. Injected rather than self-constructed so the composition root stays the one place the object graph is assembled.
- async assign_codes(request: CodingAssignmentRequestModel, deadline: datetime) CodingAssignmentResultModel[source]#
Assign hierarchical codes to a feedback record.
Picking is one shot: the full coding framework is flattened into one option per node (at every depth, not just leaves), and a single LLM call selects the best-fitting path(s) directly — no recursive per-level picking. Judging is unchanged from the per-level design: each selected path is then scored level by level by a separate judge call per level, stopping at the first level that falls below
confidence_threshold, exactly as when picking was also per-level.- Parameters:
request (CodingAssignmentRequest) – Feedback records, coding framework,
max_codes, and tenant id.deadline (datetime) – Absolute UTC deadline by which all records must be coded.
- Returns:
Per-record codes from the judge, ordered by confidence, highest first.
assigned_codesis never empty: when no code is applied it holds exactly one entry with nullcoding_level_*/confidence_*fields and anexplanationleading withNO CODING APPLIED.(#256). That explanation lists the near misses whenconfidence_thresholdfiltered every candidate out, and states that nothing was relevant when nothing was selected at all.- Return type:
CodingAssignmentResult
- Raises:
AnalysisTimeoutError – When
deadlineis reached before every record is processed.AnalysisError – When the judge returns a score outside 0.0-1.0.
LLMTimeoutError – When a single LLM completion exceeds the configured timeout.
LLMRateLimitError – When the LLM provider returns rate limiting.
LLMError – For other LLM provider failures. A pick response that fails schema validation (
LLMResponseParseError) is treated as an empty pick instead of being raised.