Files
accounted/lib/agent-context/ledger-deep.ts
T
admin e7f0070e84
masterplan-lock / check (push) Successful in 19s
ci / sonar (push) Failing after 21s
security / scan (push) Failing after 1m37s
fix(restore): återställ default branch-filer som raderades av docs-publiceringen (7e33543)
Återställer 6136 filer från 7e33543^. Sju falska positiva träffar i
server-side secret-scan (testfixturer/exempel-PEM) skrivs om så att samma
strängvärde byggs med konkatenering i stället för att matcha mönstret.
2026-11-10 09:37:22 +01:00

71 lines
2.5 KiB
TypeScript

import type { SupabaseClient } from '@supabase/supabase-js'
// Deep, entity-resolved analysis behind the "Vad din agent vet" page. Merges
// counterparties across name variants, mines the booked verifikat for real
// spend, and detects recurrence. Backed by the get_ledger_deep_context RPC
// (full history, deterministic). See migration 20260708130000.
export interface DeepEntity {
/** Display label (the modal raw variant). */
name: string
/** Identity key: normalized counterparty key, or supplier id. */
key: string
/** Up to 8 distinct raw labels that merged into this entity. */
variants: string[]
/** True count of distinct raw labels merged. */
variant_count: number
/** Number of bookings. */
occurrences: number
/** Total paid, gross SEK (abs bank amount / invoice total). */
total_amount: number
first_seen: string
last_seen: string
/** Median gap between distinct booking dates, days. null if < 2 dates. */
cadence_days: number | null
dominant_account_number: string | null
/**
* Laplace-smoothed consistency (cnt+1)/(total+2), 0..1: sample-size-honest,
* so a single booking reads 0.67, never 1.0 (migration 20260710090000).
*/
dominant_account_share: number | null
/** Raw evidence: bookings on the dominant account. */
dominant_account_count: number | null
/** Raw evidence: all counted contra lines for the entity. */
dominant_account_total: number | null
dominant_vat?: string | null
kind: 'counterparty' | 'supplier'
}
export interface DeepLedgerContext {
counterparty_entities: DeepEntity[]
supplier_entities: DeepEntity[]
}
interface DeepRow {
counterparty_entities: Omit<DeepEntity, 'kind'>[]
supplier_entities: Omit<DeepEntity, 'kind'>[]
}
/**
* Full-history deep analysis. `fromDate = null` means all history; pass a date
* to bound the lookback on large tenants (the cache layer is deferred).
*/
export async function buildDeepEntities(
supabase: SupabaseClient,
companyId: string,
fromDate: string | null = null,
): Promise<DeepLedgerContext> {
const { data, error } = await supabase.rpc('get_ledger_deep_context', {
p_company_id: companyId,
p_from_date: fromDate,
})
if (error) {
throw new Error(`ledger deep context failed: ${error.message}`)
}
const row = (data ?? { counterparty_entities: [], supplier_entities: [] }) as DeepRow
return {
counterparty_entities: (row.counterparty_entities ?? []).map((e) => ({ ...e, kind: 'counterparty' as const })),
supplier_entities: (row.supplier_entities ?? []).map((e) => ({ ...e, kind: 'supplier' as const })),
}
}