Files
2026-10-27 14:03:35 +08:00

71 lines
3.7 KiB
Diff

--- server/app/adminapi/service/AssistantSseTiming.php.before
+++ server/app/adminapi/service/AssistantSseTiming.php
@@ -17,6 +17,7 @@
'source_bytes', 'query_bytes', 'attachment_count',
'delta_bytes', 'delta_count', 'first_delta_ms', 'inputs_bytes',
'source_ms', 'upload_ms', 'ocr_ms', 'reuse_wait_ms', 'upload_cache_hits', 'upload_cache_misses',
+ 'clinical_issue_count', 'clinical_repair_count',
];
private const BOOLEAN_FIELDS = ['compaction_attempted', 'retry_attempted', 'first_delta_observed', 'compaction_cache_hit'];
@@ -29,7 +30,27 @@
'STREAM_DELIVERY_FAILED', 'REQUEST_BUILD_FAILED', 'CURL_INIT_FAILED',
'UPSTREAM_BUSY', 'INVALID_RESPONSE', 'AI_ASSISTANT_CANCELLED',
'TCM_FACT_STATE_CONFLICT', 'TCM_REPORT_INCOMPLETE', 'TASK_ANALYSIS_MISSING',
- 'STREAM_CONTENT_MISMATCH', 'UPSTREAM_CLEANUP_PENDING',
+ 'STREAM_CONTENT_MISMATCH', 'UPSTREAM_CLEANUP_PENDING', 'PULSE_EXCLUDED_FROM_ASSESSMENT',
+ ];
+
+ /** Exact issue enums emitted by the consultation stream's local guards. */
+ private const CLINICAL_ISSUE_CODES = [
+ 'FACT_STATE_LEDGER_UNAVAILABLE', 'FACT_STATE_INVALID_UTF8', 'SOURCE_ANNOTATION_INVALID',
+ 'UNSUPPORTED_NEGATIVE_REFERENCE', 'ATTRIBUTED_SOURCE_QUOTE_INVALID',
+ 'UNKNOWN_TO_NEGATIVE', 'UNKNOWN_TO_POSITIVE', 'POSITIVE_TO_NEGATIVE', 'NEGATIVE_TO_POSITIVE',
+ 'NEGATION_SCOPE_WIDENED', 'NEGATIVE_NOT_IN_SOURCE', 'NEGATIVE_TO_UNKNOWN',
+ 'POSITIVE_TO_UNKNOWN', 'RECORDED_TO_UNKNOWN', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE',
+ 'PULSE_FEATURE_NOT_IN_CURRENT_SOURCE',
+ 'CONSULTATION_INVALID_UTF8', 'PULSE_EXCLUDED_FROM_ASSESSMENT',
+ 'EVIDENCE_INDEX_UNAVAILABLE', 'EVIDENCE_INVALID_UTF8', 'EXAM_ROLE_MISSING',
+ 'EXAM_HEADING_METADATA_UNSUPPORTED', 'EXAM_FACT_PROTOCOL_INVALID',
+ 'EXAM_NEGATIVE_HEADING_UNSUPPORTED', 'LONGITUDINAL_FACT_PROTOCOL_INVALID',
+ 'EXAM_KNOWN_ROLE_TO_UNKNOWN', 'EVIDENCE_SOURCE_DATE_MISMATCH',
+ 'LONGITUDINAL_SOURCE_NOT_HISTORICAL', 'EXAM_SOURCE_QUOTE_INVALID',
+ 'EXAM_SOURCE_NOT_CURRENT', 'EXAM_ROLE_MISMATCH', 'DENIAL_SOURCE_QUOTE_INVALID',
+ 'UNQUOTED_PATIENT_DENIAL', 'HERBAL_PREMISE_SELF_CONFLICT',
+ 'REPORT_BOUND_EXCEEDED', 'REPORT_FINAL_CONTRACT_INVALID',
+ 'REPORT_CONTINUATION_REPEATED_SECTION', 'REPORT_FINAL_SECTION_UNDELIVERED',
];
private string $traceId;
@@ -52,11 +73,11 @@
}
/**
- * Only a fixed set of numeric and boolean metadata reaches the log. In particular,
+ * Only fixed numeric, boolean and enum metadata reaches the log. In particular,
* arbitrary prompt text, patient identifiers, URLs and credentials are discarded.
*
* @param array<string,mixed> $metrics
- * @return array<string,int|bool|string>
+ * @return array<string,int|bool|string|list<string>>
*/
public function event(string $phase, array $metrics = []): array
{
@@ -81,6 +102,16 @@
}
if (isset($metrics['error_code']) && in_array($metrics['error_code'], self::ERROR_CODES, true)) {
$event['error_code'] = $metrics['error_code'];
+ }
+ if ($event['phase'] === 'upstream_done' && is_array($metrics['clinical_issue_codes'] ?? null)) {
+ $codes = [];
+ foreach ($metrics['clinical_issue_codes'] as $code) {
+ // Never accept source IDs, raw issue objects or a regex-shaped arbitrary string.
+ if (!is_string($code) || !in_array($code, self::CLINICAL_ISSUE_CODES, true)) continue;
+ $codes[$code] = true;
+ if (count($codes) === 16) break;
+ }
+ $event['clinical_issue_codes'] = array_keys($codes);
}
return $event;
}