korrespond v1 premiere: Bedrock routing, engine picker, journal auto-save + status

- KorrespondAgent: add resolveDeployment() helper; fix classify/translate to use
  Haiku via Bedrock, draft to use Haiku (quick) or Sonnet (thorough) — fixes broken
  withDeployment('gpt-4o-mini') calls when DBN_BEDROCK_ENABLED=true
- korrespond.php: add Quick/Thorough engine picker (case_toggle already present)
- korrespond.js: pass engine in request payload
- api/korrespond.php: accept user-selected engine, auto-save to case_tool_results
  for paid users after each successful run, update deployment log label
- CaseResults: add korr_status to listForUser SELECT, add updateStatus() method
- result-action.php: add set_status action for correspondence journal
- account.php: show status dropdown (Draft/Sent/Reply received/Resolved) for
  korrespond entries in #analyses, wire JS change handler to result-action.php

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 21:52:09 +02:00
parent c2735fa919
commit b2e1bf268d
7 changed files with 94 additions and 6 deletions
+22 -1
View File
@@ -124,7 +124,7 @@ final class CaseResults
$db = dbnmDb();
$stmt = $db->prepare(
'SELECT id, user_id, owner_user_id, tool, title, used_case_context,
model, latency_ms, credits_charged, pinned, created_at
model, latency_ms, credits_charged, pinned, korr_status, created_at
FROM case_tool_results
WHERE owner_user_id = ? AND deleted_at IS NULL
ORDER BY pinned DESC, created_at DESC
@@ -226,6 +226,27 @@ final class CaseResults
return $stmt->rowCount() > 0;
}
/** Update the correspondence status (korrespond tool only). */
public static function updateStatus(int $userId, int $id, string $status): bool
{
$allowed = ['draft', 'sent', 'replied', 'resolved'];
if (!in_array($status, $allowed, true)) {
return false;
}
if (!self::tableReady()) {
return false;
}
$ownerId = CaseStore::caseResolveClientId($userId);
$db = dbnmDb();
$stmt = $db->prepare(
"UPDATE case_tool_results
SET korr_status = ?
WHERE id = ? AND owner_user_id = ? AND tool = 'korrespond' AND deleted_at IS NULL"
);
$stmt->execute([$status, $id, $ownerId]);
return $stmt->rowCount() > 0;
}
/** Human-readable Norwegian title hint per tool. */
public static function toolLabel(string $tool): string
{
+15 -3
View File
@@ -27,6 +27,14 @@ final class DbnKorrespondAgent
private const MAX_CONTEXT_CHARS = 24000;
private const MAX_DRAFT_TOKENS = 2000;
private function resolveDeployment(string $azureDeployment, bool $heavy = false): string
{
if (!($this->azure instanceof DbnBedrockGateway)) {
return $azureDeployment;
}
return $heavy ? DbnBedrockModelRouter::LITELLM_SONNET : DbnBedrockModelRouter::LITELLM_HAIKU;
}
/** Recipient-body presets → default slice toggles for law retrieval. */
private const BODY_PRESETS = [
'barnehage' => ['family_core', 'bufdir_guidance'],
@@ -215,7 +223,7 @@ PROMPT;
];
try {
$raw = $this->azure->withDeployment(self::CLASSIFY_DEPLOYMENT)->chatText([
$raw = $this->azure->withDeployment($this->resolveDeployment(self::CLASSIFY_DEPLOYMENT))->chatText([
['role' => 'system', 'content' => 'You return valid JSON only. No markdown fences.'],
['role' => 'user', 'content' => $prompt],
], ['json' => true, 'temperature' => 0.1, 'max_tokens' => 800, 'timeout' => 30]);
@@ -237,7 +245,11 @@ PROMPT;
*/
public function generate(array $intake, array $classify, ?callable $emit = null, string $engine = 'azure_mini'): array
{
$draftDeployment = ($engine === 'azure_full') ? 'gpt-4o' : 'gpt-4o-mini';
$draftDeployment = ($this->azure instanceof DbnBedrockGateway)
? (($engine === 'claude_sonnet' || $engine === 'azure_full')
? DbnBedrockModelRouter::LITELLM_SONNET
: DbnBedrockModelRouter::LITELLM_HAIKU)
: (($engine === 'azure_full') ? self::DRAFT_DEPLOYMENT : 'gpt-4o-mini');
$body = $intake['recipient_body'] ?? 'other';
$outputType = $intake['output_type'] ?? 'email';
$tone = $intake['tone'] ?? 'neutral';
@@ -667,7 +679,7 @@ Norwegian source:
PROMPT;
try {
return $this->azure->withDeployment(self::SELFCHECK_DEPLOYMENT)->chatText([
return $this->azure->withDeployment($this->resolveDeployment(self::SELFCHECK_DEPLOYMENT))->chatText([
['role' => 'system', 'content' => 'You are a precise legal translator.'],
['role' => 'user', 'content' => $prompt],
], [