0) { @ob_end_clean(); } ob_implicit_flush(true); header('Content-Type: application/x-ndjson; charset=utf-8'); header('Cache-Control: no-store'); header('X-Accel-Buffering: no'); $startTime = microtime(true); $language = 'en'; $creditDeducted = false; $ftUid = 0; $ftRemaining = -1; $emit = function (string $event, array $payload = []) use ($startTime): void { $payload['event'] = $event; $payload['t_ms'] = (int)round((microtime(true) - $startTime) * 1000); echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n"; @flush(); }; try { $input = dbnToolsJsonInput(400000); $language = dbnToolsNormalizeLanguage($input['language'] ?? 'en'); $docType = (string)($input['doc_type'] ?? 'other'); $allowedDocTypes = ['auto','barnevernet','adopsjon','emergency','samvær','fylkesnemnd','other']; if (!in_array($docType, $allowedDocTypes, true)) { $docType = 'other'; } $text = dbnToolsInjectDocContent($input, dbnToolsString($input, 'text', 128000, false)); if (mb_strlen(trim($text), 'UTF-8') < 80) { throw new DbnToolsHttpException( 'Paste at least 80 characters of text, upload a file, or select a document.', 422, 'empty_text' ); } $ftUid = dbnToolsFreeTierCheck('legal-analysis'); $emit('start', [ 'mode' => 'legal-analysis', 'language' => $language, 'doc_type' => $docType, 'chars' => mb_strlen($text, 'UTF-8'), ]); $agent = new DbnLegalAnalysisAgent(); // Pass 1 — extract issues (Azure, fast); deduct credit AFTER this succeeds $emit('progress', ['step' => 'extracting_issues', 'detail' => 'Identifying distinct legal issues…']); $issues = $agent->extractIssues($text, $language, $docType); if (empty($issues)) { $emit('final', [ 'result' => [ 'ok' => true, 'issues' => [], 'overall_assessment' => 'No discrete legal issues were identified in this document.', 'next_steps' => [], 'disclaimer' => 'Automated analysis — not legal advice.', 'model' => 'dbn-legal-agent-v3', 'doc_type' => $docType, 'latency_ms' => (int)round((microtime(true) - $startTime) * 1000), ], ]); exit; } // Deduct credit (gated until extract succeeds and at least one issue exists) $ftRemaining = dbnToolsFreeTierDeduct($ftUid, 'legal-analysis'); $creditDeducted = true; $emit('issues_extracted', [ 'count' => count($issues), 'issues' => array_map(fn($i) => [ 'id' => $i['id'], 'question' => $i['question'], 'brief_context' => $i['brief_context'], 'severity_hint' => $i['severity_hint'], ], $issues), ]); // Pass 2 — answer each issue sequentially on ocelot (keeps fine-tune hot) $svc = new DbnLegalToolsService(); $answered = []; foreach ($issues as $issue) { $emit('progress', [ 'step' => 'issue_searching_corpus', 'detail' => sprintf('Issue %d: searching legal corpus…', $issue['id']), 'issue_id' => $issue['id'], ]); $corpusQuery = $issue['question'] . "\n" . $issue['brief_context']; $corpusContext = $svc->corpusContextForSummarize($corpusQuery, 3); $emit('progress', [ 'step' => 'issue_answering', 'detail' => sprintf('Issue %d: asking dbn-legal-agent-v3…', $issue['id']), 'issue_id' => $issue['id'], ]); $answer = $agent->answerIssue($issue, $corpusContext, $language); $answered[] = $answer; $emit('issue_answered', ['issue' => $answer]); } // Pass 3 — synthesise (Azure) $emit('progress', ['step' => 'synthesising', 'detail' => 'Synthesising overall assessment…']); $synth = $agent->synthesise($answered, $language, $docType); $result = [ 'ok' => true, 'issues' => $answered, 'overall_assessment' => $synth['overall_assessment'], 'next_steps' => $synth['next_steps'], 'disclaimer' => $synth['disclaimer'], 'doc_type' => $docType, 'model' => 'dbn-legal-agent-v3', 'latency_ms' => (int)round((microtime(true) - $startTime) * 1000), ]; if ($ftRemaining >= 0) { $result['balance'] = $ftRemaining; } dbnToolsLogMetadata([ 'tool' => 'legal-analysis', 'language' => $language, 'ok' => true, 'latency_ms' => $result['latency_ms'], 'issue_count' => count($answered), 'deployment' => 'dbn-legal-agent-v3', ]); $emit('final', ['result' => $result]); } catch (DbnToolsHttpException $e) { $latency = (int)round((microtime(true) - $startTime) * 1000); dbnToolsLogMetadata([ 'tool' => 'legal-analysis', 'language' => $language, 'ok' => false, 'latency_ms' => $latency, 'error_code' => $e->errorCode, ]); $emit('error', ['code' => $e->errorCode, 'message' => $e->getMessage(), 'status' => $e->status]); } catch (Throwable $e) { error_log('legal-analysis fatal: ' . $e->getMessage()); $latency = (int)round((microtime(true) - $startTime) * 1000); dbnToolsLogMetadata([ 'tool' => 'legal-analysis', 'language' => $language, 'ok' => false, 'latency_ms' => $latency, 'error_code' => 'internal_error', ]); $emit('error', ['code' => 'internal_error', 'message' => 'Legal analysis could not complete this request.']); }