@robosystems/client 0.3.10 → 0.3.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -29,33 +29,35 @@ import {
29
29
  opAutoMapElements,
30
30
  opBuildFactGrid,
31
31
  opClosePeriod,
32
- opCreateClosingEntry,
32
+ opCreateAgent,
33
+ opCreateEventBlock,
34
+ opCreateEventHandler,
33
35
  opCreateInformationBlock,
34
- opCreateJournalEntry,
35
- opCreateManualClosingEntry,
36
36
  opCreateMappingAssociation,
37
37
  opCreatePublishList,
38
38
  opCreateReport,
39
39
  opCreateTaxonomyBlock,
40
- opCreateTransaction,
41
40
  opDeleteInformationBlock,
42
41
  opDeleteJournalEntry,
43
42
  opDeleteMappingAssociation,
44
43
  opDeletePublishList,
45
44
  opDeleteReport,
46
45
  opDeleteTaxonomyBlock,
47
- opDisposeSchedule,
48
46
  opEvaluateRules,
47
+ opFinancialStatementAnalysis,
49
48
  opInitializeLedger,
50
49
  opLinkEntityTaxonomy,
50
+ opLiveFinancialStatement,
51
+ opPreviewEventBlock,
51
52
  opRegenerateReport,
52
53
  opRemovePublishListMember,
53
54
  opReopenPeriod,
54
- opReverseJournalEntry,
55
55
  opSetCloseTarget,
56
56
  opShareReport,
57
- opTruncateSchedule,
57
+ opUpdateAgent,
58
58
  opUpdateEntity,
59
+ opUpdateEventBlock,
60
+ opUpdateEventHandler,
59
61
  opUpdateInformationBlock,
60
62
  opUpdateJournalEntry,
61
63
  opUpdatePublishList,
@@ -65,31 +67,31 @@ import type {
65
67
  AddPublishListMembersOperation,
66
68
  AutoMapElementsOperation,
67
69
  ClosePeriodOperation,
68
- CreateClosingEntryOperation,
70
+ CreateAgentRequest,
71
+ CreateEventBlockRequest,
72
+ CreateEventHandlerRequest,
69
73
  CreateInformationBlockRequest,
70
- CreateJournalEntryRequest,
71
- CreateManualClosingEntryRequest,
72
74
  CreateMappingAssociationOperation,
73
75
  CreatePublishListRequest,
74
76
  CreateReportRequest,
75
77
  CreateTaxonomyBlockRequest,
76
- CreateTransactionRequest,
77
78
  CreateViewRequest,
78
79
  DeleteInformationBlockRequest,
79
80
  DeleteJournalEntryRequest,
80
81
  DeleteMappingAssociationOperation,
81
82
  DeleteTaxonomyBlockRequest,
82
- DisposeScheduleRequest,
83
83
  EvaluateRulesRequest,
84
+ FinancialStatementAnalysisRequest,
84
85
  InitializeLedgerRequest,
85
- JournalEntryLineItemInput,
86
86
  LinkEntityTaxonomyRequest,
87
+ LiveFinancialStatementRequest,
87
88
  OperationEnvelope,
88
89
  ReopenPeriodOperation,
89
- ReverseJournalEntryRequest,
90
90
  SetCloseTargetOperation,
91
- TruncateScheduleOperation,
91
+ UpdateAgentRequest,
92
92
  UpdateEntityRequest,
93
+ UpdateEventBlockRequest,
94
+ UpdateEventHandlerRequest,
93
95
  UpdateInformationBlockRequest,
94
96
  UpdateJournalEntryRequest,
95
97
  UpdatePublishListOperation,
@@ -300,18 +302,6 @@ interface RawClosePeriodResult {
300
302
  fiscal_calendar: RawFiscalCalendar
301
303
  }
302
304
 
303
- interface RawClosingEntryResult {
304
- outcome: ClosingEntryOutcome
305
- entry_id: string | null
306
- status: string | null
307
- posting_date: string | null
308
- memo: string | null
309
- debit_element_id: string | null
310
- credit_element_id: string | null
311
- amount: number | null
312
- reason: string | null
313
- }
314
-
315
305
  interface RawScheduleCreatedResult {
316
306
  structure_id: string
317
307
  name: string
@@ -320,13 +310,6 @@ interface RawScheduleCreatedResult {
320
310
  total_facts: number
321
311
  }
322
312
 
323
- interface RawTruncateScheduleResult {
324
- structure_id: string
325
- new_end_date: string
326
- facts_deleted: number
327
- reason: string
328
- }
329
-
330
313
  export interface InitializeLedgerResult {
331
314
  fiscalCalendar: LedgerFiscalCalendar
332
315
  periodsCreated: number
@@ -340,20 +323,6 @@ export interface ClosePeriodResult {
340
323
  fiscalCalendar: LedgerFiscalCalendar
341
324
  }
342
325
 
343
- export type ClosingEntryOutcome = 'created' | 'unchanged' | 'regenerated' | 'removed' | 'skipped'
344
-
345
- export interface ClosingEntry {
346
- outcome: ClosingEntryOutcome
347
- entryId: string | null
348
- status: string | null
349
- postingDate: string | null
350
- memo: string | null
351
- debitElementId: string | null
352
- creditElementId: string | null
353
- amount: number | null
354
- reason: string | null
355
- }
356
-
357
326
  export interface ScheduleCreated {
358
327
  structureId: string
359
328
  name: string
@@ -362,13 +331,6 @@ export interface ScheduleCreated {
362
331
  totalFacts: number
363
332
  }
364
333
 
365
- export interface TruncateScheduleResult {
366
- structureId: string
367
- newEndDate: string
368
- factsDeleted: number
369
- reason: string
370
- }
371
-
372
334
  export type LedgerEntryType = 'standard' | 'adjusting' | 'closing' | 'reversing'
373
335
 
374
336
  // ── Caller-facing option interfaces ────────────────────────────────────
@@ -386,25 +348,6 @@ export interface ClosePeriodOptions {
386
348
  allowStaleSync?: boolean
387
349
  }
388
350
 
389
- export interface ManualClosingLineItem {
390
- elementId: string
391
- debitAmount?: number
392
- creditAmount?: number
393
- description?: string | null
394
- }
395
-
396
- export interface CreateManualClosingEntryOptions {
397
- postingDate: string
398
- memo: string
399
- lineItems: ManualClosingLineItem[]
400
- entryType?: LedgerEntryType
401
- }
402
-
403
- export interface TruncateScheduleOptions {
404
- newEndDate: string
405
- reason: string
406
- }
407
-
408
351
  export interface CreateScheduleOptions {
409
352
  name: string
410
353
  elementIds: string[]
@@ -626,56 +569,6 @@ export class LedgerClient {
626
569
  )
627
570
  }
628
571
 
629
- /**
630
- * Create a standalone business-event Transaction without entries.
631
- *
632
- * Returns a `transaction_id` that can be passed to `createJournalEntry`
633
- * to attach entries to this event. Use when a single event (invoice,
634
- * payment, deposit) produces multiple entries over its lifecycle.
635
- *
636
- * `amount` is in minor currency units (cents).
637
- * `type` is free-form: invoice, payment, bill, expense, deposit, etc.
638
- */
639
- async createTransaction(
640
- graphId: string,
641
- options: {
642
- type: string
643
- date: string
644
- amount: number
645
- currency?: string
646
- description?: string
647
- merchantName?: string
648
- referenceNumber?: string
649
- number?: string
650
- category?: string
651
- dueDate?: string
652
- status?: 'pending' | 'posted'
653
- idempotencyKey?: string
654
- }
655
- ): Promise<Record<string, unknown>> {
656
- const body: CreateTransactionRequest = {
657
- type: options.type,
658
- date: options.date,
659
- amount: options.amount,
660
- currency: options.currency ?? 'USD',
661
- status: options.status ?? 'pending',
662
- description: options.description ?? null,
663
- merchant_name: options.merchantName ?? null,
664
- reference_number: options.referenceNumber ?? null,
665
- number: options.number ?? null,
666
- category: options.category ?? null,
667
- due_date: options.dueDate ?? null,
668
- }
669
- const headers = options.idempotencyKey
670
- ? { 'Idempotency-Key': options.idempotencyKey }
671
- : undefined
672
- const envelope = await this.callOperation(
673
- 'Create transaction',
674
- opCreateTransaction({ path: { graph_id: graphId }, body, headers })
675
- )
676
- return (envelope.result ?? {}) as Record<string, unknown>
677
- }
678
-
679
572
  // ── Trial Balance ──────────────────────────────────────────────────
680
573
 
681
574
  /** Trial balance by raw CoA account. */
@@ -1060,38 +953,43 @@ export class LedgerClient {
1060
953
  return (envelope.result ?? { deleted: true }) as { deleted: boolean }
1061
954
  }
1062
955
 
1063
- /** Truncate a schedule — end it early at `newEndDate`. */
1064
- async truncateSchedule(
1065
- graphId: string,
1066
- structureId: string,
1067
- options: TruncateScheduleOptions
1068
- ): Promise<TruncateScheduleResult> {
1069
- const body: TruncateScheduleOperation = {
1070
- structure_id: structureId,
1071
- new_end_date: options.newEndDate,
1072
- reason: options.reason,
1073
- }
1074
- const envelope = await this.callOperation(
1075
- 'Truncate schedule',
1076
- opTruncateSchedule({ path: { graph_id: graphId }, body })
1077
- )
1078
- const raw = envelope.result as unknown as RawTruncateScheduleResult
1079
- return {
1080
- structureId: raw.structure_id,
1081
- newEndDate: raw.new_end_date,
1082
- factsDeleted: raw.facts_deleted,
1083
- reason: raw.reason,
1084
- }
1085
- }
1086
-
1087
- /** Dispose of a schedule asset — post a disposal entry and delete forward facts. */
956
+ /**
957
+ * Dispose of a schedule asset — atomically truncates forward facts,
958
+ * deletes the SumEquals rule, and posts a balanced disposal entry.
959
+ *
960
+ * Routes through `create-event-block` with `event_type='asset_disposed'`.
961
+ * `occurred_at` is required and represents the disposal date.
962
+ */
1088
963
  async disposeSchedule(
1089
964
  graphId: string,
1090
- body: DisposeScheduleRequest
965
+ options: {
966
+ scheduleId: string
967
+ occurredAt: string
968
+ proceeds?: number
969
+ proceedsElementId?: string | null
970
+ gainLossElementId?: string | null
971
+ memo?: string | null
972
+ reason?: string
973
+ }
1091
974
  ): Promise<Record<string, unknown>> {
975
+ const body: CreateEventBlockRequest = {
976
+ event_type: 'asset_disposed',
977
+ event_category: 'adjustment',
978
+ source: 'native',
979
+ occurred_at: options.occurredAt,
980
+ apply_handlers: true,
981
+ metadata: {
982
+ schedule_id: options.scheduleId,
983
+ proceeds: options.proceeds ?? 0,
984
+ proceeds_element_id: options.proceedsElementId ?? null,
985
+ gain_loss_element_id: options.gainLossElementId ?? null,
986
+ memo: options.memo ?? null,
987
+ reason: options.reason ?? 'asset_disposed_event',
988
+ },
989
+ }
1092
990
  const envelope = await this.callOperation(
1093
991
  'Dispose schedule',
1094
- opDisposeSchedule({ path: { graph_id: graphId }, body })
992
+ opCreateEventBlock({ path: { graph_id: graphId }, body })
1095
993
  )
1096
994
  return (envelope.result ?? {}) as Record<string, unknown>
1097
995
  }
@@ -1138,7 +1036,12 @@ export class LedgerClient {
1138
1036
 
1139
1037
  /**
1140
1038
  * Idempotently create (or refresh) a draft closing entry from a
1141
- * schedule for a period. See `ClosingEntryOutcome` for semantics.
1039
+ * schedule for a period.
1040
+ *
1041
+ * Routes through `create-event-block` with
1042
+ * `event_type='schedule_entry_due'`. Returns the EventBlockEnvelope —
1043
+ * the underlying handler is idempotent and dispatches to one of
1044
+ * created / unchanged / regenerated / removed / skipped internally.
1142
1045
  */
1143
1046
  async createClosingEntry(
1144
1047
  graphId: string,
@@ -1147,45 +1050,26 @@ export class LedgerClient {
1147
1050
  periodStart: string,
1148
1051
  periodEnd: string,
1149
1052
  memo?: string
1150
- ): Promise<ClosingEntry> {
1151
- const body: CreateClosingEntryOperation = {
1152
- structure_id: structureId,
1153
- posting_date: postingDate,
1154
- period_start: periodStart,
1155
- period_end: periodEnd,
1156
- memo: memo ?? undefined,
1053
+ ): Promise<Record<string, unknown>> {
1054
+ const body: CreateEventBlockRequest = {
1055
+ event_type: 'schedule_entry_due',
1056
+ event_category: 'recognition',
1057
+ source: 'scheduled',
1058
+ occurred_at: `${postingDate}T00:00:00Z`,
1059
+ apply_handlers: true,
1060
+ metadata: {
1061
+ schedule_id: structureId,
1062
+ posting_date: postingDate,
1063
+ period_start: periodStart,
1064
+ period_end: periodEnd,
1065
+ memo: memo ?? null,
1066
+ },
1157
1067
  }
1158
1068
  const envelope = await this.callOperation(
1159
1069
  'Create closing entry',
1160
- opCreateClosingEntry({ path: { graph_id: graphId }, body })
1070
+ opCreateEventBlock({ path: { graph_id: graphId }, body })
1161
1071
  )
1162
- return rawToClosingEntry(envelope.result as unknown as RawClosingEntryResult)
1163
- }
1164
-
1165
- /**
1166
- * Create a manual balanced closing entry (not tied to a schedule).
1167
- * Used for disposals, adjustments, and one-off closing events.
1168
- */
1169
- async createManualClosingEntry(
1170
- graphId: string,
1171
- options: CreateManualClosingEntryOptions
1172
- ): Promise<ClosingEntry> {
1173
- const body: CreateManualClosingEntryRequest = {
1174
- posting_date: options.postingDate,
1175
- memo: options.memo,
1176
- entry_type: options.entryType,
1177
- line_items: options.lineItems.map((li) => ({
1178
- element_id: li.elementId,
1179
- debit_amount: li.debitAmount ?? 0,
1180
- credit_amount: li.creditAmount ?? 0,
1181
- description: li.description ?? null,
1182
- })),
1183
- }
1184
- const envelope = await this.callOperation(
1185
- 'Create manual closing entry',
1186
- opCreateManualClosingEntry({ path: { graph_id: graphId }, body })
1187
- )
1188
- return rawToClosingEntry(envelope.result as unknown as RawClosingEntryResult)
1072
+ return (envelope.result ?? {}) as Record<string, unknown>
1189
1073
  }
1190
1074
 
1191
1075
  // ── Closing book ───────────────────────────────────────────────────
@@ -1300,35 +1184,48 @@ export class LedgerClient {
1300
1184
  /**
1301
1185
  * Create a journal entry with balanced line items (DR=CR enforced).
1302
1186
  *
1303
- * Defaults to `status='draft'`. Pass `status='posted'` for historical
1304
- * data import where entries represent already-happened business events.
1187
+ * Routes through `create-event-block` with
1188
+ * `event_type='journal_entry_recorded'` the Python handler forwards
1189
+ * to the internal journal-entry command. Defaults to `status='draft'`;
1190
+ * pass `status='posted'` for historical data imports.
1305
1191
  *
1306
1192
  * Supply `idempotencyKey` to make the call safe to retry — replays
1307
1193
  * within 24 hours return the same envelope. Reusing the key with a
1308
1194
  * different body returns HTTP 409.
1195
+ *
1196
+ * Returns the EventBlockEnvelope (event row fields); query the ledger
1197
+ * separately if you need the resulting entry_id.
1309
1198
  */
1310
1199
  async createJournalEntry(
1311
1200
  graphId: string,
1312
1201
  options: CreateJournalEntryOptions
1313
1202
  ): Promise<Record<string, unknown>> {
1314
- const body: CreateJournalEntryRequest = {
1315
- posting_date: options.postingDate,
1316
- memo: options.memo,
1317
- line_items: options.lineItems.map(
1318
- (li): JournalEntryLineItemInput => ({
1203
+ const body: CreateEventBlockRequest = {
1204
+ event_type: 'journal_entry_recorded',
1205
+ event_category: 'adjustment',
1206
+ source: 'native',
1207
+ occurred_at: `${options.postingDate}T00:00:00Z`,
1208
+ apply_handlers: true,
1209
+ metadata: {
1210
+ posting_date: options.postingDate,
1211
+ memo: options.memo,
1212
+ line_items: options.lineItems.map((li) => ({
1319
1213
  element_id: li.elementId,
1320
1214
  debit_amount: li.debitAmount,
1321
1215
  credit_amount: li.creditAmount,
1322
1216
  description: li.description ?? null,
1323
- })
1324
- ),
1325
- type: options.type,
1326
- status: options.status,
1327
- transaction_id: options.transactionId ?? null,
1217
+ })),
1218
+ type: options.type ?? 'standard',
1219
+ status: options.status ?? 'draft',
1220
+ transaction_id: options.transactionId ?? null,
1221
+ },
1328
1222
  }
1223
+ const headers = options.idempotencyKey
1224
+ ? { 'Idempotency-Key': options.idempotencyKey }
1225
+ : undefined
1329
1226
  const envelope = await this.callOperation(
1330
1227
  'Create journal entry',
1331
- opCreateJournalEntry({ path: { graph_id: graphId }, body })
1228
+ opCreateEventBlock({ path: { graph_id: graphId }, body, headers })
1332
1229
  )
1333
1230
  return (envelope.result ?? {}) as Record<string, unknown>
1334
1231
  }
@@ -1358,20 +1255,177 @@ export class LedgerClient {
1358
1255
  /**
1359
1256
  * Reverse a posted journal entry — creates an offsetting entry and marks
1360
1257
  * the original as reversed.
1258
+ *
1259
+ * Routes through `create-event-block` with
1260
+ * `event_type='journal_entry_reversed'`.
1361
1261
  */
1362
1262
  async reverseJournalEntry(
1363
1263
  graphId: string,
1364
1264
  entryId: string,
1365
- options?: { postingDate?: string | null; memo?: string | null }
1265
+ options?: {
1266
+ postingDate?: string | null
1267
+ memo?: string | null
1268
+ reason?: string | null
1269
+ }
1366
1270
  ): Promise<Record<string, unknown>> {
1367
- const body: ReverseJournalEntryRequest = {
1368
- entry_id: entryId,
1369
- posting_date: options?.postingDate ?? null,
1370
- memo: options?.memo ?? null,
1271
+ const occurredDate = options?.postingDate ?? new Date().toISOString().slice(0, 10)
1272
+ const body: CreateEventBlockRequest = {
1273
+ event_type: 'journal_entry_reversed',
1274
+ event_category: 'adjustment',
1275
+ source: 'native',
1276
+ occurred_at: `${occurredDate}T00:00:00Z`,
1277
+ apply_handlers: true,
1278
+ metadata: {
1279
+ entry_id: entryId,
1280
+ posting_date: options?.postingDate ?? null,
1281
+ memo: options?.memo ?? null,
1282
+ reason: options?.reason ?? null,
1283
+ },
1371
1284
  }
1372
1285
  const envelope = await this.callOperation(
1373
1286
  'Reverse journal entry',
1374
- opReverseJournalEntry({ path: { graph_id: graphId }, body })
1287
+ opCreateEventBlock({ path: { graph_id: graphId }, body })
1288
+ )
1289
+ return (envelope.result ?? {}) as Record<string, unknown>
1290
+ }
1291
+
1292
+ // ── Event blocks (generic preview + status transitions) ──────────────
1293
+
1294
+ /**
1295
+ * Dry-run an event block — resolve the handler, evaluate metadata, and
1296
+ * return the planned GL rows without writing anything. Companion to
1297
+ * `createJournalEntry` / `reverseJournalEntry` / `createClosingEntry` /
1298
+ * `disposeSchedule`: pass the same body you'd send to those methods
1299
+ * (the underlying `CreateEventBlockRequest`) and inspect what the
1300
+ * handler would do.
1301
+ */
1302
+ async previewEventBlock(
1303
+ graphId: string,
1304
+ body: CreateEventBlockRequest
1305
+ ): Promise<Record<string, unknown>> {
1306
+ const envelope = await this.callOperation(
1307
+ 'Preview event block',
1308
+ opPreviewEventBlock({ path: { graph_id: graphId }, body })
1309
+ )
1310
+ return (envelope.result ?? {}) as Record<string, unknown>
1311
+ }
1312
+
1313
+ /**
1314
+ * Apply a status transition and/or field corrections to an existing
1315
+ * event block. Use for posting drafts (`classified` → `committed` →
1316
+ * `fulfilled`), voiding, superseding (correction chains), or patching
1317
+ * `description` / `effective_at` / `metadata`.
1318
+ */
1319
+ async updateEventBlock(
1320
+ graphId: string,
1321
+ body: UpdateEventBlockRequest
1322
+ ): Promise<Record<string, unknown>> {
1323
+ const envelope = await this.callOperation(
1324
+ 'Update event block',
1325
+ opUpdateEventBlock({ path: { graph_id: graphId }, body })
1326
+ )
1327
+ return (envelope.result ?? {}) as Record<string, unknown>
1328
+ }
1329
+
1330
+ // ── Agents (REA counterparties) ───────────────────────────────────────
1331
+
1332
+ /**
1333
+ * Create an agent — REA counterparty (customer, vendor, employee, etc.)
1334
+ * referenced by event blocks via `agent_id`. `(source, external_id)` is
1335
+ * unique when `external_id` is provided, so external-source ingestion is
1336
+ * idempotent at the DB level.
1337
+ */
1338
+ async createAgent(
1339
+ graphId: string,
1340
+ body: CreateAgentRequest,
1341
+ idempotencyKey?: string
1342
+ ): Promise<Record<string, unknown>> {
1343
+ const headers = idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : undefined
1344
+ const envelope = await this.callOperation(
1345
+ 'Create agent',
1346
+ opCreateAgent({ path: { graph_id: graphId }, body, headers })
1347
+ )
1348
+ return (envelope.result ?? {}) as Record<string, unknown>
1349
+ }
1350
+
1351
+ /**
1352
+ * Update an agent. `metadata_patch` is a partial merge into the existing
1353
+ * metadata object; all other fields replace.
1354
+ */
1355
+ async updateAgent(graphId: string, body: UpdateAgentRequest): Promise<Record<string, unknown>> {
1356
+ const envelope = await this.callOperation(
1357
+ 'Update agent',
1358
+ opUpdateAgent({ path: { graph_id: graphId }, body })
1359
+ )
1360
+ return (envelope.result ?? {}) as Record<string, unknown>
1361
+ }
1362
+
1363
+ // ── Event handlers (DSL handler registry) ────────────────────────────
1364
+
1365
+ /**
1366
+ * Register a tenant-configurable event handler — DSL row in the
1367
+ * `event_handlers` table that drives `create-event-block` for event
1368
+ * types not covered by a Python handler. Match selectors plus a
1369
+ * `transaction_template` describing the GL rows to produce.
1370
+ */
1371
+ async createEventHandler(
1372
+ graphId: string,
1373
+ body: CreateEventHandlerRequest
1374
+ ): Promise<Record<string, unknown>> {
1375
+ const envelope = await this.callOperation(
1376
+ 'Create event handler',
1377
+ opCreateEventHandler({ path: { graph_id: graphId }, body })
1378
+ )
1379
+ return (envelope.result ?? {}) as Record<string, unknown>
1380
+ }
1381
+
1382
+ /**
1383
+ * Update a registered event handler. Pass `approve: true` to flip an
1384
+ * AI-suggested handler from unapproved to active.
1385
+ */
1386
+ async updateEventHandler(
1387
+ graphId: string,
1388
+ body: UpdateEventHandlerRequest
1389
+ ): Promise<Record<string, unknown>> {
1390
+ const envelope = await this.callOperation(
1391
+ 'Update event handler',
1392
+ opUpdateEventHandler({ path: { graph_id: graphId }, body })
1393
+ )
1394
+ return (envelope.result ?? {}) as Record<string, unknown>
1395
+ }
1396
+
1397
+ // ── Financial statements (graph-backed) ──────────────────────────────
1398
+
1399
+ /**
1400
+ * Live financial statement — pulls facts directly from the graph for
1401
+ * an explicit period window (or fiscal year) and returns the statement
1402
+ * shape without a persisted Report row. Useful for ad-hoc previews and
1403
+ * dashboards.
1404
+ */
1405
+ async liveFinancialStatement(
1406
+ graphId: string,
1407
+ body: LiveFinancialStatementRequest
1408
+ ): Promise<Record<string, unknown>> {
1409
+ const envelope = await this.callOperation(
1410
+ 'Live financial statement',
1411
+ opLiveFinancialStatement({ path: { graph_id: graphId }, body })
1412
+ )
1413
+ return (envelope.result ?? {}) as Record<string, unknown>
1414
+ }
1415
+
1416
+ /**
1417
+ * Run a financial statement analysis against an existing report.
1418
+ * On shared-repo graphs (e.g. SEC), `ticker` is required; on tenant
1419
+ * graphs it's ignored. Either pass an explicit `report_id` or let the
1420
+ * server auto-resolve via `fiscal_year` + `period_type`.
1421
+ */
1422
+ async financialStatementAnalysis(
1423
+ graphId: string,
1424
+ body: FinancialStatementAnalysisRequest
1425
+ ): Promise<Record<string, unknown>> {
1426
+ const envelope = await this.callOperation(
1427
+ 'Financial statement analysis',
1428
+ opFinancialStatementAnalysis({ path: { graph_id: graphId }, body })
1375
1429
  )
1376
1430
  return (envelope.result ?? {}) as Record<string, unknown>
1377
1431
  }
@@ -1704,20 +1758,6 @@ export class LedgerClient {
1704
1758
 
1705
1759
  // ── Module-private conversion helpers ─────────────────────────────────
1706
1760
 
1707
- function rawToClosingEntry(data: RawClosingEntryResult): ClosingEntry {
1708
- return {
1709
- outcome: data.outcome,
1710
- entryId: data.entry_id ?? null,
1711
- status: data.status ?? null,
1712
- postingDate: data.posting_date ?? null,
1713
- memo: data.memo ?? null,
1714
- debitElementId: data.debit_element_id ?? null,
1715
- creditElementId: data.credit_element_id ?? null,
1716
- amount: data.amount ?? null,
1717
- reason: data.reason ?? null,
1718
- }
1719
- }
1720
-
1721
1761
  function rawFiscalCalendarToCamel(raw: RawFiscalCalendar): LedgerFiscalCalendar {
1722
1762
  return {
1723
1763
  graphId: raw.graph_id,
@@ -88,6 +88,20 @@ class OperationClient {
88
88
  this.scheduleCleanup(operationId, 5000);
89
89
  resolve(result);
90
90
  });
91
+ // Handle transport-level failures after the stream was initially
92
+ // opened (server drop, network blip exhausting retries). Without
93
+ // this, max_retries_exceeded closes the SSE client but leaves the
94
+ // monitorOperation promise pending until options.timeout fires.
95
+ sseClient.on('max_retries_exceeded', (err) => {
96
+ if (timeoutHandle)
97
+ clearTimeout(timeoutHandle);
98
+ result = {
99
+ success: false,
100
+ error: (err && (err.message || err.error)) || 'SSE connection lost (max retries exceeded)',
101
+ };
102
+ this.scheduleCleanup(operationId, 5000);
103
+ resolve(result);
104
+ });
91
105
  })
92
106
  .catch((error) => {
93
107
  if (timeoutHandle)
@@ -136,6 +136,21 @@ export class OperationClient {
136
136
  this.scheduleCleanup(operationId, 5000)
137
137
  resolve(result)
138
138
  })
139
+
140
+ // Handle transport-level failures after the stream was initially
141
+ // opened (server drop, network blip exhausting retries). Without
142
+ // this, max_retries_exceeded closes the SSE client but leaves the
143
+ // monitorOperation promise pending until options.timeout fires.
144
+ sseClient.on('max_retries_exceeded', (err) => {
145
+ if (timeoutHandle) clearTimeout(timeoutHandle)
146
+ result = {
147
+ success: false,
148
+ error:
149
+ (err && (err.message || err.error)) || 'SSE connection lost (max retries exceeded)',
150
+ }
151
+ this.scheduleCleanup(operationId, 5000)
152
+ resolve(result)
153
+ })
139
154
  })
140
155
  .catch((error) => {
141
156
  if (timeoutHandle) clearTimeout(timeoutHandle)