feat: production readiness — 3-extension deploy with security hardening and observability
- Strip extensions to enable-banking, ai-categorization, ai-chat only - Remove push-notifications cron from vercel.json - Add security headers (HSTS, CSP, X-Frame-Options, Permissions-Policy) - Add /api/health endpoint for uptime monitoring - Add env var validation in ensureInitialized() - Fix SIE4 #IB opening balance records from year-end closing entry - Replace in-memory ai-chat rate limiter with Supabase-backed distributed rate limiting - Add Sentry error tracking scaffolding (@sentry/nextjs, instrumentation hook) - Add AI token usage tracking (migration 047, usage-tracker, wired into both AI extensions) - Include pending enable-banking and dashboard improvements Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
1a1c6ba40f
commit
13725ffc16
@@ -14,6 +14,7 @@ import 'server-only'
|
||||
import Anthropic from '@anthropic-ai/sdk'
|
||||
import { BOOKING_TEMPLATES, type BookingTemplate } from '@/lib/bookkeeping/booking-templates'
|
||||
import type { TransactionCategory, EntityType } from '@/types'
|
||||
import type { SupabaseClient } from '@supabase/supabase-js'
|
||||
|
||||
// ============================================================
|
||||
// Types
|
||||
@@ -72,10 +73,16 @@ export interface CategorizationSuggestion {
|
||||
templateId?: string
|
||||
}
|
||||
|
||||
export interface TrackingContext {
|
||||
supabase: SupabaseClient
|
||||
userId: string
|
||||
}
|
||||
|
||||
export interface CategorizationProvider {
|
||||
categorize(
|
||||
transactions: TransactionForCategorization[],
|
||||
context: CategorizationContext | EnrichedCategorizationContext
|
||||
context: CategorizationContext | EnrichedCategorizationContext,
|
||||
tracking?: TrackingContext
|
||||
): Promise<CategorizationSuggestion[]>
|
||||
}
|
||||
|
||||
@@ -198,7 +205,8 @@ export class AnthropicCategorizationProvider implements CategorizationProvider {
|
||||
|
||||
async categorize(
|
||||
transactions: TransactionForCategorization[],
|
||||
context: CategorizationContext | EnrichedCategorizationContext
|
||||
context: CategorizationContext | EnrichedCategorizationContext,
|
||||
tracking?: TrackingContext
|
||||
): Promise<CategorizationSuggestion[]> {
|
||||
// Cap batch size
|
||||
const batch = transactions.slice(0, MAX_BATCH_SIZE)
|
||||
@@ -318,6 +326,16 @@ ${transactionList}`
|
||||
],
|
||||
})
|
||||
|
||||
// Track token usage (fire-and-forget)
|
||||
if (tracking && message.usage) {
|
||||
const { trackTokenUsage } = await import('@/lib/ai/usage-tracker')
|
||||
trackTokenUsage(tracking.supabase, tracking.userId, 'ai-categorization', {
|
||||
inputTokens: message.usage.input_tokens,
|
||||
outputTokens: message.usage.output_tokens,
|
||||
model: this.model,
|
||||
})
|
||||
}
|
||||
|
||||
// Extract tool_use block from response
|
||||
const toolUseBlock = message.content.find(
|
||||
(block) => block.type === 'tool_use' && block.name === 'classify_transactions'
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
type CategorizationSuggestion,
|
||||
type AccountUsageEntry,
|
||||
type MerchantHistoryEntry,
|
||||
type TrackingContext,
|
||||
} from './categorizer'
|
||||
import type { BookingTemplate } from '@/lib/bookkeeping/booking-templates'
|
||||
|
||||
@@ -124,7 +125,7 @@ export async function categorizeTransactions(
|
||||
const context = await buildEnrichedContext(userId, supabase, batch)
|
||||
|
||||
const aiProvider = getProvider(settings.providerModel)
|
||||
const suggestions = await aiProvider.categorize(batch, context)
|
||||
const suggestions = await aiProvider.categorize(batch, context, { supabase, userId })
|
||||
|
||||
// Store suggestions
|
||||
await storeSuggestions(userId, suggestions, supabase)
|
||||
@@ -174,7 +175,7 @@ async function handleTransactionSynced(
|
||||
|
||||
const context = await buildEnrichedContext(userId, supabase, batch)
|
||||
const aiProvider = getProvider(settings.providerModel)
|
||||
const suggestions = await aiProvider.categorize(batch, context)
|
||||
const suggestions = await aiProvider.categorize(batch, context, { supabase, userId })
|
||||
|
||||
// Store only suggestions above confidence threshold
|
||||
const qualifiedSuggestions = suggestions.filter(
|
||||
|
||||
Reference in New Issue
Block a user