feat: add reviewed follow-up audio patient enrichment

This commit is contained in:
2026-09-29 15:44:30 +08:00
parent 3cdc812f2b
commit 70be2fc70f
46 changed files with 7384 additions and 246 deletions
+62
View File
@@ -0,0 +1,62 @@
<?php
/** Loopback HTTP server for synthetic transport tests. Never load app/.env or provider credentials. */
$directory = getenv('FOLLOWUP_AUDIO_MOCK_DIR');
if (!$directory || !is_dir($directory)) { http_response_code(500); exit; }
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$scenario = explode('/', trim($path, '/'))[0];
$isUpload = str_ends_with($path, '/files/upload');
$isChat = str_ends_with($path, '/chat-messages');
$record = ['scenario' => $scenario, 'path' => $path, 'upload' => $isUpload, 'chat' => $isChat];
$authorization = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
if ($authorization !== 'Bearer synthetic-test-key') { http_response_code(401); echo '{}'; exit; }
header('Content-Type: application/json');
header('X-Request-Id: mock-request-' . $scenario);
if ($isUpload) {
$file = $_FILES['file'] ?? [];
$record['user'] = $_POST['user'] ?? '';
$record['filename'] = $file['name'] ?? '';
$record['mime'] = $file['type'] ?? '';
$record['sha256'] = isset($file['tmp_name']) && is_file($file['tmp_name']) ? hash_file('sha256', $file['tmp_name']) : '';
$types = ['followup-audio.wav' => 'audio/wav', 'followup-audio.mp3' => 'audio/mpeg',
'followup-audio.m4a' => 'audio/mp4', 'followup-audio.amr' => 'audio/amr'];
$record['size'] = (int) ($file['size'] ?? 0);
$record['valid'] = ($file['error'] ?? -1) === UPLOAD_ERR_OK && $record['user'] !== ''
&& ($types[$record['filename']] ?? '') === $record['mime'];
file_put_contents($directory . '/requests.jsonl', json_encode($record) . "\n", FILE_APPEND | LOCK_EX);
if (!$record['valid'] || $scenario === 'upload_reject') { http_response_code(415); echo '{"code":"unsupported_file"}'; exit; }
file_put_contents($directory . '/user-' . $scenario, $record['user']);
http_response_code(201);
echo json_encode(['id' => 'mock-upload-' . $scenario, 'mime_type' => $scenario === 'wrong_type' ? 'image/png' : $record['mime']]);
exit;
}
if (!$isChat) { http_response_code(404); echo '{}'; exit; }
$payload = json_decode(file_get_contents('php://input'), true);
$record['payload'] = $payload;
$record['valid'] = ($payload['user'] ?? '') === trim((string) @file_get_contents($directory . '/user-' . $scenario))
&& ($payload['files'] ?? []) === [['type' => 'audio', 'transfer_method' => 'local_file', 'upload_file_id' => 'mock-upload-' . $scenario]]
&& ($payload['response_mode'] ?? '') === 'blocking';
file_put_contents($directory . '/requests.jsonl', json_encode($record, JSON_UNESCAPED_UNICODE) . "\n", FILE_APPEND | LOCK_EX);
if (!$record['valid']) { http_response_code(400); echo '{"code":"attachment_required"}'; exit; }
if ($scenario === 'chat_reject') { http_response_code(415); echo '{"code":"unsupported_audio","message":"private-body-must-not-escape"}'; exit; }
if ($scenario === 'unknown') { http_response_code(524); echo '{"message":"private-body-must-not-escape","task_id":"mock-uncertain-task","conversation_id":"mock-uncertain-conversation"}'; exit; }
if ($scenario === 'timeout') { sleep(3); echo '{}'; exit; }
if ($scenario === 'malformed_response') { echo '<html>private-body-must-not-escape</html>'; exit; }
$transcript = '昨天晚上九点,收缩压126,舒张压82。紫色海豚水晶风车。';
$raw = ['schema_version' => 'followup-audio-v1', 'audio_processed' => true,
'summary' => '合成血压事实', 'transcript' => $transcript, 'uncertainties' => [], 'items' => [[
'kind' => 'blood', 'values' => ['systolic_pressure' => 126, 'diastolic_pressure' => 82],
'record_date' => null, 'record_time' => '21:00:00', 'time_period' => null, 'time_estimated' => false,
'date_text' => '昨天', 'time_text' => '晚上九点', 'evidence' => [['text' => '昨天晚上九点,收缩压126,舒张压82。', 'start_ms' => 0, 'end_ms' => 500]],
'needs_review' => false,
]]];
if ($scenario === 'text_only') { $raw['audio_processed'] = false; }
if ($scenario === 'evidence') { $raw['items'][0]['evidence'][0]['text'] = '音频里根本没有说过的事实'; }
if ($scenario === 'extra_field') { $raw['items'][0]['target_id'] = 999; }
if ($scenario === 'bad_values') { $raw['items'][0]['values']['unknown_field'] = 'malicious'; }
if ($scenario === 'bad_time') { $raw['items'][0]['evidence'][0]['end_ms'] = 3600001; }
if ($scenario === 'schema') { unset($raw['transcript']); }
$answer = $scenario === 'markdown' ? '```json\n' . json_encode($raw) . '\n```' : json_encode($raw, JSON_UNESCAPED_UNICODE);
$response = ['answer' => $answer, 'task_id' => 'mock-task-' . $scenario, 'message_id' => 'mock-message-' . $scenario,
'conversation_id' => 'mock-conversation-' . $scenario];
if ($scenario === 'omitted') { $response['metadata']['omitted_files'] = [['type' => 'audio']]; }
echo json_encode($response, JSON_UNESCAPED_UNICODE);