@firela/api-types 0.0.0-canary.e8d26ec2 → 0.0.0-canary.e98f0384

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.
@@ -51,6 +51,14 @@ export const $CreateAccountDto = {
51
51
  description: 'Icon identifier (overrides template)',
52
52
  example: 'bank-custom'
53
53
  },
54
+ displayName: {
55
+ type: 'string',
56
+ description:
57
+ 'User-set display name override (omit/null = keep the derived name)',
58
+ nullable: true,
59
+ maxLength: 50,
60
+ example: 'Salary card'
61
+ },
54
62
  openDirectiveMeta: {
55
63
  type: 'object',
56
64
  description:
@@ -124,6 +132,8 @@ export const $AccountResponseDto = {
124
132
  'STUDENT_LOAN',
125
133
  'CREDIT_CARD',
126
134
  'PERSONAL_LOAN',
135
+ 'ACCOUNTS_PAYABLE',
136
+ 'TAX_PAYABLE',
127
137
  'OTHER'
128
138
  ],
129
139
  nullable: true,
@@ -179,7 +189,8 @@ export const $AccountResponseDto = {
179
189
  },
180
190
  displayName: {
181
191
  type: 'string',
182
- description: 'Localized display name (ADR-0114, read-time projection)',
192
+ description:
193
+ 'Display name with precedence: user-set name (#762) > ADR-0114 localized name > path leaf (read-time projection)',
183
194
  example: 'Checking'
184
195
  },
185
196
  icon: {
@@ -195,7 +206,7 @@ export const $AccountResponseDto = {
195
206
  }
196
207
  },
197
208
  platformId: {
198
- type: 'object',
209
+ type: 'string',
199
210
  description: 'Platform ID (null if unbound)',
200
211
  example: 'c98e5d4a-2f71-4a5a-bb3c-92c9f231d5e2'
201
212
  },
@@ -275,6 +286,14 @@ export const $UpdateAccountDto = {
275
286
  description: 'Icon identifier',
276
287
  example: 'bank-custom'
277
288
  },
289
+ displayName: {
290
+ type: 'string',
291
+ description:
292
+ 'User-set display name override (null = clear the override and fall back to the derived name, omit = unchanged)',
293
+ nullable: true,
294
+ maxLength: 50,
295
+ example: 'Salary card'
296
+ },
278
297
  openDirectiveMeta: {
279
298
  type: 'object',
280
299
  description:
@@ -375,16 +394,43 @@ export const $AccountStandardResponseDto = {
375
394
  },
376
395
  name: {
377
396
  type: 'string',
378
- description: 'Short localized display name',
397
+ description:
398
+ 'Short display name. Universal rows project to the request locale (Accept-Language); regional rows keep the authored native name — mixed-language by design (ADR-0131 class P vs class A).',
379
399
  example: 'Housing Fund'
380
400
  },
401
+ aliases: {
402
+ description:
403
+ 'Authored market-language alternative names delivered verbatim (not localized copy, not xlf-managed, not locale-projected). Flat string[] per ADR-0129 D1; ADR-0131 class A.',
404
+ example: ['Alipay', 'WeChat Pay'],
405
+ type: 'array',
406
+ items: {
407
+ type: 'string'
408
+ }
409
+ },
410
+ searchTerms: {
411
+ description:
412
+ 'Locale-projected search synonyms (e.g. the zh bank-card / debit-card everyday terms for the checking account). Pure-locale projection — absent when the locale has no seeded synonyms; English fallback rides the authored aliases field. Search-only vocabulary, not the NLP routing corpus (#698, ADR-0131 fourth-class adjudication).',
413
+ example: ['yinhangka', 'jiejika'],
414
+ type: 'array',
415
+ items: {
416
+ type: 'string'
417
+ }
418
+ },
419
+ currency: {
420
+ type: 'string',
421
+ description:
422
+ 'Product denomination as a 3-letter ISO 4217 code, authored market data delivered verbatim (not localized, not xlf-managed). ADR-0131 class A. Absent = single-currency not asserted — consumers fall back to their own region currency (#714).',
423
+ example: 'HKD'
424
+ },
381
425
  description: {
382
426
  type: 'string',
383
- description: 'Account description (stable semantics only)',
427
+ description:
428
+ 'Account description (stable semantics only). Mixed-language contract: universal rows project to the request locale via the accountDesc xlf axis with an en fallback (ADR-0131 class P, ADR-0132; unseeded locales falling back to English are expected); regional rows deliver the authored market language (ADR-0131 class A, verbatim, never xlf-managed).',
384
429
  example: 'ICBC checking account for daily transactions'
385
430
  },
386
431
  tags: {
387
- description: 'Account tags for categorization',
432
+ description:
433
+ 'Account tags for categorization — structured metadata delivered verbatim (not localized, not xlf-managed). ADR-0131 class A.',
388
434
  example: ['bank', 'checking', 'primary'],
389
435
  type: 'array',
390
436
  items: {
@@ -395,9 +441,47 @@ export const $AccountStandardResponseDto = {
395
441
  type: 'string',
396
442
  description: 'Icon identifier for UI display',
397
443
  example: 'bank-icbc'
444
+ },
445
+ productCategory: {
446
+ type: 'string',
447
+ description:
448
+ 'Onboarding product category (coarse grouping derived from assetSubClass)',
449
+ enum: [
450
+ 'cash',
451
+ 'investment',
452
+ 'credit_card',
453
+ 'loan',
454
+ 'payable_tax',
455
+ 'other'
456
+ ],
457
+ example: 'investment'
458
+ },
459
+ assetClass: {
460
+ type: 'string',
461
+ description:
462
+ 'Asset class (LIQUIDITY/EQUITY/.../LIABILITY), derived at read time from classification rules',
463
+ enum: [
464
+ 'LIQUIDITY',
465
+ 'EQUITY',
466
+ 'FIXED_INCOME',
467
+ 'PRECIOUS_METALS',
468
+ 'COMMODITY',
469
+ 'INSURANCE',
470
+ 'ALTERNATIVE_INVESTMENT',
471
+ 'PERSONAL_ASSETS',
472
+ 'LIABILITY',
473
+ 'REAL_ESTATE',
474
+ 'INDEX'
475
+ ]
476
+ },
477
+ assetSubClass: {
478
+ type: 'string',
479
+ description:
480
+ 'Asset sub-class (product type, derived at read time from classification rules)',
481
+ example: 'STOCK'
398
482
  }
399
483
  },
400
- required: ['path', 'type', 'description', 'tags', 'icon']
484
+ required: ['path', 'type', 'description', 'tags', 'icon', 'productCategory']
401
485
  } as const;
402
486
 
403
487
  export const $AccountStandardListResponseDto = {
@@ -458,7 +542,10 @@ export const $RegionConfigDto = {
458
542
  },
459
543
  locale: {
460
544
  type: 'string',
461
- example: 'de-DE'
545
+ example: 'de-DE',
546
+ pattern: '^[a-z]{2,8}-[A-Z]{2}$',
547
+ description:
548
+ "Region-qualified BCP-47 tag whose region subtag equals the region's own ISO 3166-1 code (e.g., ja-JP, zh-CN, zh-HK)"
462
549
  }
463
550
  },
464
551
  required: ['currency', 'dateFormat', 'locale']
@@ -471,6 +558,12 @@ export const $RegionInfoDto = {
471
558
  type: 'string',
472
559
  example: 'de'
473
560
  },
561
+ open: {
562
+ type: 'boolean',
563
+ example: true,
564
+ description:
565
+ 'Whether the region is open (has a ready regional account template). Not-yet-open regions still return identity metadata and degrade to the universal-only catalog.'
566
+ },
474
567
  displayName: {
475
568
  type: 'string',
476
569
  example: 'Germany'
@@ -489,7 +582,7 @@ export const $RegionInfoDto = {
489
582
  $ref: '#/components/schemas/RegionConfigDto'
490
583
  }
491
584
  },
492
- required: ['code', 'displayName', 'chain', 'config']
585
+ required: ['code', 'open', 'displayName', 'chain', 'config']
493
586
  } as const;
494
587
 
495
588
  export const $RegionsMetadataResponseDto = {
@@ -736,7 +829,7 @@ export const $PostingResponseDto = {
736
829
  units: {
737
830
  type: 'string',
738
831
  description:
739
- 'Amount as decimal string. Typed optional but always present in responses: interpolation fills any MISSING posting before it is persisted or returned.',
832
+ 'Amount as decimal string. Typed optional but always present in responses: interpolation fills any MISSING posting before it is persisted or returned. Carries the raw Beancount sign (credit-normal accounts such as Income post negative — the accounting truth, ADR-0126); renderers must not infer economic semantics from this sign.',
740
833
  example: '100.50'
741
834
  },
742
835
  currency: {
@@ -1102,7 +1195,7 @@ export const $PostingDetailDto = {
1102
1195
  units: {
1103
1196
  type: 'string',
1104
1197
  description:
1105
- 'Amount as decimal string. Typed optional but always present in responses: interpolation fills any MISSING posting before it is persisted or returned.',
1198
+ 'Amount as decimal string. Typed optional but always present in responses: interpolation fills any MISSING posting before it is persisted or returned. Carries the raw Beancount sign (credit-normal accounts such as Income post negative — the accounting truth, ADR-0126); renderers must not infer economic semantics from this sign.',
1106
1199
  example: '100.50'
1107
1200
  },
1108
1201
  currency: {
@@ -1286,6 +1379,147 @@ export const $TransactionDetailDto = {
1286
1379
  ]
1287
1380
  } as const;
1288
1381
 
1382
+ export const $TransactionListItemDto = {
1383
+ type: 'object',
1384
+ properties: {
1385
+ id: {
1386
+ type: 'string',
1387
+ description: 'Transaction ID',
1388
+ example: 'clh1234567890abcdef'
1389
+ },
1390
+ date: {
1391
+ type: 'string',
1392
+ description: 'Transaction date',
1393
+ example: '2024-11-28'
1394
+ },
1395
+ flag: {
1396
+ type: 'string',
1397
+ description: 'Transaction flag',
1398
+ enum: [
1399
+ 'CLEARED',
1400
+ 'PENDING',
1401
+ 'PADDING',
1402
+ 'SUMMARIZE',
1403
+ 'TRANSFER',
1404
+ 'CONVERSIONS'
1405
+ ],
1406
+ example: 'CLEARED'
1407
+ },
1408
+ customFlag: {
1409
+ type: 'string',
1410
+ description: 'Custom flag (if not using standard flags)',
1411
+ example: 'R'
1412
+ },
1413
+ payee: {
1414
+ type: 'string',
1415
+ description: 'Payee name',
1416
+ example: 'Whole Foods Market'
1417
+ },
1418
+ narration: {
1419
+ type: 'string',
1420
+ description: 'Transaction narration',
1421
+ example: 'Grocery shopping'
1422
+ },
1423
+ tags: {
1424
+ description: 'Transaction tags',
1425
+ example: ['groceries'],
1426
+ type: 'array',
1427
+ items: {
1428
+ type: 'string'
1429
+ }
1430
+ },
1431
+ links: {
1432
+ description: 'Transaction links',
1433
+ example: ['invoice-2024-001'],
1434
+ type: 'array',
1435
+ items: {
1436
+ type: 'string'
1437
+ }
1438
+ },
1439
+ meta: {
1440
+ type: 'object',
1441
+ description: 'Transaction metadata'
1442
+ },
1443
+ status: {
1444
+ type: 'string',
1445
+ description: 'Transaction status',
1446
+ enum: ['ACTIVE', 'VOIDED', 'SUPERSEDED'],
1447
+ example: 'ACTIVE'
1448
+ },
1449
+ sourceType: {
1450
+ type: 'string',
1451
+ description:
1452
+ 'Source type (free-form string from transaction metadata, e.g. import, api)'
1453
+ },
1454
+ sourcePlatform: {
1455
+ type: 'string',
1456
+ description: 'Source platform (e.g., alipay, wechat)',
1457
+ example: 'alipay'
1458
+ },
1459
+ postings: {
1460
+ description: 'Transaction postings',
1461
+ type: 'array',
1462
+ items: {
1463
+ $ref: '#/components/schemas/PostingDetailDto'
1464
+ }
1465
+ },
1466
+ createdAt: {
1467
+ type: 'string',
1468
+ description: 'Created at timestamp',
1469
+ example: '2024-11-28T10:30:00.000Z'
1470
+ },
1471
+ voidedAt: {
1472
+ type: 'string',
1473
+ description: 'Voided at timestamp (if voided)',
1474
+ example: '2024-11-29T15:00:00.000Z'
1475
+ },
1476
+ voidedBy: {
1477
+ type: 'string',
1478
+ description: 'User ID who voided this transaction',
1479
+ example: 'clh1234567890abcdef'
1480
+ },
1481
+ correctionReason: {
1482
+ type: 'string',
1483
+ description: 'Correction reason (if voided or superseded)',
1484
+ example: 'Duplicate entry'
1485
+ },
1486
+ supersededBy: {
1487
+ type: 'string',
1488
+ description:
1489
+ 'ID of the transaction that supersedes this one (set when status=SUPERSEDED)',
1490
+ example: 'clh1234567890abcdef'
1491
+ },
1492
+ originalTxn: {
1493
+ type: 'string',
1494
+ description:
1495
+ 'ID of the transaction this one corrected/replaced (back-link on the replacement)',
1496
+ example: 'clh1234567890abcdef'
1497
+ },
1498
+ viewpointAmount: {
1499
+ type: 'string',
1500
+ description:
1501
+ 'Row amount under the request viewpoint (ADR-0126). Category viewpoint (category + flow): per-leg sign-normalized sum over the category account set (Income-root legs negated, Expenses-root identity) — positive under normal booking but NOT clamped (explicit negative expense legs and net-flip refund months stay negative). No viewpoint (plain list / search, no accountId): wallet money-flow net = raw-sign sum over cost-less Assets/Liabilities legs (income positive, expenses negative, transfers net ~0); color cue is the wallet sign (net < 0 = wealth-decreasing). Status-orthogonal: audit views match too (ADR-0128 amount-as-matching-key). Omitted under the account viewpoint (incl. dual) and for rows with no wallet leg.',
1502
+ example: '10000.00'
1503
+ },
1504
+ viewpointCurrency: {
1505
+ type: 'string',
1506
+ description:
1507
+ 'Currency of viewpointAmount. A row spanning multiple currencies takes the largest-magnitude currency group (known simplification, ADR-0126).',
1508
+ example: 'CNY'
1509
+ }
1510
+ },
1511
+ required: [
1512
+ 'id',
1513
+ 'date',
1514
+ 'narration',
1515
+ 'tags',
1516
+ 'links',
1517
+ 'status',
1518
+ 'postings',
1519
+ 'createdAt'
1520
+ ]
1521
+ } as const;
1522
+
1289
1523
  export const $BalanceByCurrencyDto = {
1290
1524
  type: 'object',
1291
1525
  properties: {
@@ -1331,7 +1565,7 @@ export const $TransactionListSummaryDto = {
1331
1565
  totalAmount: {
1332
1566
  type: 'string',
1333
1567
  description:
1334
- 'Partial converted total in base currency (rated currencies only, raw Beancount sign). When warnings is non-empty this excludes currencies missing an FX rate; may be "0.00" if ALL non-base currencies lack a rate. Converted at the dateTo (or current) available rate.',
1568
+ 'Partial converted total in base currency (rated currencies only). Sign by viewpoint (ADR-0126): account viewpoint keeps the raw Beancount sign (income negative); category viewpoint is per-leg sign-normalized (Income legs negated, Expenses legs identity — positive under normal booking, not clamped). When warnings is non-empty this excludes currencies missing an FX rate; may be "0.00" if ALL non-base currencies lack a rate. Converted at the dateTo (or current) available rate.',
1335
1569
  example: '-6000.00'
1336
1570
  },
1337
1571
  currency: {
@@ -1357,6 +1591,26 @@ export const $TransactionListSummaryDto = {
1357
1591
  required: ['totalAmount', 'currency', 'balanceByCurrency']
1358
1592
  } as const;
1359
1593
 
1594
+ export const $TransactionListViewpointDto = {
1595
+ type: 'object',
1596
+ properties: {
1597
+ type: {
1598
+ type: 'string',
1599
+ description:
1600
+ 'Viewpoint type (only category drill-down carries a viewpoint today)',
1601
+ enum: ['category'],
1602
+ example: 'category'
1603
+ },
1604
+ flow: {
1605
+ type: 'string',
1606
+ description: 'Flow root the category account set is restricted to',
1607
+ enum: ['income', 'expense'],
1608
+ example: 'expense'
1609
+ }
1610
+ },
1611
+ required: ['type', 'flow']
1612
+ } as const;
1613
+
1360
1614
  export const $TransactionListResponseDto = {
1361
1615
  type: 'object',
1362
1616
  properties: {
@@ -1364,7 +1618,7 @@ export const $TransactionListResponseDto = {
1364
1618
  description: 'List of transactions',
1365
1619
  type: 'array',
1366
1620
  items: {
1367
- $ref: '#/components/schemas/TransactionDetailDto'
1621
+ $ref: '#/components/schemas/TransactionListItemDto'
1368
1622
  }
1369
1623
  },
1370
1624
  total: {
@@ -1390,6 +1644,15 @@ export const $TransactionListResponseDto = {
1390
1644
  $ref: '#/components/schemas/TransactionListSummaryDto'
1391
1645
  }
1392
1646
  ]
1647
+ },
1648
+ viewpoint: {
1649
+ description:
1650
+ 'Viewpoint metadata (ADR-0126). Present only for a single category filter (category + flow, no accountId); dual-perspective requests are viewpoint-less (raw signs, no viewpointAmount).',
1651
+ allOf: [
1652
+ {
1653
+ $ref: '#/components/schemas/TransactionListViewpointDto'
1654
+ }
1655
+ ]
1393
1656
  }
1394
1657
  },
1395
1658
  required: ['data', 'total', 'limit', 'offset']
@@ -1741,13 +2004,17 @@ export const $ReviewStatsDto = {
1741
2004
  type: 'object',
1742
2005
  description: 'Count by type'
1743
2006
  },
2007
+ resolved: {
2008
+ type: 'number',
2009
+ description: 'Current count of reviews in RESOLVED status'
2010
+ },
1744
2011
  oldestPending: {
1745
2012
  format: 'date-time',
1746
2013
  type: 'string',
1747
2014
  description: 'Oldest pending review date'
1748
2015
  }
1749
2016
  },
1750
- required: ['total', 'byType']
2017
+ required: ['total', 'byType', 'resolved']
1751
2018
  } as const;
1752
2019
 
1753
2020
  export const $DecisionOptionDto = {
@@ -2052,6 +2319,44 @@ export const $BatchResolveDto = {
2052
2319
  required: ['reviewIds', 'action']
2053
2320
  } as const;
2054
2321
 
2322
+ export const $BatchResolveItemDto = {
2323
+ type: 'object',
2324
+ properties: {
2325
+ reviewId: {
2326
+ type: 'string',
2327
+ description: 'Review item ID'
2328
+ },
2329
+ success: {
2330
+ type: 'boolean',
2331
+ description: 'Whether this item was resolved successfully'
2332
+ },
2333
+ resolutionId: {
2334
+ type: 'string',
2335
+ description:
2336
+ 'Resolution ID for undo. Present only on successful items (failed items stay PENDING).'
2337
+ },
2338
+ messageKey: {
2339
+ type: 'string',
2340
+ description:
2341
+ 'i18n message key for the per-item failure reason (e.g., review.payee.error.no_suggestion), mirroring the single-item resolve response. Present only on resolver rejections, not on unexpected errors.'
2342
+ },
2343
+ messageParams: {
2344
+ type: 'object',
2345
+ description:
2346
+ 'Parameters for message interpolation (e.g., { name: "PayeeName" })',
2347
+ additionalProperties: {
2348
+ type: 'string'
2349
+ }
2350
+ },
2351
+ error: {
2352
+ type: 'string',
2353
+ description:
2354
+ 'Diagnostic string: the messageKey on the resolver-rejection path; an i18n key mapped from the exception type on the unexpected-error path (#903, raw exception detail stays in server logs).'
2355
+ }
2356
+ },
2357
+ required: ['reviewId', 'success']
2358
+ } as const;
2359
+
2055
2360
  export const $BatchResolveResultDto = {
2056
2361
  type: 'object',
2057
2362
  properties: {
@@ -2067,7 +2372,7 @@ export const $BatchResolveResultDto = {
2067
2372
  description: 'Details for each item',
2068
2373
  type: 'array',
2069
2374
  items: {
2070
- type: 'string'
2375
+ $ref: '#/components/schemas/BatchResolveItemDto'
2071
2376
  }
2072
2377
  }
2073
2378
  },
@@ -2300,10 +2605,10 @@ export const $UpdatePayeeDto = {
2300
2605
  meta: {
2301
2606
  type: 'object',
2302
2607
  description:
2303
- 'Metadata for extended information (location, notes, contact info, etc.). Will merge with existing metadata.',
2608
+ 'Metadata for extended information (location, notes, contact info, etc.)',
2304
2609
  example: {
2305
2610
  location: 'Zhongguancun',
2306
- note: 'Updated note',
2611
+ note: 'Near subway station',
2307
2612
  favorite: true
2308
2613
  }
2309
2614
  },
@@ -2864,568 +3169,660 @@ export const $UpdateCommodityDto = {
2864
3169
  }
2865
3170
  } as const;
2866
3171
 
2867
- export const $CreateBeanPriceDto = {
3172
+ export const $CurrencyBalanceDto = {
2868
3173
  type: 'object',
2869
3174
  properties: {
2870
3175
  currency: {
2871
3176
  type: 'string',
2872
- description: 'Currency being priced (e.g., USD, AAPL, BTC)',
2873
- example: 'USD'
2874
- },
2875
- quoteCurrency: {
2876
- type: 'string',
2877
- description: 'Quote currency (pricing currency, e.g., CNY, EUR)',
3177
+ description: 'ISO 4217 currency code',
2878
3178
  example: 'CNY'
2879
3179
  },
2880
- amount: {
2881
- type: 'number',
2882
- description:
2883
- 'Price amount (MUST be >= 0 per Beancount spec, supports up to 15 decimal places). Zero allowed for conversion entries, negative strictly prohibited.',
2884
- example: 175.5,
2885
- minimum: 0
2886
- },
2887
- date: {
3180
+ balance: {
2888
3181
  type: 'string',
2889
- description: 'Price date (ISO 8601 format)',
2890
- example: '2024-11-05'
2891
- },
2892
- metadata: {
2893
- type: 'object',
2894
- description:
2895
- 'Metadata (validated by Zod schema, max field lengths enforced)',
2896
- example: {
2897
- source: 'MANUAL',
2898
- note: 'Bank valuation report',
2899
- confidence: 0.95
2900
- }
3182
+ description: 'Balance amount',
3183
+ example: '500000.00'
2901
3184
  }
2902
3185
  },
2903
- required: ['currency', 'quoteCurrency', 'amount', 'date']
3186
+ required: ['currency', 'balance']
2904
3187
  } as const;
2905
3188
 
2906
- export const $PriceResponseDto = {
3189
+ export const $TimeSeriesPointDto = {
2907
3190
  type: 'object',
2908
3191
  properties: {
2909
- id: {
3192
+ date: {
2910
3193
  type: 'string',
2911
- description: 'Unique identifier',
2912
- example: 'uuid-123-456'
3194
+ description: 'Date in YYYY-MM-DD format',
3195
+ example: '2024-06-15'
2913
3196
  },
2914
- userId: {
3197
+ value: {
2915
3198
  type: 'string',
2916
- description: 'User ID (owner of the price)',
2917
- example: 'user-123'
3199
+ description: 'Value at this date (in base currency)',
3200
+ example: '500000.00'
2918
3201
  },
2919
- currency: {
3202
+ change: {
2920
3203
  type: 'string',
2921
- description: 'Currency being priced (e.g., USD, AAPL, BTC)',
2922
- example: 'BTC'
3204
+ description: 'Change from previous point',
3205
+ example: '5000.00'
2923
3206
  },
2924
- quoteCurrency: {
3207
+ assets: {
2925
3208
  type: 'string',
2926
- description: 'Quote currency (pricing currency, e.g., USD, CNY)',
2927
- example: 'USD'
2928
- },
2929
- amount: {
2930
- type: 'number',
2931
- description:
2932
- 'Price amount (corresponds to Beancount Amount.number). Supports up to 15 decimal places.',
2933
- example: 50000
3209
+ description: 'Total assets at this date (in base currency)',
3210
+ example: '494338.00'
2934
3211
  },
2935
- date: {
3212
+ liabilities: {
2936
3213
  type: 'string',
2937
- description:
2938
- 'Price date (ISO 8601 format). Represents the date this price was valid.',
2939
- example: '2024-01-01',
2940
- format: 'date'
3214
+ description: 'Total liabilities at this date (in base currency)',
3215
+ example: '310098.00'
2941
3216
  },
2942
- meta: {
2943
- type: 'object',
2944
- description:
2945
- 'Metadata (corresponds to Beancount meta field). Contains source, confidence, note, etc.',
2946
- example: {
2947
- source: 'MANUAL',
2948
- note: 'User-defined price',
2949
- confidence: 1
3217
+ byCurrency: {
3218
+ description: 'Multi-currency breakdown for this point',
3219
+ type: 'array',
3220
+ items: {
3221
+ $ref: '#/components/schemas/CurrencyBalanceDto'
2950
3222
  }
3223
+ }
3224
+ },
3225
+ required: ['date', 'value']
3226
+ } as const;
3227
+
3228
+ export const $TrendSummaryDto = {
3229
+ type: 'object',
3230
+ properties: {
3231
+ startValue: {
3232
+ type: 'string',
3233
+ description: 'Value at start of period',
3234
+ example: '450000.00'
2951
3235
  },
2952
- createdAt: {
2953
- format: 'date-time',
3236
+ endValue: {
2954
3237
  type: 'string',
2955
- description: 'Creation timestamp',
2956
- example: '2024-11-03T10:00:00Z'
3238
+ description: 'Value at end of period',
3239
+ example: '500000.00'
2957
3240
  },
2958
- updatedAt: {
2959
- format: 'date-time',
3241
+ totalChange: {
2960
3242
  type: 'string',
2961
- description: 'Last update timestamp',
2962
- example: '2024-11-03T10:00:00Z'
3243
+ description: 'Total change over period',
3244
+ example: '50000.00'
3245
+ },
3246
+ totalChangePercentage: {
3247
+ type: 'string',
3248
+ description: 'Total change percentage',
3249
+ example: '+11.11%'
2963
3250
  }
2964
3251
  },
2965
- required: [
2966
- 'id',
2967
- 'userId',
2968
- 'currency',
2969
- 'quoteCurrency',
2970
- 'amount',
2971
- 'date',
2972
- 'meta',
2973
- 'createdAt',
2974
- 'updatedAt'
2975
- ]
3252
+ required: ['startValue', 'endValue', 'totalChange', 'totalChangePercentage']
2976
3253
  } as const;
2977
3254
 
2978
- export const $PriceListResponseDto = {
3255
+ export const $MultiCurrencyPointDto = {
2979
3256
  type: 'object',
2980
3257
  properties: {
2981
- items: {
2982
- description: 'List of prices',
3258
+ date: {
3259
+ type: 'string',
3260
+ description: 'Date in YYYY-MM-DD format',
3261
+ example: '2024-06-15'
3262
+ },
3263
+ byCurrency: {
3264
+ description: 'Balances by currency',
2983
3265
  type: 'array',
2984
3266
  items: {
2985
- $ref: '#/components/schemas/PriceResponseDto'
3267
+ $ref: '#/components/schemas/CurrencyBalanceDto'
2986
3268
  }
2987
- },
2988
- total: {
2989
- type: 'number',
2990
- description: 'Total number of prices',
2991
- example: 42
2992
3269
  }
2993
3270
  },
2994
- required: ['items', 'total']
3271
+ required: ['date', 'byCurrency']
2995
3272
  } as const;
2996
3273
 
2997
- export const $UpdateBeanPriceDto = {
3274
+ export const $PortfolioTrendsResponseDto = {
2998
3275
  type: 'object',
2999
3276
  properties: {
3000
- currency: {
3001
- type: 'string',
3002
- description: 'Currency being priced'
3277
+ series: {
3278
+ description: 'Time series data points',
3279
+ type: 'array',
3280
+ items: {
3281
+ $ref: '#/components/schemas/TimeSeriesPointDto'
3282
+ }
3003
3283
  },
3004
- quoteCurrency: {
3284
+ summary: {
3285
+ description: 'Period summary',
3286
+ allOf: [
3287
+ {
3288
+ $ref: '#/components/schemas/TrendSummaryDto'
3289
+ }
3290
+ ]
3291
+ },
3292
+ period: {
3005
3293
  type: 'string',
3006
- description: 'Quote currency (pricing currency)'
3294
+ description: 'Period requested',
3295
+ example: '6m'
3007
3296
  },
3008
- amount: {
3009
- type: 'number',
3010
- description: 'Price amount (MUST be >= 0 per Beancount spec)',
3011
- minimum: 0
3297
+ granularity: {
3298
+ type: 'string',
3299
+ description: 'Data granularity',
3300
+ example: 'month'
3012
3301
  },
3013
- date: {
3302
+ currency: {
3014
3303
  type: 'string',
3015
- description: 'Price date (ISO 8601 format)'
3304
+ description: 'Base currency for converted values',
3305
+ example: 'CNY'
3016
3306
  },
3017
- metadata: {
3018
- type: 'object',
3019
- description: 'Metadata'
3307
+ byCurrency: {
3308
+ description:
3309
+ 'Multi-currency time series (each point has currency breakdown)',
3310
+ type: 'array',
3311
+ items: {
3312
+ $ref: '#/components/schemas/MultiCurrencyPointDto'
3313
+ }
3314
+ },
3315
+ warnings: {
3316
+ description: 'Exchange rate warnings',
3317
+ type: 'array',
3318
+ items: {
3319
+ $ref: '#/components/schemas/ExchangeRateWarningDto'
3320
+ }
3020
3321
  }
3021
- }
3322
+ },
3323
+ required: ['series', 'summary', 'period', 'granularity', 'currency']
3022
3324
  } as const;
3023
3325
 
3024
- export const $CreateRecurringRuleDto = {
3326
+ export const $CashFlowPointDto = {
3025
3327
  type: 'object',
3026
3328
  properties: {
3027
- name: {
3329
+ month: {
3028
3330
  type: 'string',
3029
- description: 'Rule name (unique per user)',
3030
- maxLength: 100
3331
+ description: 'Month key (YYYY-MM)',
3332
+ example: '2024-03'
3031
3333
  },
3032
- icon: {
3334
+ income: {
3033
3335
  type: 'string',
3034
- description: 'Icon emoji',
3035
- maxLength: 10
3336
+ description: 'Income in base currency (absolute, converted)',
3337
+ example: '10000.00'
3036
3338
  },
3037
- frequency: {
3339
+ expense: {
3038
3340
  type: 'string',
3039
- description: 'Recurring frequency',
3040
- enum: [
3041
- 'WEEKLY',
3042
- 'BIWEEKLY',
3043
- 'MONTHLY',
3044
- 'BIMONTHLY',
3045
- 'QUARTERLY',
3046
- 'YEARLY',
3047
- 'CUSTOM'
3048
- ]
3049
- },
3050
- expectedAmount: {
3051
- type: 'number',
3052
- description: 'Expected amount (positive number)',
3053
- minimum: 0
3054
- },
3055
- expectedDay: {
3056
- type: 'number',
3057
- description: 'Expected day of month (1-31)',
3058
- minimum: 1,
3059
- maximum: 31
3060
- },
3061
- customIntervalDays: {
3062
- type: 'number',
3063
- description: 'Custom interval in days (required for CUSTOM frequency)',
3064
- minimum: 1
3341
+ description: 'Expense in base currency (absolute, converted)',
3342
+ example: '5000.00'
3065
3343
  },
3066
- currency: {
3344
+ netSavings: {
3067
3345
  type: 'string',
3068
- description: 'Currency code',
3069
- default: 'CNY',
3070
- maxLength: 10
3071
- },
3072
- matchPayeePattern: {
3073
- type: 'string',
3074
- description: 'Payee matching pattern (supports wildcards)',
3075
- maxLength: 200
3076
- },
3077
- matchAmountTolerance: {
3078
- type: 'number',
3079
- description: 'Amount tolerance percentage (0-1)',
3080
- default: 0.075,
3081
- minimum: 0,
3082
- maximum: 1
3083
- },
3084
- defaultExpenseAccount: {
3085
- type: 'string',
3086
- description: 'Default expense account for auto-create',
3087
- maxLength: 200
3088
- },
3089
- defaultPaymentAccount: {
3346
+ description: 'netSavings = income − expense (savings positive)',
3347
+ example: '5000.00'
3348
+ }
3349
+ },
3350
+ required: ['month', 'income', 'expense', 'netSavings']
3351
+ } as const;
3352
+
3353
+ export const $CashFlowTrendSummaryDto = {
3354
+ type: 'object',
3355
+ properties: {
3356
+ totalIncome: {
3090
3357
  type: 'string',
3091
- description: 'Default payment account for auto-create',
3092
- maxLength: 200
3358
+ description: 'Total income across the period',
3359
+ example: '60000.00'
3093
3360
  },
3094
- defaultPayee: {
3361
+ totalExpense: {
3095
3362
  type: 'string',
3096
- description: 'Default payee for auto-create',
3097
- maxLength: 200
3098
- },
3099
- autoCreate: {
3100
- type: 'boolean',
3101
- description: 'Auto-create transaction when expected date arrives',
3102
- default: false
3363
+ description: 'Total expense across the period',
3364
+ example: '30000.00'
3103
3365
  },
3104
- startDate: {
3366
+ totalNetSavings: {
3105
3367
  type: 'string',
3106
- description: 'Rule start date (ISO format)'
3368
+ description: 'income − expense across the period',
3369
+ example: '30000.00'
3107
3370
  },
3108
- endDate: {
3371
+ averageMonthlyNetSavings: {
3109
3372
  type: 'string',
3110
- description: 'Rule end date (ISO format)'
3373
+ description:
3374
+ 'totalNetSavings divided by the window length (N months, incl. zero-filled)',
3375
+ example: '5000.00'
3111
3376
  }
3112
3377
  },
3113
3378
  required: [
3114
- 'name',
3115
- 'frequency',
3116
- 'expectedAmount',
3117
- 'currency',
3118
- 'matchAmountTolerance',
3119
- 'autoCreate'
3379
+ 'totalIncome',
3380
+ 'totalExpense',
3381
+ 'totalNetSavings',
3382
+ 'averageMonthlyNetSavings'
3120
3383
  ]
3121
3384
  } as const;
3122
3385
 
3123
- export const $RecurringRuleResponseDto = {
3386
+ export const $CashFlowTrendsResponseDto = {
3124
3387
  type: 'object',
3125
3388
  properties: {
3126
- id: {
3127
- type: 'string',
3128
- description: 'Rule ID'
3389
+ series: {
3390
+ description:
3391
+ 'Monthly cash-flow series (fixed N-month window, zero-filled)',
3392
+ type: 'array',
3393
+ items: {
3394
+ $ref: '#/components/schemas/CashFlowPointDto'
3395
+ }
3129
3396
  },
3130
- userId: {
3131
- type: 'string',
3132
- description: 'User ID'
3397
+ summary: {
3398
+ description: 'Period totals',
3399
+ allOf: [
3400
+ {
3401
+ $ref: '#/components/schemas/CashFlowTrendSummaryDto'
3402
+ }
3403
+ ]
3133
3404
  },
3134
- name: {
3405
+ period: {
3135
3406
  type: 'string',
3136
- description: 'Rule name'
3137
- },
3138
- icon: {
3139
- type: 'object',
3140
- description: 'Icon emoji'
3407
+ description: 'Period requested',
3408
+ example: '6m'
3141
3409
  },
3142
- frequency: {
3410
+ granularity: {
3143
3411
  type: 'string',
3144
- description: 'Recurring frequency'
3145
- },
3146
- expectedAmount: {
3147
- type: 'number',
3148
- description: 'Expected amount'
3149
- },
3150
- expectedDay: {
3151
- type: 'object',
3152
- description: 'Expected day of month'
3412
+ description: 'Data granularity (v1 returns month buckets)',
3413
+ example: 'month'
3153
3414
  },
3154
- customIntervalDays: {
3155
- type: 'object',
3156
- description: 'Custom interval in days'
3415
+ currency: {
3416
+ type: 'string',
3417
+ description: 'Base currency for converted values',
3418
+ example: 'CNY'
3157
3419
  },
3420
+ warnings: {
3421
+ description: 'Exchange rate warnings (e.g. missing rate for a currency)',
3422
+ type: 'array',
3423
+ items: {
3424
+ $ref: '#/components/schemas/ExchangeRateWarningDto'
3425
+ }
3426
+ }
3427
+ },
3428
+ required: ['series', 'summary', 'period', 'granularity', 'currency']
3429
+ } as const;
3430
+
3431
+ export const $GenerateSnapshotBody = {
3432
+ type: 'object',
3433
+ properties: {}
3434
+ } as const;
3435
+
3436
+ export const $GenerateSnapshotResponse = {
3437
+ type: 'object',
3438
+ properties: {}
3439
+ } as const;
3440
+
3441
+ export const $BackfillSnapshotsBody = {
3442
+ type: 'object',
3443
+ properties: {}
3444
+ } as const;
3445
+
3446
+ export const $BackfillSnapshotsResponse = {
3447
+ type: 'object',
3448
+ properties: {}
3449
+ } as const;
3450
+
3451
+ export const $CreateBeanPriceDto = {
3452
+ type: 'object',
3453
+ properties: {
3158
3454
  currency: {
3159
3455
  type: 'string',
3160
- description: 'Currency code'
3456
+ description: 'Currency being priced (e.g., USD, AAPL, BTC)',
3457
+ example: 'USD'
3161
3458
  },
3162
- matchPayeePattern: {
3163
- type: 'object',
3164
- description: 'Payee matching pattern'
3459
+ quoteCurrency: {
3460
+ type: 'string',
3461
+ description: 'Quote currency (pricing currency, e.g., CNY, EUR)',
3462
+ example: 'CNY'
3165
3463
  },
3166
- matchAmountTolerance: {
3464
+ amount: {
3167
3465
  type: 'number',
3168
- description: 'Amount tolerance percentage'
3466
+ description:
3467
+ 'Price amount (MUST be >= 0 per Beancount spec, supports up to 15 decimal places). Zero allowed for conversion entries, negative strictly prohibited.',
3468
+ example: 175.5,
3469
+ minimum: 0
3169
3470
  },
3170
- defaultExpenseAccount: {
3171
- type: 'object',
3172
- description: 'Default expense account'
3471
+ date: {
3472
+ type: 'string',
3473
+ description: 'Price date (ISO 8601 format)',
3474
+ example: '2024-11-05'
3173
3475
  },
3174
- defaultPaymentAccount: {
3476
+ metadata: {
3175
3477
  type: 'object',
3176
- description: 'Default payment account'
3478
+ description:
3479
+ 'Metadata (validated by Zod schema, max field lengths enforced)',
3480
+ example: {
3481
+ source: 'MANUAL',
3482
+ note: 'Bank valuation report',
3483
+ confidence: 0.95
3484
+ }
3485
+ }
3486
+ },
3487
+ required: ['currency', 'quoteCurrency', 'amount', 'date']
3488
+ } as const;
3489
+
3490
+ export const $PriceResponseDto = {
3491
+ type: 'object',
3492
+ properties: {
3493
+ id: {
3494
+ type: 'string',
3495
+ description: 'Unique identifier',
3496
+ example: 'uuid-123-456'
3177
3497
  },
3178
- defaultPayee: {
3179
- type: 'object',
3180
- description: 'Default payee'
3498
+ userId: {
3499
+ type: 'string',
3500
+ description: 'User ID (owner of the price)',
3501
+ example: 'user-123'
3181
3502
  },
3182
- isActive: {
3183
- type: 'boolean',
3184
- description: 'Whether rule is active'
3503
+ currency: {
3504
+ type: 'string',
3505
+ description: 'Currency being priced (e.g., USD, AAPL, BTC)',
3506
+ example: 'BTC'
3185
3507
  },
3186
- startDate: {
3508
+ quoteCurrency: {
3187
3509
  type: 'string',
3188
- description: 'Rule start date (YYYY-MM-DD)'
3510
+ description: 'Quote currency (pricing currency, e.g., USD, CNY)',
3511
+ example: 'USD'
3189
3512
  },
3190
- endDate: {
3191
- type: 'object',
3192
- description: 'Rule end date (YYYY-MM-DD)'
3513
+ amount: {
3514
+ type: 'number',
3515
+ description:
3516
+ 'Price amount (corresponds to Beancount Amount.number). Supports up to 15 decimal places.',
3517
+ example: 50000
3193
3518
  },
3194
- autoCreate: {
3195
- type: 'boolean',
3196
- description: 'Auto-create transaction on expected date'
3519
+ date: {
3520
+ type: 'string',
3521
+ description:
3522
+ 'Price date (ISO 8601 format). Represents the date this price was valid.',
3523
+ example: '2024-01-01',
3524
+ format: 'date'
3197
3525
  },
3198
- lastOccurrence: {
3526
+ meta: {
3199
3527
  type: 'object',
3200
- description: 'Last matched occurrence date (YYYY-MM-DD)'
3201
- },
3202
- totalCount: {
3203
- type: 'number',
3204
- description: 'Total matched transactions count'
3528
+ description:
3529
+ 'Metadata (corresponds to Beancount meta field). Contains source, confidence, note, etc.',
3530
+ example: {
3531
+ source: 'MANUAL',
3532
+ note: 'User-defined price',
3533
+ confidence: 1
3534
+ }
3205
3535
  },
3206
3536
  createdAt: {
3207
3537
  format: 'date-time',
3208
3538
  type: 'string',
3209
- description: 'Created at timestamp'
3539
+ description: 'Creation timestamp',
3540
+ example: '2024-11-03T10:00:00Z'
3210
3541
  },
3211
3542
  updatedAt: {
3212
3543
  format: 'date-time',
3213
3544
  type: 'string',
3214
- description: 'Updated at timestamp'
3545
+ description: 'Last update timestamp',
3546
+ example: '2024-11-03T10:00:00Z'
3215
3547
  }
3216
3548
  },
3217
3549
  required: [
3218
3550
  'id',
3219
3551
  'userId',
3220
- 'name',
3221
- 'frequency',
3222
- 'expectedAmount',
3223
3552
  'currency',
3224
- 'matchAmountTolerance',
3225
- 'isActive',
3226
- 'startDate',
3227
- 'autoCreate',
3228
- 'totalCount',
3553
+ 'quoteCurrency',
3554
+ 'amount',
3555
+ 'date',
3556
+ 'meta',
3229
3557
  'createdAt',
3230
3558
  'updatedAt'
3231
3559
  ]
3232
3560
  } as const;
3233
3561
 
3234
- export const $CreateRuleFromTransactionDto = {
3562
+ export const $PriceListResponseDto = {
3235
3563
  type: 'object',
3236
3564
  properties: {
3237
- frequency: {
3565
+ items: {
3566
+ description: 'List of prices',
3567
+ type: 'array',
3568
+ items: {
3569
+ $ref: '#/components/schemas/PriceResponseDto'
3570
+ }
3571
+ },
3572
+ total: {
3573
+ type: 'number',
3574
+ description: 'Total number of prices',
3575
+ example: 42
3576
+ }
3577
+ },
3578
+ required: ['items', 'total']
3579
+ } as const;
3580
+
3581
+ export const $UpdateBeanPriceDto = {
3582
+ type: 'object',
3583
+ properties: {
3584
+ currency: {
3238
3585
  type: 'string',
3239
- description: 'Recurring frequency',
3240
- enum: [
3241
- 'WEEKLY',
3242
- 'BIWEEKLY',
3243
- 'MONTHLY',
3244
- 'BIMONTHLY',
3245
- 'QUARTERLY',
3246
- 'YEARLY',
3247
- 'CUSTOM'
3248
- ],
3249
- example: 'MONTHLY'
3586
+ description: 'Currency being priced'
3250
3587
  },
3251
- name: {
3588
+ quoteCurrency: {
3252
3589
  type: 'string',
3253
- description: 'Optional name override (default: transaction payee)',
3254
- maxLength: 100
3590
+ description: 'Quote currency (pricing currency)'
3255
3591
  },
3256
- icon: {
3592
+ amount: {
3593
+ type: 'number',
3594
+ description: 'Price amount (MUST be >= 0 per Beancount spec)',
3595
+ minimum: 0
3596
+ },
3597
+ date: {
3257
3598
  type: 'string',
3258
- description: 'Optional icon emoji',
3259
- maxLength: 10
3599
+ description: 'Price date (ISO 8601 format)'
3600
+ },
3601
+ metadata: {
3602
+ type: 'object',
3603
+ description: 'Metadata'
3604
+ }
3605
+ }
3606
+ } as const;
3607
+
3608
+ export const $DeleteOwnUserDto = {
3609
+ type: 'object',
3610
+ properties: {
3611
+ accessToken: {
3612
+ type: 'string',
3613
+ description: 'Access token for user verification',
3614
+ example: 'abc123xyz'
3260
3615
  }
3261
3616
  },
3262
- required: ['frequency']
3617
+ required: ['accessToken']
3263
3618
  } as const;
3264
3619
 
3265
- export const $RecurringRuleWithStatsResponseDto = {
3620
+ export const $UserSettingsResponseDto = {
3621
+ type: 'object',
3622
+ properties: {
3623
+ baseCurrency: {
3624
+ type: 'string',
3625
+ description:
3626
+ 'Stored base currency choice (ISO 4217) for net-worth/report aggregation. null = user never chose; aggregates fall back to the region default at display time (#713).',
3627
+ example: 'USD',
3628
+ nullable: true
3629
+ }
3630
+ },
3631
+ required: ['baseCurrency']
3632
+ } as const;
3633
+
3634
+ export const $UserResponseDto = {
3266
3635
  type: 'object',
3267
3636
  properties: {
3268
3637
  id: {
3269
3638
  type: 'string',
3270
- description: 'Rule ID'
3639
+ description: 'User ID'
3271
3640
  },
3272
- userId: {
3641
+ role: {
3273
3642
  type: 'string',
3274
- description: 'User ID'
3643
+ description: 'Assigned user role'
3275
3644
  },
3276
- name: {
3645
+ permissions: {
3646
+ description: 'Permission strings',
3647
+ type: 'array',
3648
+ items: {
3649
+ type: 'string'
3650
+ }
3651
+ },
3652
+ settings: {
3653
+ description: 'User settings',
3654
+ allOf: [
3655
+ {
3656
+ $ref: '#/components/schemas/UserSettingsResponseDto'
3657
+ }
3658
+ ]
3659
+ }
3660
+ },
3661
+ required: ['id', 'role', 'permissions', 'settings']
3662
+ } as const;
3663
+
3664
+ export const $SignupDto = {
3665
+ type: 'object',
3666
+ properties: {
3667
+ turnstileToken: {
3277
3668
  type: 'string',
3278
- description: 'Rule name'
3669
+ description:
3670
+ 'Cloudflare Turnstile verification token (optional when Turnstile disabled)',
3671
+ example: '0.abc123def456...'
3672
+ }
3673
+ }
3674
+ } as const;
3675
+
3676
+ export const $SignupResponseDto = {
3677
+ type: 'object',
3678
+ properties: {
3679
+ authToken: {
3680
+ type: 'string',
3681
+ description: 'JWT auth token',
3682
+ example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
3279
3683
  },
3280
- icon: {
3281
- type: 'object',
3282
- description: 'Icon emoji'
3684
+ accessToken: {
3685
+ type: 'string',
3686
+ description: 'Auto-generated access token'
3283
3687
  },
3284
- frequency: {
3688
+ role: {
3285
3689
  type: 'string',
3286
- description: 'Recurring frequency'
3690
+ description: 'Assigned user role',
3691
+ enum: ['USER', 'ADMIN', 'DEMO', 'INACTIVE', 'PAID', 'OPS']
3692
+ }
3693
+ },
3694
+ required: ['authToken', 'accessToken', 'role']
3695
+ } as const;
3696
+
3697
+ export const $UpdateUserSettingDto = {
3698
+ type: 'object',
3699
+ properties: {
3700
+ secId: {
3701
+ type: 'number',
3702
+ description: 'Security ID'
3287
3703
  },
3288
- expectedAmount: {
3704
+ annualInterestRate: {
3289
3705
  type: 'number',
3290
- description: 'Expected amount'
3706
+ description: 'Annual interest rate',
3707
+ example: 0.05
3291
3708
  },
3292
- expectedDay: {
3293
- type: 'object',
3294
- description: 'Expected day of month'
3709
+ currency: {
3710
+ type: 'string',
3711
+ description: 'Currency code',
3712
+ example: 'USD'
3295
3713
  },
3296
- customIntervalDays: {
3297
- type: 'object',
3298
- description: 'Custom interval in days'
3714
+ baseCurrency: {
3715
+ type: 'string',
3716
+ description: 'Base currency code',
3717
+ example: 'USD'
3299
3718
  },
3300
- currency: {
3719
+ benchmark: {
3301
3720
  type: 'string',
3302
- description: 'Currency code'
3721
+ description: 'Benchmark symbol',
3722
+ example: 'SPY'
3303
3723
  },
3304
- matchPayeePattern: {
3305
- type: 'object',
3306
- description: 'Payee matching pattern'
3724
+ colorScheme: {
3725
+ type: 'string',
3726
+ description: 'Color scheme',
3727
+ enum: ['DARK', 'LIGHT']
3307
3728
  },
3308
- matchAmountTolerance: {
3309
- type: 'number',
3310
- description: 'Amount tolerance percentage'
3729
+ dateRange: {
3730
+ type: 'string',
3731
+ description: 'Date range filter',
3732
+ example: '1y'
3311
3733
  },
3312
- defaultExpenseAccount: {
3313
- type: 'object',
3314
- description: 'Default expense account'
3734
+ emergencyFund: {
3735
+ type: 'number',
3736
+ description: 'Emergency fund amount',
3737
+ example: 10000
3315
3738
  },
3316
- defaultPaymentAccount: {
3317
- type: 'object',
3318
- description: 'Default payment account'
3739
+ 'filters.accounts': {
3740
+ description: 'Account filter IDs',
3741
+ type: 'array',
3742
+ items: {
3743
+ type: 'string'
3744
+ }
3319
3745
  },
3320
- defaultPayee: {
3321
- type: 'object',
3322
- description: 'Default payee'
3746
+ 'filters.assetClasses': {
3747
+ description: 'Asset class filters',
3748
+ type: 'array',
3749
+ items: {
3750
+ type: 'string'
3751
+ }
3323
3752
  },
3324
- isActive: {
3325
- type: 'boolean',
3326
- description: 'Whether rule is active'
3753
+ 'filters.dataSource': {
3754
+ type: 'string',
3755
+ description: 'Data source filter'
3327
3756
  },
3328
- startDate: {
3757
+ 'filters.symbol': {
3329
3758
  type: 'string',
3330
- description: 'Rule start date (YYYY-MM-DD)'
3759
+ description: 'Symbol filter'
3331
3760
  },
3332
- endDate: {
3333
- type: 'object',
3334
- description: 'Rule end date (YYYY-MM-DD)'
3761
+ 'filters.tags': {
3762
+ description: 'Tag filters',
3763
+ type: 'array',
3764
+ items: {
3765
+ type: 'string'
3766
+ }
3335
3767
  },
3336
- autoCreate: {
3768
+ isExperimentalFeatures: {
3337
3769
  type: 'boolean',
3338
- description: 'Auto-create transaction on expected date'
3339
- },
3340
- lastOccurrence: {
3341
- type: 'object',
3342
- description: 'Last matched occurrence date (YYYY-MM-DD)'
3770
+ description: 'Enable experimental features'
3343
3771
  },
3344
- totalCount: {
3345
- type: 'number',
3346
- description: 'Total matched transactions count'
3772
+ isRestrictedView: {
3773
+ type: 'boolean',
3774
+ description: 'Enable restricted view mode'
3347
3775
  },
3348
- createdAt: {
3349
- format: 'date-time',
3776
+ language: {
3350
3777
  type: 'string',
3351
- description: 'Created at timestamp'
3778
+ description: 'Language code',
3779
+ example: 'en'
3352
3780
  },
3353
- updatedAt: {
3354
- format: 'date-time',
3781
+ locale: {
3355
3782
  type: 'string',
3356
- description: 'Updated at timestamp'
3783
+ description: 'Locale code',
3784
+ example: 'en-US'
3357
3785
  },
3358
- pendingCount: {
3359
- type: 'number',
3360
- description: 'Number of pending expected transactions'
3361
- },
3362
- overdueCount: {
3363
- type: 'number',
3364
- description: 'Number of overdue expected transactions'
3365
- },
3366
- nextExpectedDate: {
3367
- type: 'object',
3368
- description: 'Next expected date (YYYY-MM-DD)'
3369
- },
3370
- totalAmount: {
3371
- type: 'number',
3372
- description: 'Total amount of all matched transactions'
3373
- },
3374
- averageAmount: {
3375
- type: 'number',
3376
- description: 'Average amount per transaction'
3377
- },
3378
- transactionCount: {
3786
+ projectedTotalAmount: {
3379
3787
  type: 'number',
3380
- description: 'Number of matched transactions'
3381
- },
3382
- firstDate: {
3383
- type: 'object',
3384
- description: 'First matched transaction date (YYYY-MM-DD)'
3788
+ description: 'Projected total amount',
3789
+ example: 1000000
3385
3790
  },
3386
- lastDate: {
3387
- type: 'object',
3388
- description: 'Last matched transaction date (YYYY-MM-DD)'
3791
+ retirementDate: {
3792
+ type: 'string',
3793
+ description: 'Retirement date in ISO 8601 format',
3794
+ example: '2050-01-01'
3389
3795
  },
3390
- variance: {
3796
+ savingsRate: {
3391
3797
  type: 'number',
3392
- description: 'Amount variance (standard deviation squared)'
3798
+ description: 'Savings rate percentage',
3799
+ example: 0.2
3393
3800
  },
3394
- upcomingCount: {
3395
- type: 'number',
3396
- description: 'Number of upcoming expected transactions'
3801
+ viewMode: {
3802
+ type: 'string',
3803
+ description: 'View mode',
3804
+ enum: ['DEFAULT', 'ZEN']
3805
+ }
3806
+ }
3807
+ } as const;
3808
+
3809
+ export const $UpdatePropertyDto = {
3810
+ type: 'object',
3811
+ properties: {
3812
+ value: {
3813
+ type: 'string',
3814
+ description: 'Property value'
3397
3815
  }
3398
3816
  },
3399
- required: [
3400
- 'id',
3401
- 'userId',
3402
- 'name',
3403
- 'frequency',
3404
- 'expectedAmount',
3405
- 'currency',
3406
- 'matchAmountTolerance',
3407
- 'isActive',
3408
- 'startDate',
3409
- 'autoCreate',
3410
- 'totalCount',
3411
- 'createdAt',
3412
- 'updatedAt',
3413
- 'pendingCount',
3414
- 'overdueCount',
3415
- 'totalAmount',
3416
- 'averageAmount',
3417
- 'transactionCount',
3418
- 'variance',
3419
- 'upcomingCount'
3420
- ]
3817
+ required: ['value']
3421
3818
  } as const;
3422
3819
 
3423
- export const $UpdateRecurringRuleDto = {
3820
+ export const $CreateRecurringRuleDto = {
3424
3821
  type: 'object',
3425
3822
  properties: {
3426
3823
  name: {
3427
3824
  type: 'string',
3428
- description: 'Rule name',
3825
+ description: 'Rule name (unique per user)',
3429
3826
  maxLength: 100
3430
3827
  },
3431
3828
  icon: {
@@ -3448,7 +3845,7 @@ export const $UpdateRecurringRuleDto = {
3448
3845
  },
3449
3846
  expectedAmount: {
3450
3847
  type: 'number',
3451
- description: 'Expected amount',
3848
+ description: 'Expected amount (positive number)',
3452
3849
  minimum: 0
3453
3850
  },
3454
3851
  expectedDay: {
@@ -3459,7 +3856,7 @@ export const $UpdateRecurringRuleDto = {
3459
3856
  },
3460
3857
  customIntervalDays: {
3461
3858
  type: 'number',
3462
- description: 'Custom interval in days',
3859
+ description: 'Custom interval in days (required for CUSTOM frequency)',
3463
3860
  minimum: 1
3464
3861
  },
3465
3862
  currency: {
@@ -3469,118 +3866,136 @@ export const $UpdateRecurringRuleDto = {
3469
3866
  },
3470
3867
  matchPayeePattern: {
3471
3868
  type: 'string',
3472
- description: 'Payee matching pattern',
3869
+ description: 'Payee matching pattern (supports wildcards)',
3473
3870
  maxLength: 200
3474
3871
  },
3475
3872
  matchAmountTolerance: {
3476
3873
  type: 'number',
3477
3874
  description: 'Amount tolerance percentage (0-1)',
3875
+ default: 0.075,
3478
3876
  minimum: 0,
3479
3877
  maximum: 1
3480
3878
  },
3481
3879
  defaultExpenseAccount: {
3482
3880
  type: 'string',
3483
- description: 'Default expense account',
3881
+ description: 'Default expense account for auto-create',
3484
3882
  maxLength: 200
3485
3883
  },
3486
3884
  defaultPaymentAccount: {
3487
3885
  type: 'string',
3488
- description: 'Default payment account',
3886
+ description: 'Default payment account for auto-create',
3489
3887
  maxLength: 200
3490
3888
  },
3491
3889
  defaultPayee: {
3492
3890
  type: 'string',
3493
- description: 'Default payee',
3891
+ description: 'Default payee for auto-create',
3494
3892
  maxLength: 200
3495
3893
  },
3496
3894
  autoCreate: {
3497
3895
  type: 'boolean',
3498
- description: 'Auto-create transaction'
3896
+ description: 'Auto-create transaction when expected date arrives',
3897
+ default: false
3499
3898
  },
3500
- isActive: {
3501
- type: 'boolean',
3502
- description: 'Rule active status'
3899
+ startDate: {
3900
+ type: 'string',
3901
+ description: 'Rule start date (ISO format)'
3503
3902
  },
3504
3903
  endDate: {
3505
3904
  type: 'string',
3506
3905
  description: 'Rule end date (ISO format)'
3507
3906
  }
3508
- }
3907
+ },
3908
+ required: [
3909
+ 'name',
3910
+ 'frequency',
3911
+ 'expectedAmount',
3912
+ 'matchAmountTolerance',
3913
+ 'autoCreate'
3914
+ ]
3509
3915
  } as const;
3510
3916
 
3511
- export const $ExpectedTransactionRuleDto = {
3917
+ export const $RecurringRuleResponseDto = {
3512
3918
  type: 'object',
3513
3919
  properties: {
3920
+ id: {
3921
+ type: 'string',
3922
+ description: 'Rule ID'
3923
+ },
3924
+ userId: {
3925
+ type: 'string',
3926
+ description: 'User ID'
3927
+ },
3514
3928
  name: {
3515
3929
  type: 'string',
3516
3930
  description: 'Rule name'
3517
3931
  },
3518
3932
  icon: {
3519
- type: 'object',
3520
- description: 'Rule icon'
3933
+ type: 'string',
3934
+ description: 'Icon emoji'
3521
3935
  },
3522
3936
  frequency: {
3523
3937
  type: 'string',
3524
- description: 'Rule frequency'
3938
+ description: 'Recurring frequency'
3939
+ },
3940
+ expectedAmount: {
3941
+ type: 'number',
3942
+ description: 'Expected amount'
3943
+ },
3944
+ expectedDay: {
3945
+ type: 'number',
3946
+ description: 'Expected day of month'
3947
+ },
3948
+ customIntervalDays: {
3949
+ type: 'number',
3950
+ description: 'Custom interval in days'
3525
3951
  },
3526
3952
  currency: {
3527
3953
  type: 'string',
3528
3954
  description: 'Currency code'
3529
- }
3530
- },
3531
- required: ['name', 'frequency', 'currency']
3532
- } as const;
3533
-
3534
- export const $ExpectedTransactionResponseDto = {
3535
- type: 'object',
3536
- properties: {
3537
- id: {
3538
- type: 'string',
3539
- description: 'Expected transaction ID'
3540
3955
  },
3541
- userId: {
3956
+ matchPayeePattern: {
3542
3957
  type: 'string',
3543
- description: 'User ID'
3958
+ description: 'Payee matching pattern'
3544
3959
  },
3545
- ruleId: {
3546
- type: 'string',
3547
- description: 'Associated rule ID'
3960
+ matchAmountTolerance: {
3961
+ type: 'number',
3962
+ description: 'Amount tolerance percentage'
3548
3963
  },
3549
- expectedDate: {
3964
+ defaultExpenseAccount: {
3550
3965
  type: 'string',
3551
- description: 'Expected date (YYYY-MM-DD)'
3966
+ description: 'Default expense account'
3552
3967
  },
3553
- expectedAmount: {
3554
- type: 'number',
3555
- description: 'Expected amount'
3968
+ defaultPaymentAccount: {
3969
+ type: 'string',
3970
+ description: 'Default payment account'
3556
3971
  },
3557
- status: {
3972
+ defaultPayee: {
3558
3973
  type: 'string',
3559
- description: 'Status (PENDING, COMPLETED, SKIPPED)'
3974
+ description: 'Default payee'
3560
3975
  },
3561
- matchedTransactionId: {
3562
- type: 'object',
3563
- description: 'Matched transaction ID'
3976
+ isActive: {
3977
+ type: 'boolean',
3978
+ description: 'Whether rule is active'
3564
3979
  },
3565
- matchedAt: {
3566
- type: 'object',
3567
- description: 'Match timestamp (ISO 8601)'
3980
+ startDate: {
3981
+ type: 'string',
3982
+ description: 'Rule start date (YYYY-MM-DD)'
3568
3983
  },
3569
- matchConfidence: {
3570
- type: 'object',
3571
- description: 'Match confidence score (0-1)'
3984
+ endDate: {
3985
+ type: 'string',
3986
+ description: 'Rule end date (YYYY-MM-DD)'
3572
3987
  },
3573
- isOverdue: {
3988
+ autoCreate: {
3574
3989
  type: 'boolean',
3575
- description: 'Whether this expected transaction is overdue'
3990
+ description: 'Auto-create transaction on expected date'
3576
3991
  },
3577
- rule: {
3578
- description: 'Rule information',
3579
- allOf: [
3580
- {
3581
- $ref: '#/components/schemas/ExpectedTransactionRuleDto'
3582
- }
3583
- ]
3992
+ lastOccurrence: {
3993
+ type: 'string',
3994
+ description: 'Last matched occurrence date (YYYY-MM-DD)'
3995
+ },
3996
+ totalCount: {
3997
+ type: 'number',
3998
+ description: 'Total matched transactions count'
3584
3999
  },
3585
4000
  createdAt: {
3586
4001
  format: 'date-time',
@@ -3596,647 +4011,581 @@ export const $ExpectedTransactionResponseDto = {
3596
4011
  required: [
3597
4012
  'id',
3598
4013
  'userId',
3599
- 'ruleId',
3600
- 'expectedDate',
4014
+ 'name',
4015
+ 'frequency',
3601
4016
  'expectedAmount',
3602
- 'status',
3603
- 'isOverdue',
3604
- 'rule',
4017
+ 'currency',
4018
+ 'matchAmountTolerance',
4019
+ 'isActive',
4020
+ 'startDate',
4021
+ 'autoCreate',
4022
+ 'totalCount',
3605
4023
  'createdAt',
3606
4024
  'updatedAt'
3607
4025
  ]
3608
4026
  } as const;
3609
4027
 
3610
- export const $ExpectedTransactionListResponseDto = {
4028
+ export const $CreateRuleFromTransactionDto = {
3611
4029
  type: 'object',
3612
4030
  properties: {
3613
- items: {
3614
- type: 'array',
3615
- items: {
3616
- $ref: '#/components/schemas/ExpectedTransactionResponseDto'
3617
- }
4031
+ frequency: {
4032
+ type: 'string',
4033
+ description: 'Recurring frequency',
4034
+ enum: [
4035
+ 'WEEKLY',
4036
+ 'BIWEEKLY',
4037
+ 'MONTHLY',
4038
+ 'BIMONTHLY',
4039
+ 'QUARTERLY',
4040
+ 'YEARLY',
4041
+ 'CUSTOM'
4042
+ ],
4043
+ example: 'MONTHLY'
3618
4044
  },
3619
- total: {
3620
- type: 'number',
3621
- description: 'Total count'
4045
+ name: {
4046
+ type: 'string',
4047
+ description: 'Optional name override (default: transaction payee)',
4048
+ maxLength: 100
4049
+ },
4050
+ icon: {
4051
+ type: 'string',
4052
+ description: 'Optional icon emoji',
4053
+ maxLength: 10
3622
4054
  }
3623
4055
  },
3624
- required: ['items', 'total']
3625
- } as const;
3626
-
3627
- export const $ConfirmMatchDto = {
3628
- type: 'object',
3629
- properties: {
3630
- transactionId: {
3631
- type: 'string',
3632
- description: 'Transaction ID to match with'
3633
- }
3634
- },
3635
- required: ['transactionId']
4056
+ required: ['frequency']
3636
4057
  } as const;
3637
4058
 
3638
- export const $EnterNowDto = {
4059
+ export const $RecurringRuleWithStatsResponseDto = {
3639
4060
  type: 'object',
3640
4061
  properties: {
3641
- expenseAccount: {
4062
+ id: {
3642
4063
  type: 'string',
3643
- description:
3644
- 'Override expense account (uses rule default if not provided)',
3645
- maxLength: 200
4064
+ description: 'Rule ID'
3646
4065
  },
3647
- paymentAccount: {
4066
+ userId: {
3648
4067
  type: 'string',
3649
- description:
3650
- 'Override payment account (uses rule default if not provided)',
3651
- maxLength: 200
4068
+ description: 'User ID'
3652
4069
  },
3653
- amount: {
3654
- type: 'number',
3655
- description: 'Override amount (uses expected amount if not provided)',
3656
- minimum: 0
4070
+ name: {
4071
+ type: 'string',
4072
+ description: 'Rule name'
3657
4073
  },
3658
- payee: {
4074
+ icon: {
3659
4075
  type: 'string',
3660
- description: 'Override payee (uses rule default if not provided)',
3661
- maxLength: 200
4076
+ description: 'Icon emoji'
3662
4077
  },
3663
- narration: {
4078
+ frequency: {
3664
4079
  type: 'string',
3665
- description: 'Optional narration',
3666
- maxLength: 500
3667
- }
3668
- }
3669
- } as const;
3670
-
3671
- export const $ForecastItemDto = {
3672
- type: 'object',
3673
- properties: {
3674
- rule: {
4080
+ description: 'Recurring frequency'
4081
+ },
4082
+ expectedAmount: {
4083
+ type: 'number',
4084
+ description: 'Expected amount'
4085
+ },
4086
+ expectedDay: {
4087
+ type: 'number',
4088
+ description: 'Expected day of month'
4089
+ },
4090
+ customIntervalDays: {
4091
+ type: 'number',
4092
+ description: 'Custom interval in days'
4093
+ },
4094
+ currency: {
3675
4095
  type: 'string',
3676
- description: 'Rule name',
3677
- example: 'Rent'
4096
+ description: 'Currency code'
3678
4097
  },
3679
- ruleId: {
4098
+ matchPayeePattern: {
3680
4099
  type: 'string',
3681
- description: 'Rule ID',
3682
- example: 'clx123...'
4100
+ description: 'Payee matching pattern'
3683
4101
  },
3684
- amount: {
4102
+ matchAmountTolerance: {
3685
4103
  type: 'number',
3686
- description: 'Expected amount',
3687
- example: 3000
4104
+ description: 'Amount tolerance percentage'
3688
4105
  },
3689
- date: {
4106
+ defaultExpenseAccount: {
3690
4107
  type: 'string',
3691
- description: 'Expected date (YYYY-MM-DD)',
3692
- example: '2024-04-01'
4108
+ description: 'Default expense account'
3693
4109
  },
3694
- icon: {
4110
+ defaultPaymentAccount: {
3695
4111
  type: 'string',
3696
- description: 'Rule icon emoji',
3697
- example: '🏠',
3698
- nullable: true
4112
+ description: 'Default payment account'
3699
4113
  },
3700
- currency: {
4114
+ defaultPayee: {
3701
4115
  type: 'string',
3702
- description: 'Currency code',
3703
- example: 'CNY'
3704
- }
3705
- },
3706
- required: ['rule', 'ruleId', 'amount', 'date', 'icon', 'currency']
3707
- } as const;
3708
-
3709
- export const $MonthlyForecastDto = {
3710
- type: 'object',
3711
- properties: {
3712
- month: {
4116
+ description: 'Default payee'
4117
+ },
4118
+ isActive: {
4119
+ type: 'boolean',
4120
+ description: 'Whether rule is active'
4121
+ },
4122
+ startDate: {
3713
4123
  type: 'string',
3714
- description: 'Month (YYYY-MM)',
3715
- example: '2024-04'
4124
+ description: 'Rule start date (YYYY-MM-DD)'
3716
4125
  },
3717
- expectedOutflow: {
4126
+ endDate: {
4127
+ type: 'string',
4128
+ description: 'Rule end date (YYYY-MM-DD)'
4129
+ },
4130
+ autoCreate: {
4131
+ type: 'boolean',
4132
+ description: 'Auto-create transaction on expected date'
4133
+ },
4134
+ lastOccurrence: {
4135
+ type: 'string',
4136
+ description: 'Last matched occurrence date (YYYY-MM-DD)'
4137
+ },
4138
+ totalCount: {
3718
4139
  type: 'number',
3719
- description: 'Total expected outflow for the month',
3720
- example: 8500
4140
+ description: 'Total matched transactions count'
3721
4141
  },
3722
- itemCount: {
4142
+ createdAt: {
4143
+ format: 'date-time',
4144
+ type: 'string',
4145
+ description: 'Created at timestamp'
4146
+ },
4147
+ updatedAt: {
4148
+ format: 'date-time',
4149
+ type: 'string',
4150
+ description: 'Updated at timestamp'
4151
+ },
4152
+ pendingCount: {
3723
4153
  type: 'number',
3724
- description: 'Number of expected transactions',
3725
- example: 3
4154
+ description: 'Number of pending expected transactions'
3726
4155
  },
3727
- byCurrency: {
3728
- type: 'object',
3729
- description: 'Breakdown by currency',
3730
- example: {
3731
- CNY: 8500,
3732
- USD: 100
3733
- }
4156
+ overdueCount: {
4157
+ type: 'number',
4158
+ description: 'Number of overdue expected transactions'
3734
4159
  },
3735
- items: {
3736
- description: 'Individual forecast items',
3737
- type: 'array',
3738
- items: {
3739
- $ref: '#/components/schemas/ForecastItemDto'
3740
- }
3741
- }
3742
- },
3743
- required: ['month', 'expectedOutflow', 'itemCount', 'byCurrency', 'items']
3744
- } as const;
3745
-
3746
- export const $ForecastResponseDto = {
3747
- type: 'object',
3748
- properties: {
3749
- forecast: {
3750
- description: 'Monthly forecast data',
3751
- type: 'array',
3752
- items: {
3753
- $ref: '#/components/schemas/MonthlyForecastDto'
3754
- }
4160
+ nextExpectedDate: {
4161
+ type: 'string',
4162
+ description: 'Next expected date (YYYY-MM-DD)'
3755
4163
  },
3756
- totalOutflow: {
4164
+ totalAmount: {
3757
4165
  type: 'number',
3758
- description: 'Total expected outflow across all months',
3759
- example: 25500
4166
+ description: 'Total amount of all matched transactions'
3760
4167
  },
3761
- totalByCurrency: {
3762
- type: 'object',
3763
- description: 'Total by currency across all months',
3764
- example: {
3765
- CNY: 25500,
3766
- USD: 300
3767
- }
4168
+ averageAmount: {
4169
+ type: 'number',
4170
+ description: 'Average amount per transaction'
3768
4171
  },
3769
- rulesCount: {
4172
+ transactionCount: {
3770
4173
  type: 'number',
3771
- description: 'Number of active recurring rules included',
3772
- example: 5
4174
+ description: 'Number of matched transactions'
3773
4175
  },
3774
- periodStart: {
4176
+ firstDate: {
3775
4177
  type: 'string',
3776
- description: 'Forecast period start date',
3777
- example: '2024-04-01'
4178
+ description: 'First matched transaction date (YYYY-MM-DD)'
3778
4179
  },
3779
- periodEnd: {
4180
+ lastDate: {
3780
4181
  type: 'string',
3781
- description: 'Forecast period end date',
3782
- example: '2024-06-30'
4182
+ description: 'Last matched transaction date (YYYY-MM-DD)'
4183
+ },
4184
+ variance: {
4185
+ type: 'number',
4186
+ description: 'Amount variance (standard deviation squared)'
4187
+ },
4188
+ upcomingCount: {
4189
+ type: 'number',
4190
+ description: 'Number of upcoming expected transactions'
3783
4191
  }
3784
4192
  },
3785
4193
  required: [
3786
- 'forecast',
3787
- 'totalOutflow',
3788
- 'totalByCurrency',
3789
- 'rulesCount',
3790
- 'periodStart',
3791
- 'periodEnd'
4194
+ 'id',
4195
+ 'userId',
4196
+ 'name',
4197
+ 'frequency',
4198
+ 'expectedAmount',
4199
+ 'currency',
4200
+ 'matchAmountTolerance',
4201
+ 'isActive',
4202
+ 'startDate',
4203
+ 'autoCreate',
4204
+ 'totalCount',
4205
+ 'createdAt',
4206
+ 'updatedAt',
4207
+ 'pendingCount',
4208
+ 'overdueCount',
4209
+ 'totalAmount',
4210
+ 'averageAmount',
4211
+ 'transactionCount',
4212
+ 'variance',
4213
+ 'upcomingCount'
3792
4214
  ]
3793
4215
  } as const;
3794
4216
 
3795
- export const $CurrencyBalanceDto = {
3796
- type: 'object',
3797
- properties: {
3798
- currency: {
3799
- type: 'string',
3800
- description: 'ISO 4217 currency code',
3801
- example: 'CNY'
3802
- },
3803
- balance: {
3804
- type: 'string',
3805
- description: 'Balance amount',
3806
- example: '500000.00'
3807
- }
3808
- },
3809
- required: ['currency', 'balance']
3810
- } as const;
3811
-
3812
- export const $TimeSeriesPointDto = {
4217
+ export const $UpdateRecurringRuleDto = {
3813
4218
  type: 'object',
3814
4219
  properties: {
3815
- date: {
3816
- type: 'string',
3817
- description: 'Date in YYYY-MM-DD format',
3818
- example: '2024-06-15'
3819
- },
3820
- value: {
4220
+ name: {
3821
4221
  type: 'string',
3822
- description: 'Value at this date (in base currency)',
3823
- example: '500000.00'
3824
- },
3825
- change: {
3826
- type: 'object',
3827
- description: 'Change from previous point',
3828
- example: '5000.00'
4222
+ description: 'Rule name (unique per user)',
4223
+ maxLength: 100
3829
4224
  },
3830
- assets: {
4225
+ icon: {
3831
4226
  type: 'string',
3832
- description: 'Total assets at this date (in base currency)',
3833
- example: '494338.00'
4227
+ description: 'Icon emoji',
4228
+ maxLength: 10
3834
4229
  },
3835
- liabilities: {
4230
+ frequency: {
3836
4231
  type: 'string',
3837
- description: 'Total liabilities at this date (in base currency)',
3838
- example: '310098.00'
4232
+ description: 'Recurring frequency',
4233
+ enum: [
4234
+ 'WEEKLY',
4235
+ 'BIWEEKLY',
4236
+ 'MONTHLY',
4237
+ 'BIMONTHLY',
4238
+ 'QUARTERLY',
4239
+ 'YEARLY',
4240
+ 'CUSTOM'
4241
+ ]
3839
4242
  },
3840
- byCurrency: {
3841
- description: 'Multi-currency breakdown for this point',
3842
- type: 'array',
3843
- items: {
3844
- $ref: '#/components/schemas/CurrencyBalanceDto'
3845
- }
3846
- }
3847
- },
3848
- required: ['date', 'value']
3849
- } as const;
3850
-
3851
- export const $TrendSummaryDto = {
3852
- type: 'object',
3853
- properties: {
3854
- startValue: {
3855
- type: 'string',
3856
- description: 'Value at start of period',
3857
- example: '450000.00'
4243
+ expectedAmount: {
4244
+ type: 'number',
4245
+ description: 'Expected amount (positive number)',
4246
+ minimum: 0
3858
4247
  },
3859
- endValue: {
3860
- type: 'string',
3861
- description: 'Value at end of period',
3862
- example: '500000.00'
4248
+ expectedDay: {
4249
+ type: 'number',
4250
+ description: 'Expected day of month (1-31)',
4251
+ minimum: 1,
4252
+ maximum: 31
3863
4253
  },
3864
- totalChange: {
4254
+ currency: {
3865
4255
  type: 'string',
3866
- description: 'Total change over period',
3867
- example: '50000.00'
4256
+ description: 'Currency code',
4257
+ maxLength: 10
3868
4258
  },
3869
- totalChangePercentage: {
3870
- type: 'string',
3871
- description: 'Total change percentage',
3872
- example: '+11.11%'
3873
- }
3874
- },
3875
- required: ['startValue', 'endValue', 'totalChange', 'totalChangePercentage']
3876
- } as const;
3877
-
3878
- export const $MultiCurrencyPointDto = {
3879
- type: 'object',
3880
- properties: {
3881
- date: {
4259
+ matchPayeePattern: {
3882
4260
  type: 'string',
3883
- description: 'Date in YYYY-MM-DD format',
3884
- example: '2024-06-15'
3885
- },
3886
- byCurrency: {
3887
- description: 'Balances by currency',
3888
- type: 'array',
3889
- items: {
3890
- $ref: '#/components/schemas/CurrencyBalanceDto'
3891
- }
3892
- }
3893
- },
3894
- required: ['date', 'byCurrency']
3895
- } as const;
3896
-
3897
- export const $PortfolioTrendsResponseDto = {
3898
- type: 'object',
3899
- properties: {
3900
- series: {
3901
- description: 'Time series data points',
3902
- type: 'array',
3903
- items: {
3904
- $ref: '#/components/schemas/TimeSeriesPointDto'
3905
- }
4261
+ description: 'Payee matching pattern (supports wildcards)',
4262
+ maxLength: 200
3906
4263
  },
3907
- summary: {
3908
- description: 'Period summary',
3909
- allOf: [
3910
- {
3911
- $ref: '#/components/schemas/TrendSummaryDto'
3912
- }
3913
- ]
4264
+ matchAmountTolerance: {
4265
+ type: 'number',
4266
+ description: 'Amount tolerance percentage (0-1)',
4267
+ default: 0.075,
4268
+ minimum: 0,
4269
+ maximum: 1
3914
4270
  },
3915
- period: {
4271
+ defaultExpenseAccount: {
3916
4272
  type: 'string',
3917
- description: 'Period requested',
3918
- example: '6m'
4273
+ description: 'Default expense account for auto-create',
4274
+ maxLength: 200
3919
4275
  },
3920
- granularity: {
4276
+ defaultPaymentAccount: {
3921
4277
  type: 'string',
3922
- description: 'Data granularity',
3923
- example: 'month'
4278
+ description: 'Default payment account for auto-create',
4279
+ maxLength: 200
3924
4280
  },
3925
- currency: {
4281
+ defaultPayee: {
3926
4282
  type: 'string',
3927
- description: 'Base currency for converted values',
3928
- example: 'CNY'
4283
+ description: 'Default payee for auto-create',
4284
+ maxLength: 200
3929
4285
  },
3930
- byCurrency: {
3931
- description:
3932
- 'Multi-currency time series (each point has currency breakdown)',
3933
- type: 'array',
3934
- items: {
3935
- $ref: '#/components/schemas/MultiCurrencyPointDto'
3936
- }
4286
+ autoCreate: {
4287
+ type: 'boolean',
4288
+ description: 'Auto-create transaction when expected date arrives',
4289
+ default: false
3937
4290
  },
3938
- warnings: {
3939
- description: 'Exchange rate warnings',
3940
- type: 'array',
3941
- items: {
3942
- $ref: '#/components/schemas/ExchangeRateWarningDto'
3943
- }
4291
+ endDate: {
4292
+ type: 'string',
4293
+ description: 'Rule end date (ISO format)'
4294
+ },
4295
+ customIntervalDays: {
4296
+ type: 'number',
4297
+ description: 'Custom interval in days',
4298
+ minimum: 1
4299
+ },
4300
+ isActive: {
4301
+ type: 'boolean',
4302
+ description: 'Rule active status'
3944
4303
  }
3945
- },
3946
- required: ['series', 'summary', 'period', 'granularity', 'currency']
4304
+ }
3947
4305
  } as const;
3948
4306
 
3949
- export const $CashFlowPointDto = {
4307
+ export const $ExpectedTransactionRuleDto = {
3950
4308
  type: 'object',
3951
4309
  properties: {
3952
- month: {
4310
+ name: {
3953
4311
  type: 'string',
3954
- description: 'Month key (YYYY-MM)',
3955
- example: '2024-03'
4312
+ description: 'Rule name'
3956
4313
  },
3957
- income: {
4314
+ icon: {
3958
4315
  type: 'string',
3959
- description: 'Income in base currency (absolute, converted)',
3960
- example: '10000.00'
4316
+ description: 'Rule icon'
3961
4317
  },
3962
- expense: {
4318
+ frequency: {
3963
4319
  type: 'string',
3964
- description: 'Expense in base currency (absolute, converted)',
3965
- example: '5000.00'
4320
+ description: 'Rule frequency'
3966
4321
  },
3967
- netSavings: {
4322
+ currency: {
3968
4323
  type: 'string',
3969
- description: 'netSavings = income − expense (savings positive)',
3970
- example: '5000.00'
4324
+ description: 'Currency code'
3971
4325
  }
3972
4326
  },
3973
- required: ['month', 'income', 'expense', 'netSavings']
4327
+ required: ['name', 'frequency', 'currency']
3974
4328
  } as const;
3975
4329
 
3976
- export const $CashFlowTrendSummaryDto = {
4330
+ export const $ExpectedTransactionResponseDto = {
3977
4331
  type: 'object',
3978
4332
  properties: {
3979
- totalIncome: {
4333
+ id: {
3980
4334
  type: 'string',
3981
- description: 'Total income across the period',
3982
- example: '60000.00'
4335
+ description: 'Expected transaction ID'
3983
4336
  },
3984
- totalExpense: {
4337
+ userId: {
3985
4338
  type: 'string',
3986
- description: 'Total expense across the period',
3987
- example: '30000.00'
4339
+ description: 'User ID'
3988
4340
  },
3989
- totalNetSavings: {
4341
+ ruleId: {
3990
4342
  type: 'string',
3991
- description: 'income − expense across the period',
3992
- example: '30000.00'
4343
+ description: 'Associated rule ID'
3993
4344
  },
3994
- averageMonthlyNetSavings: {
4345
+ expectedDate: {
3995
4346
  type: 'string',
3996
- description:
3997
- 'totalNetSavings divided by the window length (N months, incl. zero-filled)',
3998
- example: '5000.00'
3999
- }
4000
- },
4001
- required: [
4002
- 'totalIncome',
4003
- 'totalExpense',
4004
- 'totalNetSavings',
4005
- 'averageMonthlyNetSavings'
4006
- ]
4007
- } as const;
4008
-
4009
- export const $CashFlowTrendsResponseDto = {
4010
- type: 'object',
4011
- properties: {
4012
- series: {
4013
- description:
4014
- 'Monthly cash-flow series (fixed N-month window, zero-filled)',
4015
- type: 'array',
4016
- items: {
4017
- $ref: '#/components/schemas/CashFlowPointDto'
4018
- }
4347
+ description: 'Expected date (YYYY-MM-DD)'
4019
4348
  },
4020
- summary: {
4021
- description: 'Period totals',
4349
+ expectedAmount: {
4350
+ type: 'number',
4351
+ description: 'Expected amount'
4352
+ },
4353
+ status: {
4354
+ type: 'string',
4355
+ description: 'Status (PENDING, COMPLETED, SKIPPED)'
4356
+ },
4357
+ matchedTransactionId: {
4358
+ type: 'string',
4359
+ description: 'Matched transaction ID'
4360
+ },
4361
+ matchedAt: {
4362
+ type: 'string',
4363
+ description: 'Match timestamp (ISO 8601)'
4364
+ },
4365
+ matchConfidence: {
4366
+ type: 'number',
4367
+ description: 'Match confidence score (0-1)'
4368
+ },
4369
+ isOverdue: {
4370
+ type: 'boolean',
4371
+ description: 'Whether this expected transaction is overdue'
4372
+ },
4373
+ rule: {
4374
+ description: 'Rule information',
4022
4375
  allOf: [
4023
4376
  {
4024
- $ref: '#/components/schemas/CashFlowTrendSummaryDto'
4377
+ $ref: '#/components/schemas/ExpectedTransactionRuleDto'
4025
4378
  }
4026
4379
  ]
4027
4380
  },
4028
- period: {
4029
- type: 'string',
4030
- description: 'Period requested',
4031
- example: '6m'
4032
- },
4033
- granularity: {
4381
+ createdAt: {
4382
+ format: 'date-time',
4034
4383
  type: 'string',
4035
- description: 'Data granularity (v1 returns month buckets)',
4036
- example: 'month'
4384
+ description: 'Created at timestamp'
4037
4385
  },
4038
- currency: {
4386
+ updatedAt: {
4387
+ format: 'date-time',
4039
4388
  type: 'string',
4040
- description: 'Base currency for converted values',
4041
- example: 'CNY'
4042
- },
4043
- warnings: {
4044
- description: 'Exchange rate warnings (e.g. missing rate for a currency)',
4045
- type: 'array',
4046
- items: {
4047
- $ref: '#/components/schemas/ExchangeRateWarningDto'
4048
- }
4389
+ description: 'Updated at timestamp'
4049
4390
  }
4050
4391
  },
4051
- required: ['series', 'summary', 'period', 'granularity', 'currency']
4052
- } as const;
4053
-
4054
- export const $GenerateSnapshotBody = {
4055
- type: 'object',
4056
- properties: {}
4057
- } as const;
4058
-
4059
- export const $GenerateSnapshotResponse = {
4060
- type: 'object',
4061
- properties: {}
4062
- } as const;
4063
-
4064
- export const $BackfillSnapshotsBody = {
4065
- type: 'object',
4066
- properties: {}
4067
- } as const;
4068
-
4069
- export const $BackfillSnapshotsResponse = {
4070
- type: 'object',
4071
- properties: {}
4392
+ required: [
4393
+ 'id',
4394
+ 'userId',
4395
+ 'ruleId',
4396
+ 'expectedDate',
4397
+ 'expectedAmount',
4398
+ 'status',
4399
+ 'isOverdue',
4400
+ 'rule',
4401
+ 'createdAt',
4402
+ 'updatedAt'
4403
+ ]
4072
4404
  } as const;
4073
4405
 
4074
- export const $DeleteOwnUserDto = {
4406
+ export const $ExpectedTransactionListResponseDto = {
4075
4407
  type: 'object',
4076
4408
  properties: {
4077
- accessToken: {
4078
- type: 'string',
4079
- description: 'Access token for user verification',
4080
- example: 'abc123xyz'
4409
+ items: {
4410
+ type: 'array',
4411
+ items: {
4412
+ $ref: '#/components/schemas/ExpectedTransactionResponseDto'
4413
+ }
4414
+ },
4415
+ total: {
4416
+ type: 'number',
4417
+ description: 'Total count'
4081
4418
  }
4082
4419
  },
4083
- required: ['accessToken']
4420
+ required: ['items', 'total']
4084
4421
  } as const;
4085
4422
 
4086
- export const $SignupDto = {
4423
+ export const $ConfirmMatchDto = {
4087
4424
  type: 'object',
4088
4425
  properties: {
4089
- turnstileToken: {
4426
+ transactionId: {
4090
4427
  type: 'string',
4091
- description:
4092
- 'Cloudflare Turnstile verification token (optional when Turnstile disabled)',
4093
- example: '0.abc123def456...'
4428
+ description: 'Transaction ID to match with'
4094
4429
  }
4095
- }
4430
+ },
4431
+ required: ['transactionId']
4096
4432
  } as const;
4097
4433
 
4098
- export const $SignupResponseDto = {
4434
+ export const $EnterNowDto = {
4099
4435
  type: 'object',
4100
4436
  properties: {
4101
- authToken: {
4437
+ expenseAccount: {
4102
4438
  type: 'string',
4103
- description: 'JWT auth token',
4104
- example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
4439
+ description:
4440
+ 'Override expense account (uses rule default if not provided)',
4441
+ maxLength: 200
4105
4442
  },
4106
- accessToken: {
4443
+ paymentAccount: {
4107
4444
  type: 'string',
4108
- description: 'Auto-generated access token'
4445
+ description:
4446
+ 'Override payment account (uses rule default if not provided)',
4447
+ maxLength: 200
4448
+ },
4449
+ amount: {
4450
+ type: 'number',
4451
+ description: 'Override amount (uses expected amount if not provided)',
4452
+ minimum: 0
4453
+ },
4454
+ payee: {
4455
+ type: 'string',
4456
+ description: 'Override payee (uses rule default if not provided)',
4457
+ maxLength: 200
4109
4458
  },
4110
- role: {
4459
+ narration: {
4111
4460
  type: 'string',
4112
- description: 'Assigned user role',
4113
- enum: ['USER', 'ADMIN', 'DEMO', 'INACTIVE', 'PAID', 'OPS']
4461
+ description: 'Optional narration',
4462
+ maxLength: 500
4114
4463
  }
4115
- },
4116
- required: ['authToken', 'accessToken', 'role']
4464
+ }
4117
4465
  } as const;
4118
4466
 
4119
- export const $UpdateUserSettingDto = {
4467
+ export const $ForecastItemDto = {
4120
4468
  type: 'object',
4121
4469
  properties: {
4122
- secId: {
4123
- type: 'number',
4124
- description: 'Security ID'
4470
+ rule: {
4471
+ type: 'string',
4472
+ description: 'Rule name',
4473
+ example: 'Rent'
4125
4474
  },
4126
- annualInterestRate: {
4475
+ ruleId: {
4476
+ type: 'string',
4477
+ description: 'Rule ID',
4478
+ example: 'clx123...'
4479
+ },
4480
+ amount: {
4127
4481
  type: 'number',
4128
- description: 'Annual interest rate',
4129
- example: 0.05
4482
+ description: 'Expected amount',
4483
+ example: 3000
4130
4484
  },
4131
- currency: {
4485
+ date: {
4132
4486
  type: 'string',
4133
- description: 'Currency code',
4134
- example: 'USD'
4487
+ description: 'Expected date (YYYY-MM-DD)',
4488
+ example: '2024-04-01'
4135
4489
  },
4136
- baseCurrency: {
4490
+ icon: {
4137
4491
  type: 'string',
4138
- description: 'Base currency code',
4139
- example: 'USD'
4492
+ description: 'Rule icon emoji',
4493
+ example: '🏠',
4494
+ nullable: true
4140
4495
  },
4141
- benchmark: {
4496
+ currency: {
4142
4497
  type: 'string',
4143
- description: 'Benchmark symbol',
4144
- example: 'SPY'
4145
- },
4146
- colorScheme: {
4498
+ description: 'Currency code',
4499
+ example: 'CNY'
4500
+ }
4501
+ },
4502
+ required: ['rule', 'ruleId', 'amount', 'date', 'icon', 'currency']
4503
+ } as const;
4504
+
4505
+ export const $MonthlyForecastDto = {
4506
+ type: 'object',
4507
+ properties: {
4508
+ month: {
4147
4509
  type: 'string',
4148
- description: 'Color scheme',
4149
- enum: ['DARK', 'LIGHT']
4510
+ description: 'Month (YYYY-MM)',
4511
+ example: '2024-04'
4150
4512
  },
4151
- dateRange: {
4152
- type: 'string',
4153
- description: 'Date range filter',
4154
- example: '1y'
4513
+ expectedOutflow: {
4514
+ type: 'number',
4515
+ description: 'Total expected outflow for the month',
4516
+ example: 8500
4155
4517
  },
4156
- emergencyFund: {
4518
+ itemCount: {
4157
4519
  type: 'number',
4158
- description: 'Emergency fund amount',
4159
- example: 10000
4520
+ description: 'Number of expected transactions',
4521
+ example: 3
4160
4522
  },
4161
- 'filters.accounts': {
4162
- description: 'Account filter IDs',
4163
- type: 'array',
4164
- items: {
4165
- type: 'string'
4523
+ byCurrency: {
4524
+ type: 'object',
4525
+ description: 'Breakdown by currency',
4526
+ example: {
4527
+ CNY: 8500,
4528
+ USD: 100
4166
4529
  }
4167
4530
  },
4168
- 'filters.assetClasses': {
4169
- description: 'Asset class filters',
4531
+ items: {
4532
+ description: 'Individual forecast items',
4170
4533
  type: 'array',
4171
4534
  items: {
4172
- type: 'string'
4535
+ $ref: '#/components/schemas/ForecastItemDto'
4173
4536
  }
4174
- },
4175
- 'filters.dataSource': {
4176
- type: 'string',
4177
- description: 'Data source filter'
4178
- },
4179
- 'filters.symbol': {
4180
- type: 'string',
4181
- description: 'Symbol filter'
4182
- },
4183
- 'filters.tags': {
4184
- description: 'Tag filters',
4537
+ }
4538
+ },
4539
+ required: ['month', 'expectedOutflow', 'itemCount', 'byCurrency', 'items']
4540
+ } as const;
4541
+
4542
+ export const $ForecastResponseDto = {
4543
+ type: 'object',
4544
+ properties: {
4545
+ forecast: {
4546
+ description: 'Monthly forecast data',
4185
4547
  type: 'array',
4186
4548
  items: {
4187
- type: 'string'
4549
+ $ref: '#/components/schemas/MonthlyForecastDto'
4188
4550
  }
4189
4551
  },
4190
- isExperimentalFeatures: {
4191
- type: 'boolean',
4192
- description: 'Enable experimental features'
4193
- },
4194
- isRestrictedView: {
4195
- type: 'boolean',
4196
- description: 'Enable restricted view mode'
4197
- },
4198
- language: {
4199
- type: 'string',
4200
- description: 'Language code',
4201
- example: 'en'
4202
- },
4203
- locale: {
4204
- type: 'string',
4205
- description: 'Locale code',
4206
- example: 'en-US'
4207
- },
4208
- projectedTotalAmount: {
4552
+ totalOutflow: {
4209
4553
  type: 'number',
4210
- description: 'Projected total amount',
4211
- example: 1000000
4554
+ description: 'Total expected outflow across all months',
4555
+ example: 25500
4212
4556
  },
4213
- retirementDate: {
4214
- type: 'string',
4215
- description: 'Retirement date in ISO 8601 format',
4216
- example: '2050-01-01'
4557
+ totalByCurrency: {
4558
+ type: 'object',
4559
+ description: 'Total by currency across all months',
4560
+ example: {
4561
+ CNY: 25500,
4562
+ USD: 300
4563
+ }
4217
4564
  },
4218
- savingsRate: {
4565
+ rulesCount: {
4219
4566
  type: 'number',
4220
- description: 'Savings rate percentage',
4221
- example: 0.2
4567
+ description: 'Number of active recurring rules included',
4568
+ example: 5
4222
4569
  },
4223
- viewMode: {
4570
+ periodStart: {
4224
4571
  type: 'string',
4225
- description: 'View mode',
4226
- enum: ['DEFAULT', 'ZEN']
4227
- }
4228
- }
4229
- } as const;
4230
-
4231
- export const $UpdatePropertyDto = {
4232
- type: 'object',
4233
- properties: {
4234
- value: {
4572
+ description: 'Forecast period start date',
4573
+ example: '2024-04-01'
4574
+ },
4575
+ periodEnd: {
4235
4576
  type: 'string',
4236
- description: 'Property value'
4577
+ description: 'Forecast period end date',
4578
+ example: '2024-06-30'
4237
4579
  }
4238
4580
  },
4239
- required: ['value']
4581
+ required: [
4582
+ 'forecast',
4583
+ 'totalOutflow',
4584
+ 'totalByCurrency',
4585
+ 'rulesCount',
4586
+ 'periodStart',
4587
+ 'periodEnd'
4588
+ ]
4240
4589
  } as const;
4241
4590
 
4242
4591
  export const $CreateTransactionRuleDto = {
@@ -4798,7 +5147,8 @@ export const $UpdateTransactionRuleDto = {
4798
5147
  },
4799
5148
  matchLogic: {
4800
5149
  type: 'string',
4801
- enum: ['OR', 'AND']
5150
+ enum: ['OR', 'AND'],
5151
+ default: 'OR'
4802
5152
  },
4803
5153
  amountMin: {
4804
5154
  type: 'number',
@@ -4812,13 +5162,10 @@ export const $UpdateTransactionRuleDto = {
4812
5162
  },
4813
5163
  priority: {
4814
5164
  type: 'number',
5165
+ default: 50,
4815
5166
  minimum: 0,
4816
5167
  maximum: 1000
4817
5168
  },
4818
- enabled: {
4819
- type: 'boolean',
4820
- description: 'Enable or disable the rule'
4821
- },
4822
5169
  additionalTags: {
4823
5170
  items: {
4824
5171
  type: 'array'
@@ -4828,6 +5175,10 @@ export const $UpdateTransactionRuleDto = {
4828
5175
  },
4829
5176
  additionalMetadata: {
4830
5177
  type: 'object'
5178
+ },
5179
+ enabled: {
5180
+ type: 'boolean',
5181
+ description: 'Enable or disable the rule'
4831
5182
  }
4832
5183
  }
4833
5184
  } as const;
@@ -4856,36 +5207,113 @@ export const $TestRuleDto = {
4856
5207
  maxLength: 10
4857
5208
  }
4858
5209
  },
4859
- required: ['narration']
5210
+ required: ['narration']
5211
+ } as const;
5212
+
5213
+ export const $TestRuleResponseDto = {
5214
+ type: 'object',
5215
+ properties: {
5216
+ ruleId: {
5217
+ type: 'string',
5218
+ description: 'Rule ID that was tested'
5219
+ },
5220
+ matches: {
5221
+ type: 'boolean',
5222
+ description: 'Whether the rule matched the test data'
5223
+ },
5224
+ confidence: {
5225
+ type: 'number',
5226
+ description: 'Match confidence score (0-1)',
5227
+ example: 0.85
5228
+ },
5229
+ matchDetails: {
5230
+ type: 'object',
5231
+ description: 'Details of which fields matched',
5232
+ example: {
5233
+ narration: true,
5234
+ payee: false,
5235
+ categoryAccount: false
5236
+ }
5237
+ }
5238
+ },
5239
+ required: ['ruleId', 'matches', 'confidence', 'matchDetails']
5240
+ } as const;
5241
+
5242
+ export const $CategoryCatalogEntryDto = {
5243
+ type: 'object',
5244
+ properties: {
5245
+ slug: {
5246
+ type: 'string',
5247
+ description: 'Category slug (single source-of-truth)',
5248
+ example: 'food'
5249
+ },
5250
+ scenario: {
5251
+ type: 'string',
5252
+ description: 'Display scenario group (maps to frontend picker _scenario)',
5253
+ enum: [
5254
+ 'expense',
5255
+ 'income',
5256
+ 'investment',
5257
+ 'banking',
5258
+ 'transfer',
5259
+ 'payment'
5260
+ ],
5261
+ example: 'expense'
5262
+ },
5263
+ icon: {
5264
+ type: 'string',
5265
+ description: 'Lucide icon name',
5266
+ example: 'utensils'
5267
+ },
5268
+ regions: {
5269
+ description: "Applicable regions ('*' = all, 'cn' = CN-only)",
5270
+ example: ['*'],
5271
+ type: 'array',
5272
+ items: {
5273
+ type: 'string'
5274
+ }
5275
+ },
5276
+ categoryAccounts: {
5277
+ description:
5278
+ "Beancount account paths (categoryAccount) of the region-enabled system rules whose categoryKeywords include this slug (#816). System rules only (public endpoint — user rules excluded); one-to-many by design (e.g. 'utilities' → Electricity/Water/Internet/Gas), sorted, [] when no rule maps the slug.",
5279
+ example: [
5280
+ 'Expenses:Utilities:Electricity',
5281
+ 'Expenses:Utilities:Gas',
5282
+ 'Expenses:Utilities:Internet',
5283
+ 'Expenses:Utilities:Water'
5284
+ ],
5285
+ type: 'array',
5286
+ items: {
5287
+ type: 'string'
5288
+ }
5289
+ }
5290
+ },
5291
+ required: ['slug', 'scenario', 'icon', 'regions', 'categoryAccounts']
4860
5292
  } as const;
4861
5293
 
4862
- export const $TestRuleResponseDto = {
5294
+ export const $CategoryCatalogListResponseDto = {
4863
5295
  type: 'object',
4864
5296
  properties: {
4865
- ruleId: {
4866
- type: 'string',
4867
- description: 'Rule ID that was tested'
4868
- },
4869
- matches: {
4870
- type: 'boolean',
4871
- description: 'Whether the rule matched the test data'
5297
+ items: {
5298
+ description: 'Category entries (region-scoped, query-filtered)',
5299
+ type: 'array',
5300
+ items: {
5301
+ $ref: '#/components/schemas/CategoryCatalogEntryDto'
5302
+ }
4872
5303
  },
4873
- confidence: {
5304
+ total: {
4874
5305
  type: 'number',
4875
- description: 'Match confidence score (0-1)',
4876
- example: 0.85
5306
+ description:
5307
+ 'Total category entries for the region (before query filtering)',
5308
+ example: 30
4877
5309
  },
4878
- matchDetails: {
4879
- type: 'object',
4880
- description: 'Details of which fields matched',
4881
- example: {
4882
- narration: true,
4883
- payee: false,
4884
- categoryAccount: false
4885
- }
5310
+ region: {
5311
+ type: 'string',
5312
+ description: 'Region code',
5313
+ example: 'cn'
4886
5314
  }
4887
5315
  },
4888
- required: ['ruleId', 'matches', 'confidence', 'matchDetails']
5316
+ required: ['items', 'total', 'region']
4889
5317
  } as const;
4890
5318
 
4891
5319
  export const $CreateBeanEventDto = {
@@ -5051,6 +5479,14 @@ export const $OnboardingAccountDto = {
5051
5479
  description:
5052
5480
  'Platform ID to bind the account to (references Platform.id); omit for unbound',
5053
5481
  example: 'c98e5d4a-2f71-4a5a-bb3c-92c9f231d5e2'
5482
+ },
5483
+ displayName: {
5484
+ type: 'string',
5485
+ description:
5486
+ 'User-set display name override (omit/null = keep the derived name)',
5487
+ nullable: true,
5488
+ maxLength: 50,
5489
+ example: 'Salary card'
5054
5490
  }
5055
5491
  },
5056
5492
  required: ['path', 'currency']
@@ -5630,7 +6066,8 @@ export const $UpdateMapperDefaultsDto = {
5630
6066
  type: 'string',
5631
6067
  description: 'Source account for transactions (Beancount format)',
5632
6068
  example: 'Assets:CN:Alipay:Balance',
5633
- pattern: '^[A-Z][a-zA-Z0-9-]*:[a-zA-Z0-9-:]+$'
6069
+ pattern:
6070
+ '^(Assets|Liabilities|Income|Expenses|Equity)(:[A-Za-z0-9][A-Za-z0-9-]*)+$'
5634
6071
  },
5635
6072
  currency: {
5636
6073
  type: 'string',
@@ -5644,13 +6081,15 @@ export const $UpdateMapperDefaultsDto = {
5644
6081
  type: 'string',
5645
6082
  description: 'Default expense account (optional)',
5646
6083
  example: 'Expenses:Unknown',
5647
- pattern: '^[A-Z][a-zA-Z0-9-]*:[a-zA-Z0-9-:]+$'
6084
+ pattern:
6085
+ '^(Assets|Liabilities|Income|Expenses|Equity)(:[A-Za-z0-9][A-Za-z0-9-]*)+$'
5648
6086
  },
5649
6087
  incomeAccount: {
5650
6088
  type: 'string',
5651
6089
  description: 'Default income account (optional)',
5652
6090
  example: 'Income:Unknown',
5653
- pattern: '^[A-Z][a-zA-Z0-9-]*:[a-zA-Z0-9-:]+$'
6091
+ pattern:
6092
+ '^(Assets|Liabilities|Income|Expenses|Equity)(:[A-Za-z0-9][A-Za-z0-9-]*)+$'
5654
6093
  },
5655
6094
  methodAccountMapping: {
5656
6095
  type: 'object',
@@ -5707,12 +6146,14 @@ export const $ProviderSyncConfigDto = {
5707
6146
  },
5708
6147
  defaultExpenseAccount: {
5709
6148
  type: 'string',
5710
- description: 'Default expense account for the second posting',
6149
+ description:
6150
+ 'Default expense account for the second posting. Omit when no real default exists; the pipeline routes to Review via the Uncategorized sentinel (#618).',
5711
6151
  example: 'Expenses:Unknown'
5712
6152
  },
5713
6153
  defaultIncomeAccount: {
5714
6154
  type: 'string',
5715
- description: 'Default income account for the second posting',
6155
+ description:
6156
+ 'Default income account for the second posting. Omit when no real default exists; the pipeline routes to Review via the Uncategorized sentinel (#618).',
5716
6157
  example: 'Income:Unknown'
5717
6158
  },
5718
6159
  filterPending: {
@@ -5727,12 +6168,7 @@ export const $ProviderSyncConfigDto = {
5727
6168
  example: 'acc_gocardless_001'
5728
6169
  }
5729
6170
  },
5730
- required: [
5731
- 'sourceAccount',
5732
- 'defaultCurrency',
5733
- 'defaultExpenseAccount',
5734
- 'defaultIncomeAccount'
5735
- ]
6171
+ required: ['sourceAccount', 'defaultCurrency']
5736
6172
  } as const;
5737
6173
 
5738
6174
  export const $ProviderSyncDto = {
@@ -5942,15 +6378,106 @@ export const $UncoveredFormatMissDto = {
5942
6378
  properties: {}
5943
6379
  } as const;
5944
6380
 
6381
+ export const $ClientParsedDataDto = {
6382
+ type: 'object',
6383
+ properties: {
6384
+ amount: {
6385
+ type: 'number',
6386
+ description: 'Transaction amount',
6387
+ example: 35
6388
+ },
6389
+ currency: {
6390
+ type: 'string',
6391
+ description: 'Currency code',
6392
+ example: 'CNY'
6393
+ },
6394
+ date: {
6395
+ type: 'string',
6396
+ description: 'Transaction date (ISO 8601)',
6397
+ example: '2026-08-15'
6398
+ },
6399
+ payee: {
6400
+ type: 'string',
6401
+ description: 'Payee/merchant name',
6402
+ example: 'Starbucks'
6403
+ },
6404
+ narration: {
6405
+ type: 'string',
6406
+ description: 'Transaction narration'
6407
+ },
6408
+ category: {
6409
+ type: 'string',
6410
+ description:
6411
+ 'Category in canonical form (catalog slug or Stage-2 rule keyword token, ADR-0116) — echo back verbatim from parsedData.category. Foreign forms (locale display names, account paths) are rejected with nlp.category.invalid.',
6412
+ example: 'food'
6413
+ },
6414
+ incomeType: {
6415
+ type: 'string',
6416
+ description: 'Income type',
6417
+ example: 'Salary'
6418
+ },
6419
+ incomeSource: {
6420
+ type: 'string',
6421
+ description: 'Income source',
6422
+ example: 'Anthropic Inc.'
6423
+ },
6424
+ symbol: {
6425
+ type: 'string',
6426
+ description: 'Security symbol code (e.g., 600519, AAPL)',
6427
+ example: 'AAPL'
6428
+ },
6429
+ quantity: {
6430
+ type: 'number',
6431
+ description: 'Quantity of shares/units',
6432
+ example: 100
6433
+ },
6434
+ price: {
6435
+ type: 'number',
6436
+ description: 'Unit price per share/unit',
6437
+ example: 1900
6438
+ },
6439
+ investmentAction: {
6440
+ type: 'string',
6441
+ description: 'Investment action',
6442
+ enum: ['buy', 'sell'],
6443
+ example: 'buy'
6444
+ },
6445
+ paymentSource: {
6446
+ type: 'string',
6447
+ description: 'Payment source: asset (default) or liability (credit card)',
6448
+ enum: ['asset', 'liability'],
6449
+ example: 'asset'
6450
+ },
6451
+ liabilityHint: {
6452
+ type: 'string',
6453
+ description: 'Liability account hint (CreditCard/Huabei/Baitiao)',
6454
+ example: 'CreditCard'
6455
+ },
6456
+ warning: {
6457
+ type: 'string',
6458
+ description:
6459
+ 'Display-only warning from the prior response; accepted but ignored.',
6460
+ example: 'Cross-currency settlement applies.'
6461
+ }
6462
+ }
6463
+ } as const;
6464
+
5945
6465
  export const $ProcessNlpDto = {
5946
6466
  type: 'object',
5947
6467
  properties: {
5948
6468
  message: {
5949
6469
  type: 'string',
5950
- description: 'Natural language text describing a transaction (Chinese)',
5951
- example: 'yesterday Starbucks spent 35 yuan',
6470
+ description:
6471
+ 'Natural language text describing a transaction. Optional when `confirm` is true (structured confirm); otherwise required.',
6472
+ example: 'Starbucks 35',
5952
6473
  maxLength: 500
5953
6474
  },
6475
+ confirm: {
6476
+ type: 'boolean',
6477
+ description:
6478
+ 'Structured confirm signal — bypasses NL confirm-word matching when true. Send parsedData field edits alongside. The NL word-list path is the fallback.',
6479
+ example: true
6480
+ },
5954
6481
  sessionId: {
5955
6482
  type: 'string',
5956
6483
  description:
@@ -5958,17 +6485,51 @@ export const $ProcessNlpDto = {
5958
6485
  example: 'session_abc123'
5959
6486
  },
5960
6487
  parsedData: {
5961
- type: 'object',
5962
6488
  description:
5963
6489
  'Parsed data from previous NLP response for session recovery. Send back the parsedData received in confirm_payee/confirm responses.',
5964
6490
  example: {
5965
6491
  amount: 35,
5966
6492
  currency: 'CNY',
5967
6493
  payee: 'Starbucks'
5968
- }
6494
+ },
6495
+ allOf: [
6496
+ {
6497
+ $ref: '#/components/schemas/ClientParsedDataDto'
6498
+ }
6499
+ ]
6500
+ },
6501
+ selectedRuleId: {
6502
+ type: 'string',
6503
+ description:
6504
+ 'confirm_rule echo-back: rule id selected from the prior confirm_rule response (matchedRule.id or alternatives[i].ruleId). Applied directly when the session is confirming_rule — no NL re-parse.',
6505
+ example: 'rule_abc123'
6506
+ },
6507
+ selectedAccount: {
6508
+ type: 'string',
6509
+ description:
6510
+ 'confirm_account echo-back: account path selected from the prior confirm_account response (suggestedAccount, similarAccounts[i].path, or a typed path). Applied directly when the session is confirming_account — no NL re-parse.',
6511
+ example: 'Expenses:Food:Coffee'
6512
+ },
6513
+ viewpointAccount: {
6514
+ type: 'string',
6515
+ description:
6516
+ 'Viewpoint account hint: the beancount path the user drilled into (e.g. from an account drill-down). Tie-break only — never overrides accounts resolved from the text. Must be an owned, OPEN Assets:/Liabilities: account; unresolvable hints are silently ignored.',
6517
+ example: 'Assets:CN:Bank:ICBC'
6518
+ },
6519
+ viewpointCategory: {
6520
+ type: 'string',
6521
+ description:
6522
+ "Viewpoint category hint: the ADR-0075 Group segment the user drilled into (e.g. 'Food'). Resolved to a concrete OPEN account in that group; tie-break only — never overrides a category resolved from the text.",
6523
+ example: 'Food'
6524
+ },
6525
+ viewpointFlow: {
6526
+ type: 'string',
6527
+ description:
6528
+ "Companion flow root for viewpointCategory ('income' | 'expense'), mirroring the ADR-0126 list-endpoint invariant. Derived from the session's routed intent (multi-turn) when absent; a first-turn flow-less category hint is dropped — send the flow explicitly.",
6529
+ enum: ['income', 'expense'],
6530
+ example: 'expense'
5969
6531
  }
5970
- },
5971
- required: ['message']
6532
+ }
5972
6533
  } as const;
5973
6534
 
5974
6535
  export const $NlpTransactionInfoDto = {
@@ -6034,7 +6595,9 @@ export const $NlpParsedDataDto = {
6034
6595
  },
6035
6596
  category: {
6036
6597
  type: 'string',
6037
- description: 'Category'
6598
+ description:
6599
+ 'Category in canonical form: a CATEGORY_CATALOG slug (GET /{region}/bean/categories) or a Stage-2 rule categoryKeywords token (ADR-0116 D2). Never a locale display name or a beancount account path. Echo back verbatim on confirm.',
6600
+ example: 'food'
6038
6601
  },
6039
6602
  incomeType: {
6040
6603
  type: 'string',
@@ -6280,6 +6843,24 @@ export const $NlpRuleConfirmationDataDto = {
6280
6843
  ]
6281
6844
  } as const;
6282
6845
 
6846
+ export const $NlpAccountCandidateDto = {
6847
+ type: 'object',
6848
+ properties: {
6849
+ path: {
6850
+ type: 'string',
6851
+ description: 'Canonical beancount account path (echo back on selection)',
6852
+ example: 'Expenses:Food:Dining'
6853
+ },
6854
+ name: {
6855
+ type: 'string',
6856
+ description:
6857
+ 'Localized display name (ADR-0114 read-time projection, user locale)',
6858
+ example: '餐饮'
6859
+ }
6860
+ },
6861
+ required: ['path', 'name']
6862
+ } as const;
6863
+
6283
6864
  export const $NlpAccountConfirmationDataDto = {
6284
6865
  type: 'object',
6285
6866
  properties: {
@@ -6290,14 +6871,16 @@ export const $NlpAccountConfirmationDataDto = {
6290
6871
  },
6291
6872
  suggestedAccount: {
6292
6873
  type: 'string',
6293
- description: 'Suggested replacement account',
6874
+ description:
6875
+ 'Suggested replacement account (omitted when no clear candidate)',
6294
6876
  example: 'Expenses:Food:Drinks'
6295
6877
  },
6296
6878
  similarAccounts: {
6297
- description: 'Similar accounts for user selection',
6879
+ description:
6880
+ 'Similar accounts for user selection (path + localized name, #680)',
6298
6881
  type: 'array',
6299
6882
  items: {
6300
- type: 'string'
6883
+ $ref: '#/components/schemas/NlpAccountCandidateDto'
6301
6884
  }
6302
6885
  },
6303
6886
  errorMessage: {
@@ -6312,7 +6895,6 @@ export const $NlpAccountConfirmationDataDto = {
6312
6895
  },
6313
6896
  required: [
6314
6897
  'invalidAccount',
6315
- 'suggestedAccount',
6316
6898
  'similarAccounts',
6317
6899
  'errorMessage',
6318
6900
  'transactionContext'
@@ -6485,7 +7067,8 @@ export const $NlpSuggestedAccountDto = {
6485
7067
  },
6486
7068
  confidence: {
6487
7069
  type: 'number',
6488
- description: 'Confidence score for this suggestion (0-1)',
7070
+ description:
7071
+ 'Confidence score for this suggestion (0-1). Present = predicted (confirm/confirm_rule/confirm_account); omitted = actual persisted account (created). (#586)',
6489
7072
  example: 0.9
6490
7073
  }
6491
7074
  },
@@ -6521,23 +7104,31 @@ export const $NlpDefaultAccountsDto = {
6521
7104
  properties: {
6522
7105
  asset: {
6523
7106
  type: 'string',
6524
- description: 'Default asset account',
6525
- example: 'Assets:Checking'
7107
+ description:
7108
+ 'Default OPEN asset account (MRU when multiple), or null when none/ambiguous',
7109
+ example: 'Assets:Checking',
7110
+ nullable: true
6526
7111
  },
6527
7112
  expense: {
6528
7113
  type: 'string',
6529
- description: 'Default expense account',
6530
- example: 'Expenses:Uncategorized'
7114
+ description:
7115
+ 'Default OPEN expense account (MRU when multiple), or null when none/ambiguous',
7116
+ example: 'Expenses:Food:Coffee',
7117
+ nullable: true
6531
7118
  },
6532
7119
  income: {
6533
7120
  type: 'string',
6534
- description: 'Default income account',
6535
- example: 'Income:Uncategorized'
7121
+ description:
7122
+ 'Default OPEN income account (MRU when multiple), or null when none/ambiguous',
7123
+ example: 'Income:Salary',
7124
+ nullable: true
6536
7125
  },
6537
7126
  liability: {
6538
7127
  type: 'string',
6539
- description: 'Default liability account',
6540
- example: 'Liabilities:CreditCard'
7128
+ description:
7129
+ 'Default OPEN liability account (MRU when multiple), or null when none/ambiguous',
7130
+ example: 'Liabilities:CreditCard',
7131
+ nullable: true
6541
7132
  }
6542
7133
  },
6543
7134
  required: ['asset', 'expense', 'income', 'liability']
@@ -6577,7 +7168,7 @@ export const $NlpResponseDto = {
6577
7168
  type: 'string',
6578
7169
  description:
6579
7170
  'Asset sub-type (only present when intent is "asset"). Determines which asset-related form to render.',
6580
- enum: ['transfer', 'banking', 'investment'],
7171
+ enum: ['transfer', 'banking', 'investment', 'lend', 'lend_collect'],
6581
7172
  example: 'investment'
6582
7173
  },
6583
7174
  liabilitySubType: {
@@ -6725,7 +7316,7 @@ export const $NlpResponseDto = {
6725
7316
  },
6726
7317
  suggestedAccounts: {
6727
7318
  description:
6728
- 'Suggested accounts for this transaction. Contains recommended source and destination accounts based on the detected intent and rules.',
7319
+ 'Suggested accounts for this transaction (#586). confirm/confirm_rule/confirm_account: predicted (source/destination carry confidence); created: actual persisted accounts (confidence omitted). confirm_account destination is the suggested replacement, never the invalid account.',
6729
7320
  allOf: [
6730
7321
  {
6731
7322
  $ref: '#/components/schemas/NlpSuggestedAccountsDto'
@@ -6734,7 +7325,7 @@ export const $NlpResponseDto = {
6734
7325
  },
6735
7326
  defaultAccounts: {
6736
7327
  description:
6737
- 'Default accounts for the user/region. These are fallback accounts used when no specific suggestion is available.',
7328
+ 'Default fallback accounts for the user/region (#586). v1 returns universal constants; per-user personalization is planned.',
6738
7329
  allOf: [
6739
7330
  {
6740
7331
  $ref: '#/components/schemas/NlpDefaultAccountsDto'
@@ -6780,13 +7371,26 @@ export const $PlatformListItemDto = {
6780
7371
  suggestedSegment: {
6781
7372
  type: 'string',
6782
7373
  description:
6783
- 'Suggested path segment — canonical with first char uppercased (ACC_COMP_NAME_RE)'
7374
+ 'Suggested path segment — canonical PascalCased per hyphen-part, hyphens preserved (e.g. "Apple-Pay")'
6784
7375
  },
6785
7376
  logoUrl: {
6786
7377
  type: 'string',
6787
7378
  description: 'Logo URL',
6788
7379
  nullable: true
6789
7380
  },
7381
+ countryCode: {
7382
+ type: 'string',
7383
+ description: 'ISO 3166-1 alpha-2 (UPPERCASE); null = global platform',
7384
+ example: 'CN',
7385
+ nullable: true
7386
+ },
7387
+ category: {
7388
+ type: 'string',
7389
+ description:
7390
+ 'Region-aware category (institution vocab, e.g. DigitalWallet/Bank). null = no region-aware suggestion; fall back to type.',
7391
+ nullable: true,
7392
+ example: 'DigitalWallet'
7393
+ },
6790
7394
  isBound: {
6791
7395
  type: 'boolean',
6792
7396
  description: 'Whether user has accounts using this platform'
@@ -6800,6 +7404,8 @@ export const $PlatformListItemDto = {
6800
7404
  'canonical',
6801
7405
  'suggestedSegment',
6802
7406
  'logoUrl',
7407
+ 'countryCode',
7408
+ 'category',
6803
7409
  'isBound'
6804
7410
  ]
6805
7411
  } as const;
@@ -6835,13 +7441,26 @@ export const $PlatformMatchResultDto = {
6835
7441
  suggestedSegment: {
6836
7442
  type: 'string',
6837
7443
  description:
6838
- 'Suggested path segment — canonical, already in ACCOUNT_RE format'
7444
+ 'Suggested path segment — canonical PascalCased per hyphen-part, hyphens preserved (e.g. "Apple-Pay")'
6839
7445
  },
6840
7446
  logoUrl: {
6841
7447
  type: 'string',
6842
7448
  description: 'Logo URL',
6843
7449
  nullable: true
6844
7450
  },
7451
+ countryCode: {
7452
+ type: 'string',
7453
+ description: 'ISO 3166-1 alpha-2 (UPPERCASE); null = global platform',
7454
+ example: 'CN',
7455
+ nullable: true
7456
+ },
7457
+ category: {
7458
+ type: 'string',
7459
+ description:
7460
+ 'Region-aware category (institution vocab, e.g. DigitalWallet/Bank). null = no region-aware suggestion; fall back to type.',
7461
+ nullable: true,
7462
+ example: 'DigitalWallet'
7463
+ },
6845
7464
  matchType: {
6846
7465
  type: 'string',
6847
7466
  description: "How this row matched: 'exact' > 'prefix' > 'substring'",
@@ -6855,6 +7474,8 @@ export const $PlatformMatchResultDto = {
6855
7474
  'type',
6856
7475
  'suggestedSegment',
6857
7476
  'logoUrl',
7477
+ 'countryCode',
7478
+ 'category',
6858
7479
  'matchType'
6859
7480
  ]
6860
7481
  } as const;
@@ -6884,7 +7505,80 @@ export const $PlatformMatchResponseDto = {
6884
7505
  description: 'true when total > platforms.length (more matches exist)'
6885
7506
  }
6886
7507
  },
6887
- required: ['platforms', 'matchType', 'total', 'hasMore']
7508
+ required: ['platforms', 'matchType', 'total', 'hasMore']
7509
+ } as const;
7510
+
7511
+ export const $PlatformStandardsPlatformDto = {
7512
+ type: 'object',
7513
+ properties: {
7514
+ id: {
7515
+ type: 'string',
7516
+ description: 'Global platform ID'
7517
+ },
7518
+ name: {
7519
+ type: 'string',
7520
+ description: 'Platform name (e.g., "ICBC")'
7521
+ },
7522
+ canonical: {
7523
+ type: 'string',
7524
+ description: 'Canonical identifier in ACCOUNT_RE format (e.g., "icbc")'
7525
+ },
7526
+ suggestedSegment: {
7527
+ type: 'string',
7528
+ description:
7529
+ 'Suggested path segment — canonical PascalCased per hyphen-part, hyphens preserved (e.g. "Apple-Pay")'
7530
+ },
7531
+ type: {
7532
+ type: 'string',
7533
+ description: 'Platform type',
7534
+ enum: [
7535
+ 'BANK',
7536
+ 'BROKERAGE',
7537
+ 'CRYPTO_EXCHANGE',
7538
+ 'PAYMENT',
7539
+ 'INVESTMENT',
7540
+ 'INSURANCE',
7541
+ 'OTHER'
7542
+ ]
7543
+ },
7544
+ category: {
7545
+ type: 'string',
7546
+ description:
7547
+ 'Region-aware category (institution vocab, e.g. DigitalWallet/Bank) resolved against the final region. null = no region-aware suggestion; fall back to type.',
7548
+ nullable: true,
7549
+ example: 'Bank'
7550
+ }
7551
+ },
7552
+ required: ['id', 'name', 'canonical', 'suggestedSegment', 'type', 'category']
7553
+ } as const;
7554
+
7555
+ export const $PlatformStandardsResponseDto = {
7556
+ type: 'object',
7557
+ properties: {
7558
+ platform: {
7559
+ description: 'The selected platform (institution lock source)',
7560
+ allOf: [
7561
+ {
7562
+ $ref: '#/components/schemas/PlatformStandardsPlatformDto'
7563
+ }
7564
+ ]
7565
+ },
7566
+ region: {
7567
+ type: 'string',
7568
+ description:
7569
+ "Resolved template region (ISO 3166-1 alpha-2, UPPERCASE): the platform's own countryCode when set, else the region query param. For regions without a regional template file the template list falls back to the universal-only catalog while region still echoes the code.",
7570
+ example: 'CN'
7571
+ },
7572
+ templates: {
7573
+ description:
7574
+ 'Candidate account-standard templates of the resolved region (groupable by productCategory client-side)',
7575
+ type: 'array',
7576
+ items: {
7577
+ $ref: '#/components/schemas/AccountStandardResponseDto'
7578
+ }
7579
+ }
7580
+ },
7581
+ required: ['platform', 'region', 'templates']
6888
7582
  } as const;
6889
7583
 
6890
7584
  export const $CreatePlatformDto = {
@@ -6988,7 +7682,8 @@ export const $UpdatePlatformDto = {
6988
7682
  },
6989
7683
  isActive: {
6990
7684
  type: 'boolean',
6991
- description: 'Whether the platform is active'
7685
+ description: 'Whether the platform is active',
7686
+ default: true
6992
7687
  }
6993
7688
  }
6994
7689
  } as const;
@@ -7151,7 +7846,8 @@ export const $AccountItemDto = {
7151
7846
  },
7152
7847
  displayName: {
7153
7848
  type: 'string',
7154
- description: 'Display name (last part of account path)',
7849
+ description:
7850
+ 'Display name: user-set name if provided (#762), else the ADR-0114 chain — request-locale catalog name, en pivot, then the last part of the account path (#771)',
7155
7851
  example: 'Savings'
7156
7852
  },
7157
7853
  balance: {
@@ -7187,7 +7883,8 @@ export const $PlatformGroupDto = {
7187
7883
  example: 'CMB Bank'
7188
7884
  },
7189
7885
  accounts: {
7190
- description: 'Accounts within this platform',
7886
+ description:
7887
+ 'Accounts within this platform (Assets and Liabilities rows, #696)',
7191
7888
  type: 'array',
7192
7889
  items: {
7193
7890
  $ref: '#/components/schemas/AccountItemDto'
@@ -7195,7 +7892,8 @@ export const $PlatformGroupDto = {
7195
7892
  },
7196
7893
  totalBalance: {
7197
7894
  type: 'string',
7198
- description: 'FX-converted total balance in base currency',
7895
+ description:
7896
+ 'FX-converted total balance in base currency (nets Assets + Liabilities rows; can be negative)',
7199
7897
  example: '100000.00'
7200
7898
  },
7201
7899
  balanceByCurrency: {
@@ -7214,7 +7912,7 @@ export const $PlatformGroupDto = {
7214
7912
  sharePct: {
7215
7913
  type: 'number',
7216
7914
  description:
7217
- 'Share of the grand converted total (0-100); 0 when grand total is 0',
7915
+ 'Share of the converted asset-side grand total (0-100); liability balances are excluded from the basis; 0 when grand total is 0 (#696)',
7218
7916
  example: 42.5
7219
7917
  }
7220
7918
  },
@@ -7262,7 +7960,8 @@ export const $AccountsSummaryDto = {
7262
7960
  properties: {
7263
7961
  totalAccounts: {
7264
7962
  type: 'number',
7265
- description: 'Total number of accounts'
7963
+ description:
7964
+ 'Total number of accounts (balance sheet: Assets + Liabilities, #696)'
7266
7965
  },
7267
7966
  totalPlatforms: {
7268
7967
  type: 'number',
@@ -7320,7 +8019,8 @@ export const $AccountItemWithAssetClassDto = {
7320
8019
  },
7321
8020
  displayName: {
7322
8021
  type: 'string',
7323
- description: 'Display name (last part of account path)',
8022
+ description:
8023
+ 'Display name: user-set name if provided (#762), else the ADR-0114 chain — request-locale catalog name, en pivot, then the last part of the account path (#771)',
7324
8024
  example: 'Savings'
7325
8025
  },
7326
8026
  balance: {
@@ -7806,7 +8506,7 @@ export const $MonetaryDto = {
7806
8506
  example: 'USD'
7807
8507
  },
7808
8508
  baseCcyEquivalent: {
7809
- type: 'object',
8509
+ type: 'string',
7810
8510
  description: 'Converted to user base currency (Decimal string)',
7811
8511
  example: '21600',
7812
8512
  nullable: true
@@ -7882,13 +8582,13 @@ export const $HoldingPnlRowDto = {
7882
8582
  example: 'Assets:US:Broker:AAPL'
7883
8583
  },
7884
8584
  accountCcy: {
7885
- type: 'object',
8585
+ type: 'string',
7886
8586
  description: 'Account settlement currency (ISO 4217), from cost currency',
7887
8587
  nullable: true,
7888
8588
  example: 'USD'
7889
8589
  },
7890
8590
  brokerType: {
7891
- type: 'object',
8591
+ type: 'string',
7892
8592
  description: 'Broker type derived from Platform.type',
7893
8593
  nullable: true,
7894
8594
  example: 'broker'
@@ -7909,7 +8609,7 @@ export const $HoldingPnlRowDto = {
7909
8609
  example: 'EQUITY'
7910
8610
  },
7911
8611
  assetSubClass: {
7912
- type: 'object',
8612
+ type: 'string',
7913
8613
  nullable: true,
7914
8614
  example: 'STOCK'
7915
8615
  },
@@ -7956,14 +8656,14 @@ export const $HoldingPnlRowDto = {
7956
8656
  ]
7957
8657
  },
7958
8658
  unrealizedPnlBase: {
7959
- type: 'object',
8659
+ type: 'string',
7960
8660
  description:
7961
8661
  'Unrealized P&L in base currency (Decimal string); null when any FX/price missing',
7962
8662
  nullable: true,
7963
8663
  example: '6000'
7964
8664
  },
7965
8665
  unrealizedPnlPct: {
7966
- type: 'object',
8666
+ type: 'string',
7967
8667
  description: 'Unrealized P&L % (Decimal string)',
7968
8668
  nullable: true,
7969
8669
  example: '25'
@@ -7987,7 +8687,7 @@ export const $HoldingPnlRowDto = {
7987
8687
  ]
7988
8688
  },
7989
8689
  pctOfInvestedAssets: {
7990
- type: 'object',
8690
+ type: 'string',
7991
8691
  description:
7992
8692
  'Share of invested assets % (Decimal string); only for invested chartTokens',
7993
8693
  nullable: true,
@@ -8032,15 +8732,15 @@ export const $HoldingPnlWarningDto = {
8032
8732
  ]
8033
8733
  },
8034
8734
  symbol: {
8035
- type: 'object',
8735
+ type: 'string',
8036
8736
  nullable: true
8037
8737
  },
8038
8738
  accountId: {
8039
- type: 'object',
8739
+ type: 'string',
8040
8740
  nullable: true
8041
8741
  },
8042
8742
  currency: {
8043
- type: 'object',
8743
+ type: 'string',
8044
8744
  nullable: true
8045
8745
  }
8046
8746
  },
@@ -8103,3 +8803,356 @@ export const $AnonymousLoginResponseDto = {
8103
8803
  },
8104
8804
  required: ['authToken']
8105
8805
  } as const;
8806
+
8807
+ export const $ParserContributionMetaDto = {
8808
+ type: 'object',
8809
+ properties: {
8810
+ institution: {
8811
+ type: 'string',
8812
+ description: 'Institution slug (lowercase kebab-case)',
8813
+ pattern: '^[a-z0-9]+(-[a-z0-9]+)*$',
8814
+ example: 'icbc'
8815
+ },
8816
+ region: {
8817
+ type: 'string',
8818
+ enum: [
8819
+ 'cn',
8820
+ 'us',
8821
+ 'de',
8822
+ 'fr',
8823
+ 'gb',
8824
+ 'hk',
8825
+ 'jp',
8826
+ 'sg',
8827
+ 'au',
8828
+ 'ca',
8829
+ 'other'
8830
+ ]
8831
+ },
8832
+ accountType: {
8833
+ type: 'string',
8834
+ enum: ['checking', 'savings', 'credit', 'debit', 'investment']
8835
+ },
8836
+ format: {
8837
+ type: 'string',
8838
+ enum: ['csv', 'xlsx', 'pdf', 'ofx', 'qif']
8839
+ },
8840
+ institutionDisplayName: {
8841
+ type: 'string',
8842
+ example: '中国工商银行'
8843
+ },
8844
+ encoding: {
8845
+ type: 'string',
8846
+ example: 'utf-8'
8847
+ },
8848
+ delimiter: {
8849
+ type: 'string',
8850
+ description: 'CSV delimiter character: ",", ";", "\\t" or "|"'
8851
+ },
8852
+ headerRows: {
8853
+ type: 'number',
8854
+ default: 1,
8855
+ description: 'Header row count; the client omits the field when it is 1'
8856
+ },
8857
+ notes: {
8858
+ type: 'string',
8859
+ maxLength: 2000
8860
+ }
8861
+ },
8862
+ required: ['institution', 'region', 'accountType', 'format']
8863
+ } as const;
8864
+
8865
+ export const $ParserContributionSamplesDto = {
8866
+ type: 'object',
8867
+ properties: {
8868
+ rows: {
8869
+ description:
8870
+ 'Client-sanitized sample rows (key = column name, value = cell)',
8871
+ type: 'array',
8872
+ items: {
8873
+ type: 'object'
8874
+ }
8875
+ },
8876
+ rawHeaders: {
8877
+ type: 'array',
8878
+ items: {
8879
+ type: 'string'
8880
+ }
8881
+ }
8882
+ },
8883
+ required: ['rows']
8884
+ } as const;
8885
+
8886
+ export const $FieldHintDto = {
8887
+ type: 'object',
8888
+ properties: {
8889
+ columnName: {
8890
+ type: 'string',
8891
+ example: '交易日期'
8892
+ },
8893
+ format: {
8894
+ type: 'string',
8895
+ description: 'Date format, e.g. yyyy-MM-dd HH:mm',
8896
+ example: 'yyyy-MM-dd'
8897
+ },
8898
+ signConvention: {
8899
+ type: 'string',
8900
+ enum: ['negative-expense', 'positive-expense', 'separate-columns']
8901
+ },
8902
+ creditColumn: {
8903
+ type: 'string'
8904
+ },
8905
+ debitColumn: {
8906
+ type: 'string'
8907
+ }
8908
+ },
8909
+ required: ['columnName']
8910
+ } as const;
8911
+
8912
+ export const $ParserContributionFieldHintsDto = {
8913
+ type: 'object',
8914
+ properties: {
8915
+ date: {
8916
+ $ref: '#/components/schemas/FieldHintDto'
8917
+ },
8918
+ amount: {
8919
+ $ref: '#/components/schemas/FieldHintDto'
8920
+ },
8921
+ description: {
8922
+ $ref: '#/components/schemas/FieldHintDto'
8923
+ },
8924
+ balance: {
8925
+ $ref: '#/components/schemas/FieldHintDto'
8926
+ },
8927
+ payee: {
8928
+ $ref: '#/components/schemas/FieldHintDto'
8929
+ },
8930
+ reference: {
8931
+ $ref: '#/components/schemas/FieldHintDto'
8932
+ },
8933
+ category: {
8934
+ $ref: '#/components/schemas/FieldHintDto'
8935
+ }
8936
+ },
8937
+ required: ['date', 'amount']
8938
+ } as const;
8939
+
8940
+ export const $ExpectedTransactionDto = {
8941
+ type: 'object',
8942
+ properties: {
8943
+ date: {
8944
+ type: 'string',
8945
+ example: '2026-08-01'
8946
+ },
8947
+ amount: {
8948
+ type: 'number',
8949
+ example: -45.5
8950
+ },
8951
+ description: {
8952
+ type: 'string',
8953
+ example: '星巴克-***店'
8954
+ },
8955
+ payee: {
8956
+ type: 'string'
8957
+ },
8958
+ category: {
8959
+ type: 'string'
8960
+ }
8961
+ },
8962
+ required: ['date', 'amount', 'description']
8963
+ } as const;
8964
+
8965
+ export const $ParserContributionExamplesDto = {
8966
+ type: 'object',
8967
+ properties: {
8968
+ expectedTransactions: {
8969
+ type: 'array',
8970
+ items: {
8971
+ $ref: '#/components/schemas/ExpectedTransactionDto'
8972
+ }
8973
+ }
8974
+ },
8975
+ required: ['expectedTransactions']
8976
+ } as const;
8977
+
8978
+ export const $ParserContributionRequestDto = {
8979
+ type: 'object',
8980
+ properties: {
8981
+ meta: {
8982
+ $ref: '#/components/schemas/ParserContributionMetaDto'
8983
+ },
8984
+ samples: {
8985
+ $ref: '#/components/schemas/ParserContributionSamplesDto'
8986
+ },
8987
+ fieldHints: {
8988
+ $ref: '#/components/schemas/ParserContributionFieldHintsDto'
8989
+ },
8990
+ examples: {
8991
+ description: 'Omitted entirely by the client when empty',
8992
+ allOf: [
8993
+ {
8994
+ $ref: '#/components/schemas/ParserContributionExamplesDto'
8995
+ }
8996
+ ]
8997
+ }
8998
+ },
8999
+ required: ['meta', 'samples', 'fieldHints']
9000
+ } as const;
9001
+
9002
+ export const $ParserContributionRelayResponseDto = {
9003
+ type: 'object',
9004
+ properties: {
9005
+ issueUrl: {
9006
+ type: 'string',
9007
+ example: 'https://github.com/fire-zu/firela-vlt/issues/42'
9008
+ },
9009
+ issueNumber: {
9010
+ type: 'number',
9011
+ example: 42
9012
+ }
9013
+ },
9014
+ required: ['issueUrl', 'issueNumber']
9015
+ } as const;
9016
+
9017
+ export const $SymbolSearchResultDto = {
9018
+ type: 'object',
9019
+ properties: {
9020
+ symbol: {
9021
+ type: 'string',
9022
+ example: 'AAPL'
9023
+ },
9024
+ name: {
9025
+ type: 'string',
9026
+ example: 'Apple Inc.',
9027
+ nullable: true
9028
+ },
9029
+ exchange: {
9030
+ type: 'string',
9031
+ example: 'US',
9032
+ nullable: true
9033
+ },
9034
+ assetType: {
9035
+ type: 'string',
9036
+ description: 'OpenBB asset_type (e.g. stock, etf)',
9037
+ example: 'stock',
9038
+ nullable: true
9039
+ },
9040
+ assetClass: {
9041
+ type: 'string',
9042
+ description: 'IGN asset class (region.types.ts ASSET_CLASSES)',
9043
+ example: 'EQUITY',
9044
+ nullable: true
9045
+ },
9046
+ assetSubClass: {
9047
+ type: 'string',
9048
+ description: 'IGN asset sub-class (region.types.ts ASSET_SUB_CLASSES)',
9049
+ example: 'STOCK',
9050
+ nullable: true
9051
+ },
9052
+ currency: {
9053
+ type: 'string',
9054
+ description: 'Trading currency (extra_data or inferred from exchange)',
9055
+ example: 'USD',
9056
+ nullable: true
9057
+ }
9058
+ },
9059
+ required: ['symbol']
9060
+ } as const;
9061
+
9062
+ export const $SymbolQuoteDto = {
9063
+ type: 'object',
9064
+ properties: {
9065
+ symbol: {
9066
+ type: 'string',
9067
+ example: 'AAPL'
9068
+ },
9069
+ name: {
9070
+ type: 'string',
9071
+ example: 'Apple Inc.',
9072
+ nullable: true
9073
+ },
9074
+ exchange: {
9075
+ type: 'string',
9076
+ example: 'US',
9077
+ nullable: true
9078
+ },
9079
+ assetType: {
9080
+ type: 'string',
9081
+ description: 'OpenBB asset_type',
9082
+ example: 'stock',
9083
+ nullable: true
9084
+ },
9085
+ assetClass: {
9086
+ type: 'string',
9087
+ description: 'IGN asset class',
9088
+ example: 'EQUITY',
9089
+ nullable: true
9090
+ },
9091
+ assetSubClass: {
9092
+ type: 'string',
9093
+ description: 'IGN asset sub-class',
9094
+ example: 'STOCK',
9095
+ nullable: true
9096
+ },
9097
+ currency: {
9098
+ type: 'string',
9099
+ description: 'Trading currency (extra_data or inferred from exchange)',
9100
+ example: 'USD',
9101
+ nullable: true
9102
+ },
9103
+ price: {
9104
+ type: 'string',
9105
+ description: 'Latest price (Decimal string)',
9106
+ example: '189.84',
9107
+ nullable: true
9108
+ },
9109
+ priceDate: {
9110
+ type: 'string',
9111
+ description: 'Date the price was observed (ISO yyyy-MM-dd)',
9112
+ example: '2026-08-05',
9113
+ nullable: true
9114
+ },
9115
+ changePercent: {
9116
+ type: 'number',
9117
+ description:
9118
+ 'Change vs previous close, in percentage points (1.7 == 1.7%). openbb stores change_percent as a normalized decimal; this exposes percentage points for frontend convenience.',
9119
+ example: 1.7,
9120
+ nullable: true
9121
+ },
9122
+ prevClose: {
9123
+ type: 'string',
9124
+ description: 'Previous close (Decimal string)',
9125
+ nullable: true
9126
+ },
9127
+ open: {
9128
+ type: 'string',
9129
+ description: 'Day open (Decimal string)',
9130
+ nullable: true
9131
+ },
9132
+ high: {
9133
+ type: 'string',
9134
+ description: 'Day high (Decimal string)',
9135
+ nullable: true
9136
+ },
9137
+ low: {
9138
+ type: 'string',
9139
+ description: 'Day low (Decimal string)',
9140
+ nullable: true
9141
+ },
9142
+ volume: {
9143
+ type: 'string',
9144
+ description: 'Day volume (Decimal string)',
9145
+ nullable: true
9146
+ },
9147
+ yearHigh: {
9148
+ type: 'string',
9149
+ description: '52-week high (Decimal string)',
9150
+ nullable: true
9151
+ },
9152
+ yearLow: {
9153
+ type: 'string',
9154
+ description: '52-week low (Decimal string)',
9155
+ nullable: true
9156
+ }
9157
+ }
9158
+ } as const;