where('prescription_id', 7556)->order('id', 'desc')->find(); $context = $cipher->decrypt((string) $batch['context_cipher'], 'context'); $budget = max(6000, min(200000, (int) config('prescription_ai.manual_analysis.input_token_budget', 24000))) - 3500; $units = (new ReflectionMethod(PrescriptionAiGenerator::class, 'sourceUnits'))->invoke(null, $context['source']['records'], $budget); $chunks = (new ReflectionMethod(PrescriptionAiGenerator::class, 'pack'))->invoke(null, $units, $budget); $textPromptBytes = []; foreach ($chunks as $chunk) { $ids = array_values(array_unique(array_column($chunk, 'source_id'))); $prompt = (new ReflectionMethod(PrescriptionAiGenerator::class, 'evidencePrompt'))->invoke(null, 'text', $ids, ['patient' => $context['source']['patient'] ?? [], 'clinical_field_semantics' => $context['source']['clinical_field_semantics'] ?? [], 'records' => $chunk]); $textPromptBytes[] = strlen($prompt); } $attachmentStatuses = array_count_values(array_column($context['files'] ?? [], 'status')); $result = []; foreach (Db::name('prescription_ai_task')->where('batch_id', $batch['id'])->select()->toArray() as $task) { if (empty($task['progress_cipher'])) { continue; } $progress = $cipher->decrypt($task['progress_cipher'], 'progress:' . $task['id']); $item = ['model' => $task['model_key'], 'stage' => $progress['stage'], 'calls' => $progress['usage']['total_calls'], 'steps' => []]; foreach ($progress['steps'] ?? [] as $key => $step) { $content = (string) ($step['value']['content'] ?? ''); $decoded = json_decode(trim($content), true); $jsonError = json_last_error_msg(); $fenced = preg_match('/\A```(?:json)?\s*(.*?)\s*```\z/s', trim($content), $match) === 1; $wrapped = $fenced ? json_decode($match[1], true) : null; $summary = ['step' => $key, 'bytes' => strlen($content), 'json_error' => $jsonError, 'has_think_tag' => str_contains($content, ''), 'whole_json_fence' => $fenced, 'json_keys' => is_array($decoded) ? array_keys($decoded) : null, 'fenced_json_keys' => is_array($wrapped) ? array_keys($wrapped) : null]; if (str_starts_with($key, 'text:')) { $expected = array_values(array_unique(array_column($chunks[(int) substr($key, 5)], 'source_id'))); $summary['expected_source_count'] = count($expected); $summary['raw_valid'] = (new ReflectionMethod(PrescriptionAiGenerator::class, 'parseEvidence'))->invoke(null, $content, $expected) !== null; $summary['fenced_valid'] = $fenced && (new ReflectionMethod(PrescriptionAiGenerator::class, 'parseEvidence'))->invoke(null, $match[1], $expected) !== null; $value = is_array($decoded) ? $decoded : (is_array($wrapped) ? $wrapped : []); $summary['field_types'] = array_map('get_debug_type', $value); if (is_array($value['covered_source_ids'] ?? null)) { $summary['covered_source_count'] = count($value['covered_source_ids']); $summary['missing_source_count'] = count(array_diff($expected, $value['covered_source_ids'])); $summary['extra_source_count'] = count(array_diff($value['covered_source_ids'], $expected)); } } if (str_starts_with($key, 'files:')) { $sendable = array_values(array_filter($context['files'], static fn (array $f): bool => $f['status'] !== 'restricted' && !empty($f['url']))); $fileBatch = array_chunk($sendable, 3)[(int) substr($key, 6)]; $known = array_values(array_unique(array_merge(array_column($context['source']['records'], 'source_id'), array_column($context['files'], 'file_id')))); $value = (new ReflectionMethod(PrescriptionAiGenerator::class, 'object'))->invoke(null, $content); $summary['file_output_valid'] = (new ReflectionMethod(PrescriptionAiGenerator::class, 'parseFiles'))->invoke(null, $content, $fileBatch, $known) !== null; $summary['returned_count'] = is_array($value['files'] ?? null) ? count($value['files']) : null; $summary['expected_count'] = count($fileBatch); $summary['file_shapes'] = []; foreach ($value['files'] ?? [] as $file) { $refs = $file['evidence_references'] ?? null; $summary['file_shapes'][] = ['keys' => array_keys($file), 'types' => array_map('get_debug_type', $file), 'status' => in_array($file['status'] ?? '', ['processed', 'unreadable', 'unsupported'], true) ? $file['status'] : 'INVALID', 'expected_id' => in_array($file['file_id'] ?? null, array_column($fileBatch, 'file_id'), true), 'findings_bytes' => is_string($file['findings'] ?? null) ? strlen($file['findings']) : null, 'refs_count' => is_array($refs) ? count($refs) : null, 'unknown_refs_count' => is_array($refs) && count(array_filter($refs, 'is_string')) === count($refs) ? count(array_diff($refs, $known)) : null]; } } $item['steps'][] = $summary; } $result[] = $item; } echo json_encode(['text_chunks' => count($chunks), 'text_prompt_bytes' => $textPromptBytes, 'input_budget' => $budget + 3500, 'attachments' => count($context['files'] ?? []), 'attachment_statuses' => $attachmentStatuses, 'progress_shape_only' => $result], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE), PHP_EOL;