qfa.api.routes#

API route handlers for the feedback analysis backend.

Functions

analyze_bulk(body, request, tenant, ...)

Analyze a batch of feedback records for trends and themes.

assign_codes(body, tenant, coding, _scope)

Assign codes: one one-shot pick call, then a separate judge call per level.

detect_sensitive(body, request, tenant, ...)

Detect sensitive content in feedback items.

health()

Return service health status.

summarize(body, request, tenant, ...)

Summarize submitted feedback record.

summarize_bulk(body, request, tenant, ...)

Summarize all submitted feedback records as a single aggregate summary.

async qfa.api.routes.analyze_bulk(body: ApiAnalyzeRequest, request: Request, tenant: TenantApiKey = Depends(dependency=<function authenticate_request>, use_cache=True, scope=None), analyze_service: AnalyzeService = Depends(dependency=<function get_analyze_service>, use_cache=True, scope=None), _scope: CallContext = Depends(dependency=<function call_scope_for.<locals>._scope>, use_cache=True, scope=None)) ApiAnalyzeBulkResponse[source]#

Analyze a batch of feedback records for trends and themes.

The analyst prompt in body.prompt is wrapped in a structural envelope together with the feedback records, and the model is instructed to treat record text as data, not instructions.

A second LLM call (AI-as-judge) scores the analysis and produces a natural-language uncertainty_explanation the analyst can use to spot unsupported claims. If the judge call fails, the response still returns 200 with quality_score=null and a constant unavailable message in uncertainty_explanation.

Modes:

  • single_pass (default) — one LLM call within the token cap.

  • hierarchical — embed → cluster → map → reduce pipeline for large corpora (> 5x the single-call token cap). Returns an additional confidence field in the response.

The deterministic coding_trends table is populated for both modes — it is built from input metadata and does not depend on the LLM call or chunking. The period request field controls the table’s granularity (day / week / month); omit it to use the server-side default.

Edge cases:

  • Input that exceeds the token cap for single_pass → 413 payload_too_large (use mode=hierarchical for large corpora).

  • Records with empty content are dropped before analysis (a blank EspoCRM description must not fail the whole batch — issue #138). feedback_record_count reflects the records actually analyzed. If every record is empty the response is a 200 with feedback_record_count=0 and a fallback analysis explaining that no analysis was performed.

  • Injection-like text in record content or metadata is neutralised structurally by the envelope; regex-based detection is a separate guard handled by the LLM adapter.

Parameters:
  • body (AnalyzeRequest) – The request body containing feedback records and prompt.

  • request (Request) – The incoming HTTP request.

  • tenant (TenantApiKey) – The authenticated tenant, injected via dependency.

  • analyze_service (AnalyzeService) – The analyze service, injected via dependency.

Returns:

The analysis result with quality score, uncertainty explanation, feedback record count, and request ID. coding_trends is populated for both modes whenever metadata permits; confidence is populated only for hierarchical mode.

Return type:

AnalyzeResponse

async qfa.api.routes.summarize_bulk(body: ApiSummarizeBulkRequest, request: Request, tenant: TenantApiKey = Depends(dependency=<function authenticate_request>, use_cache=True, scope=None), summarize_service: SummarizeService = Depends(dependency=<function get_summarize_service>, use_cache=True, scope=None), _scope: CallContext = Depends(dependency=<function call_scope_for.<locals>._scope>, use_cache=True, scope=None)) ApiSummarizeBulkResponse[source]#

Summarize all submitted feedback records as a single aggregate summary.

Records with empty content are dropped before summarization (a blank EspoCRM description must not fail the whole batch — issue #138). If every record is empty the response is a 200 empty aggregate (blank title, a fallback summary explaining that no analysis was performed, quality_score=0.0).

Parameters:
  • body (ApiSummarizeBulkRequest) – The request body containing feedback records and summarization options.

  • request (Request) – The incoming HTTP request.

  • tenant (TenantApiKey) – The authenticated tenant, injected via dependency.

  • summarize_service (SummarizeService) – The summarisation service, injected via dependency.

Returns:

A single summary with themes ordered by frequency across all feedback records.

Return type:

ApiSummarizeBulkResponse

async qfa.api.routes.summarize(body: ApiSummarizeRequest, request: Request, tenant: TenantApiKey = Depends(dependency=<function authenticate_request>, use_cache=True, scope=None), summarize_service: SummarizeService = Depends(dependency=<function get_summarize_service>, use_cache=True, scope=None), _scope: CallContext = Depends(dependency=<function call_scope_for.<locals>._scope>, use_cache=True, scope=None)) ApiSummarizeResponse[source]#

Summarize submitted feedback record.

If the record’s content is empty the response is a 200 empty summary that still echoes the source id (blank title/summary, quality_score=0.0), returned without an LLM call — a blank EspoCRM description must not produce a silent 422 (issue #138).

Parameters:
  • body (ApiSummarizeRequest) – The request body containing feedback records and summarization options.

  • request (Request) – The incoming HTTP request.

  • tenant (TenantApiKey) – The authenticated tenant, injected via dependency.

  • summarize_service (SummarizeService) – The summarisation service, injected via dependency.

Returns:

The per-feedback-record titles and summaries.

Return type:

ApiSummarizeResponse

async qfa.api.routes.assign_codes(body: ApiAssignCodesRequest, tenant: TenantApiKey = Depends(dependency=<function authenticate_request>, use_cache=True, scope=None), coding: CodingService = Depends(dependency=<function get_coding_service>, use_cache=True, scope=None), _scope: CallContext = Depends(dependency=<function call_scope_for.<locals>._scope>, use_cache=True, scope=None)) ApiAssignCodesResponse[source]#

Assign codes: one one-shot pick call, then a separate judge call per level.

The full coding framework is flattened into one option per node (at every depth, not just leaves) and a single LLM call picks the best-fitting path(s) directly — no recursive per-level picking. Each selected path is then scored by a separate judge call per level, root to leaf, stopping at the first level that falls below confidence_threshold.

assigned_codes is never an empty list. Whenever no code is applied the response is a 200 carrying exactly one entry whose coding_level_*/confidence_* fields are null and whose explanation begins with the line NO CODING APPLIED. followed by the reason (#256), so a client always has something to show the user:

  • the record’s content is empty — returned without an LLM call (issue #138);

  • every candidate was filtered out by confidence_threshold — the explanation names the threshold and lists the closest near misses, highest-scoring first;

  • the classifier selected nothing at all — the explanation states that nothing in the framework was judged relevant.

The explanation is English only, regardless of the feedback language.

async qfa.api.routes.detect_sensitive(body: ApiDetectSensitiveRequest, request: Request, tenant: TenantApiKey = Depends(dependency=<function authenticate_request>, use_cache=True, scope=None), sensitivity_service: SensitivityService = Depends(dependency=<function get_sensitivity_service>, use_cache=True, scope=None), _scope: CallContext = Depends(dependency=<function call_scope_for.<locals>._scope>, use_cache=True, scope=None)) ApiDetectSensitiveResponse[source]#

Detect sensitive content in feedback items.

If the record’s content is empty the response is a 200 reporting is_sensitive=False with no sensitivity_types, returned without an LLM call (issue #138).

Parameters:
  • body (ApiDetectSensitiveRequest) – The request body containing feedback items to check for sensitive content.

  • request (Request) – The incoming HTTP request.

  • tenant (TenantApiKey) – The authenticated tenant, injected via dependency.

  • sensitivity_service (SensitivityService) – The sensitivity-detection service, injected via dependency.

Returns:

Sensitivity rating for each submitted feedback item.

Return type:

ApiDetectSensitiveResponse

async qfa.api.routes.health() ApiHealthResponse[source]#

Return service health status.

Returns:

Health status and package version.

Return type:

HealthResponse