import { nanoid } from "nanoid"; import { CommitmentContract, CommitmentMetrics } from "@/types"; import { loadCommitments, saveCommitments, loadCommitmentMetrics, saveCommitmentMetrics, } from "@/lib/config"; function nowIso(): string { return new Date().toISOString(); } export function listCommitments(): CommitmentContract[] { return loadCommitments(); } export function getCommitmentById(id: string): CommitmentContract | null { const all = loadCommitments(); return all.find((c) => c.id === id) ?? null; } export function getCommitmentByEmailId(emailId: string): CommitmentContract | null { const all = loadCommitments(); return all.find((c) => c.emailId === emailId) ?? null; } function normalizeContract(contract: CommitmentContract): CommitmentContract { return { ...contract, id: contract.id || nanoid(), mandatoryOutputs: Array.isArray(contract.mandatoryOutputs) ? contract.mandatoryOutputs : [], inferredOutputs: Array.isArray(contract.inferredOutputs) ? contract.inferredOutputs : [], permittedTools: Array.isArray(contract.permittedTools) ? contract.permittedTools : [], dependencies: Array.isArray(contract.dependencies) ? contract.dependencies : [], assumptions: Array.isArray(contract.assumptions) ? contract.assumptions : [], auditEvents: Array.isArray(contract.auditEvents) ? contract.auditEvents : [], detectedAt: contract.detectedAt || nowIso(), }; } function mergeContracts(existing: CommitmentContract, incoming: CommitmentContract): CommitmentContract { const preserveStatus = existing.status !== "detected" && incoming.status === "detected"; const merged: CommitmentContract = { ...existing, ...incoming, id: existing.id, status: preserveStatus ? existing.status : incoming.status, deliverables: incoming.deliverables ?? existing.deliverables, executedAt: incoming.executedAt ?? existing.executedAt, completedAt: incoming.completedAt ?? existing.completedAt, auditEvents: [ ...(Array.isArray(existing.auditEvents) ? existing.auditEvents : []), ...(Array.isArray(incoming.auditEvents) ? incoming.auditEvents : []), ], }; return merged; } export function upsertCommitment(contract: CommitmentContract): CommitmentContract { const all = loadCommitments(); const idx = all.findIndex((c) => c.id === contract.id); const normalized = normalizeContract(contract); if (idx >= 0) { all[idx] = mergeContracts(all[idx], normalized); } else { all.unshift(normalized); } saveCommitments(all); return idx >= 0 ? all[idx] : normalized; } export function upsertCommitmentByEmailId(contract: CommitmentContract): CommitmentContract { const existing = getCommitmentByEmailId(contract.emailId); if (existing) { return upsertCommitment({ ...contract, id: existing.id }); } return upsertCommitment(contract); } export function addCommitmentAuditEvent( id: string, event: string, detail: string, ): CommitmentContract { const all = loadCommitments(); const idx = all.findIndex((c) => c.id === id); if (idx < 0) { throw new Error(`Commitment not found: ${id}`); } const updated: CommitmentContract = { ...all[idx], auditEvents: [ ...(Array.isArray(all[idx].auditEvents) ? all[idx].auditEvents : []), { timestamp: nowIso(), event, detail }, ], }; all[idx] = updated; saveCommitments(all); return updated; } export function updateCommitmentStatus( id: string, status: CommitmentContract["status"], detail?: string, ): CommitmentContract { const all = loadCommitments(); const idx = all.findIndex((c) => c.id === id); if (idx < 0) { throw new Error(`Commitment not found: ${id}`); } const updated: CommitmentContract = { ...all[idx], status, }; if (status === "executing") { updated.executedAt = updated.executedAt || nowIso(); } if (status === "completed") { updated.completedAt = updated.completedAt || nowIso(); } updated.auditEvents = [ ...(Array.isArray(updated.auditEvents) ? updated.auditEvents : []), { timestamp: nowIso(), event: "status", detail: detail ?? `Status changed to ${status}`, }, ]; all[idx] = updated; saveCommitments(all); return updated; } export function loadMetrics(): CommitmentMetrics { return loadCommitmentMetrics(); } export function recordExecutionOutcome(input: { capabilityOk: boolean; inputsOk: boolean; toolsOk: boolean; qaOk: boolean; durationMs: number; }): CommitmentMetrics { const metrics = loadCommitmentMetrics(); const bump = (key: keyof Pick, ok: boolean) => { const existing = metrics[key]; metrics[key] = { success: existing.success + (ok ? 1 : 0), total: existing.total + 1, }; }; bump("capability", input.capabilityOk); bump("inputsAvailable", input.inputsOk); bump("toolCompletion", input.toolsOk); bump("qualityApproval", input.qaOk); const duration = Math.max(0, Math.round(input.durationMs)); metrics.durationsMs = Array.isArray(metrics.durationsMs) ? [...metrics.durationsMs, duration].slice(-500) : [duration]; metrics.lastUpdatedAt = nowIso(); saveCommitmentMetrics(metrics); return metrics; } export function recordAcceptanceOutcome(input: { acceptedWithoutRevision: boolean; }): CommitmentMetrics { const metrics = loadCommitmentMetrics(); metrics.acceptedWithoutRevision = { success: metrics.acceptedWithoutRevision.success + (input.acceptedWithoutRevision ? 1 : 0), total: metrics.acceptedWithoutRevision.total + 1, }; metrics.lastUpdatedAt = nowIso(); saveCommitmentMetrics(metrics); return metrics; } export function recordOutcome(input: { capabilityOk: boolean; inputsOk: boolean; toolsOk: boolean; qaOk: boolean; acceptedWithoutRevision?: boolean; durationMs: number; }): CommitmentMetrics { const afterExecution = recordExecutionOutcome({ capabilityOk: input.capabilityOk, inputsOk: input.inputsOk, toolsOk: input.toolsOk, qaOk: input.qaOk, durationMs: input.durationMs, }); if (typeof input.acceptedWithoutRevision === "boolean") { return recordAcceptanceOutcome({ acceptedWithoutRevision: input.acceptedWithoutRevision }); } return afterExecution; }