@finsys/core 3.2.0 → 3.4.0

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.
package/dist/index.cjs CHANGED
@@ -57,12 +57,16 @@ __export(index_exports, {
57
57
  applyAggregation: () => applyAggregation,
58
58
  applyDynamicTitles: () => applyDynamicTitles,
59
59
  assertAdapterCategory: () => assertAdapterCategory,
60
+ buildDocumentRows: () => buildDocumentRows,
60
61
  buildFileFieldTables: () => buildFileFieldTables,
61
62
  categoryFieldsOf: () => categoryFieldsOf,
62
63
  categoryForField: () => categoryForField,
63
64
  categorySchemaOf: () => categorySchemaOf,
64
65
  evaluateExpression: () => evaluateExpression,
65
66
  extractTimePeriods: () => extractTimePeriods,
67
+ formatDocumentSize: () => formatDocumentSize,
68
+ formatDocumentType: () => formatDocumentType,
69
+ formatDocumentUploaded: () => formatDocumentUploaded,
66
70
  generateRHFSchema: () => generateRHFSchema,
67
71
  generateSurveyJson: () => generateSurveyJson,
68
72
  getBaseCategories: () => getBaseCategories,
@@ -72,9 +76,12 @@ __export(index_exports, {
72
76
  getCategoryName: () => getCategoryName,
73
77
  getDisplayName: () => getDisplayName,
74
78
  getDisplayNames: () => getDisplayNames,
79
+ getDocDisplayNames: () => getDocDisplayNames,
80
+ getExtractableDocTypes: () => getExtractableDocTypes,
75
81
  getGroupDisplayNames: () => getGroupDisplayNames,
76
82
  getPastMonthLabel: () => getPastMonthLabel,
77
83
  getPastYearLabel: () => getPastYearLabel,
84
+ getReuploadableDocTypes: () => getReuploadableDocTypes,
78
85
  getStepDefaultValues: () => getStepDefaultValues,
79
86
  getStepSchema: () => getStepSchema,
80
87
  groupColumnsByTimePeriod: () => groupColumnsByTimePeriod,
@@ -85,6 +92,7 @@ __export(index_exports, {
85
92
  isFailureIhsStatus: () => isFailureIhsStatus,
86
93
  isTerminalIhsStatus: () => isTerminalIhsStatus,
87
94
  isValidIhsStatus: () => isValidIhsStatus,
95
+ parseFileField: () => parseFileField,
88
96
  processIhsDetails: () => processIhsDetails,
89
97
  resolveExtractionStatus: () => resolveExtractionStatus,
90
98
  resolvePageFields: () => resolvePageFields,
@@ -9461,7 +9469,7 @@ function formatValue(value, numeric) {
9461
9469
  }
9462
9470
  return String(value);
9463
9471
  }
9464
- function buildTableForGroup(groupName, fields, ihsData) {
9472
+ function buildTableForGroup(groupName, fields, ihsData, fieldProvenance) {
9465
9473
  const allColumns = [];
9466
9474
  for (const field of fields) {
9467
9475
  if (field.ihs_column_names) {
@@ -9480,11 +9488,20 @@ function buildTableForGroup(groupName, fields, ihsData) {
9480
9488
  const numeric = isNumericField(baseName);
9481
9489
  const data = {};
9482
9490
  const formattedData = {};
9491
+ const confidence = {};
9492
+ const provenance = {};
9483
9493
  for (const period of periods) {
9484
9494
  const colName = periodMap[period];
9485
9495
  const value = colName ? ihsData[colName] ?? null : null;
9486
9496
  data[period] = value;
9487
9497
  formattedData[period] = formatValue(value, numeric);
9498
+ const prov = colName ? fieldProvenance?.[colName] : void 0;
9499
+ if (prov) {
9500
+ provenance[period] = prov;
9501
+ if (prov.origin === "extracted" && typeof prov.confidence === "number" && !Number.isNaN(prov.confidence)) {
9502
+ confidence[period] = prov.confidence;
9503
+ }
9504
+ }
9488
9505
  }
9489
9506
  items.push({
9490
9507
  displayName: getDisplayName(baseName),
@@ -9492,7 +9509,9 @@ function buildTableForGroup(groupName, fields, ihsData) {
9492
9509
  data,
9493
9510
  formattedData,
9494
9511
  type: tableType,
9495
- isNumeric: numeric
9512
+ isNumeric: numeric,
9513
+ ...Object.keys(confidence).length ? { confidence } : {},
9514
+ ...Object.keys(provenance).length ? { provenance } : {}
9496
9515
  });
9497
9516
  }
9498
9517
  const hasData = items.some(
@@ -9504,13 +9523,16 @@ function buildTableForGroup(groupName, fields, ihsData) {
9504
9523
  for (const colName of allColumns) {
9505
9524
  const value = ihsData[colName] ?? null;
9506
9525
  const numeric = isNumericField(colName);
9526
+ const prov = fieldProvenance?.[colName];
9507
9527
  items.push({
9508
9528
  displayName: getDisplayName(colName),
9509
9529
  timePeriods: [],
9510
9530
  data: { value },
9511
9531
  formattedData: { value: formatValue(value, numeric) },
9512
9532
  type: tableType,
9513
- isNumeric: numeric
9533
+ isNumeric: numeric,
9534
+ ...prov && prov.origin === "extracted" && typeof prov.confidence === "number" && !Number.isNaN(prov.confidence) ? { confidence: { value: prov.confidence } } : {},
9535
+ ...prov ? { provenance: { value: prov } } : {}
9514
9536
  });
9515
9537
  }
9516
9538
  const hasData = items.some((item) => {
@@ -9520,19 +9542,160 @@ function buildTableForGroup(groupName, fields, ihsData) {
9520
9542
  return { name: groupName, displayName: groupDisplayName, type: tableType, items, hasData };
9521
9543
  }
9522
9544
  }
9523
- function buildFileFieldTables(ihsData) {
9545
+ function buildFileFieldTables(ihsData, fieldProvenance) {
9524
9546
  const specs = getBaseFieldSpecs();
9525
9547
  const fileFields = specs.filter((f) => f.type === "file" && f.ihs_column_names?.length);
9526
9548
  const grouped = groupFieldsByPattern(fileFields);
9527
9549
  const tables = {};
9528
9550
  for (const [groupName, fields] of Object.entries(grouped)) {
9529
- const table = buildTableForGroup(groupName, fields, ihsData);
9551
+ const table = buildTableForGroup(groupName, fields, ihsData, fieldProvenance);
9530
9552
  if (table && table.hasData) {
9531
9553
  tables[groupName] = table;
9532
9554
  }
9533
9555
  }
9534
9556
  return tables;
9535
9557
  }
9558
+ var DOC_DISPLAY_NAMES = {
9559
+ bankStatements: "Bank Statements",
9560
+ financialStatements: "Financial Statements",
9561
+ form9: "Form 9",
9562
+ epfStatements: "EPF Statements",
9563
+ payslips: "Payslips",
9564
+ ssm: "SSM Company Profile",
9565
+ ic: "Identity Card",
9566
+ ssm_registration_documents: "Form 9",
9567
+ ic_documents: "Identity Documents (Supplementary)",
9568
+ consentForm: "Consent Form",
9569
+ supplementaryDoc: "Supplementary Documents",
9570
+ coreIncomeDoc: "Core Income Document",
9571
+ incomeSupportingDoc: "Income Supporting Document",
9572
+ incomeEPF_iakaun: "EPF i-Akaun",
9573
+ photocopyRegistrationCard: "Registration Card",
9574
+ bankStatementOrSavingPassbook: "Bank Passbook",
9575
+ tnbBills: "TNB Bills"
9576
+ };
9577
+ var EXTRACTABLE_DOC_TYPES = /* @__PURE__ */ new Set([
9578
+ "bankStatements",
9579
+ "financialStatements",
9580
+ "form9",
9581
+ "epfStatements",
9582
+ "payslips",
9583
+ "ssm",
9584
+ "ic",
9585
+ "ssm_registration_documents",
9586
+ "ic_documents"
9587
+ ]);
9588
+ var REUPLOADABLE_DOC_TYPES = /* @__PURE__ */ new Set(["bankStatements", "financialStatements"]);
9589
+ function getDocDisplayNames() {
9590
+ return DOC_DISPLAY_NAMES;
9591
+ }
9592
+ function getExtractableDocTypes() {
9593
+ return EXTRACTABLE_DOC_TYPES;
9594
+ }
9595
+ function getReuploadableDocTypes() {
9596
+ return REUPLOADABLE_DOC_TYPES;
9597
+ }
9598
+ function isProbablyId(str) {
9599
+ if (!str) return false;
9600
+ const clean = str.split(".")[0];
9601
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(clean)) return true;
9602
+ if (/^[0-9a-f]{32,128}$/i.test(clean)) return true;
9603
+ return false;
9604
+ }
9605
+ function parseFileField(value) {
9606
+ if (!value) return [];
9607
+ if (Array.isArray(value)) return value;
9608
+ if (typeof value === "string") {
9609
+ try {
9610
+ const parsed = JSON.parse(value);
9611
+ return Array.isArray(parsed) ? parsed : [];
9612
+ } catch {
9613
+ if (value.startsWith("http")) {
9614
+ return [{ path: value, documentId: value.split("/").pop() || void 0 }];
9615
+ }
9616
+ return [];
9617
+ }
9618
+ }
9619
+ return [];
9620
+ }
9621
+ function toBytes(value) {
9622
+ if (typeof value === "number") return Number.isNaN(value) ? null : value;
9623
+ if (typeof value === "string") {
9624
+ const n = parseInt(value, 10);
9625
+ return Number.isNaN(n) ? null : n;
9626
+ }
9627
+ return null;
9628
+ }
9629
+ function buildDocumentRows(ihsData) {
9630
+ const metaMap = ihsData.documentMetadata ?? {};
9631
+ const rows = [];
9632
+ for (const docType of Object.keys(DOC_DISPLAY_NAMES)) {
9633
+ const files = parseFileField(ihsData[docType]);
9634
+ if (files.length === 0) continue;
9635
+ const label = DOC_DISPLAY_NAMES[docType];
9636
+ const extractable = EXTRACTABLE_DOC_TYPES.has(docType);
9637
+ const reUploadable = extractable && REUPLOADABLE_DOC_TYPES.has(docType);
9638
+ files.forEach((file, index) => {
9639
+ const path = file.path ?? null;
9640
+ const meta = path ? metaMap[path] : void 0;
9641
+ const documentId = file.documentId || (path ? path.split("/").pop() || null : null);
9642
+ const timePeriod = file.month ? `T${file.month}` : file.year ? `T${file.year}` : "ALL";
9643
+ const rawName = file.fileName ?? meta?.fileName ?? void 0;
9644
+ const fileName = rawName && !isProbablyId(rawName) ? rawName : null;
9645
+ const rawExt = (rawName ?? "").split(".").pop();
9646
+ const ext = rawExt && rawExt.length <= 5 && !isProbablyId(rawExt) ? rawExt.toUpperCase() : null;
9647
+ const mime = file.fileType ?? meta?.fileType;
9648
+ const fileType = ext || (mime?.split("/").pop()?.toUpperCase() ?? null);
9649
+ const fileSize = toBytes(file.fileSize) ?? meta?.fileSize ?? null;
9650
+ const uploadedAt = file.createdAt ?? meta?.createdAt ?? null;
9651
+ const periodLabel = file.month && file.year ? new Date(file.year, file.month - 1).toLocaleDateString("en-MY", {
9652
+ year: "numeric",
9653
+ month: "short"
9654
+ }) : file.year ? `Year ${file.year}` : null;
9655
+ const displayName = fileName || (periodLabel ? `${label} \u2014 ${periodLabel}` : files.length > 1 ? `${label} ${index + 1}` : label);
9656
+ rows.push({
9657
+ docType,
9658
+ label,
9659
+ index,
9660
+ displayName,
9661
+ documentId,
9662
+ path,
9663
+ timePeriod,
9664
+ periodLabel,
9665
+ fileName,
9666
+ fileType,
9667
+ fileSize,
9668
+ uploadedAt,
9669
+ capabilities: {
9670
+ download: !!documentId,
9671
+ viewJson: extractable,
9672
+ reExtract: extractable,
9673
+ reUpload: reUploadable
9674
+ }
9675
+ });
9676
+ });
9677
+ }
9678
+ return rows;
9679
+ }
9680
+ var EM_DASH = "\u2014";
9681
+ function formatDocumentType(row) {
9682
+ return row.fileType ?? EM_DASH;
9683
+ }
9684
+ function formatDocumentSize(bytes) {
9685
+ if (!bytes || Number.isNaN(bytes)) return EM_DASH;
9686
+ if (bytes < 1024) return `${bytes} B`;
9687
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
9688
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
9689
+ }
9690
+ function formatDocumentUploaded(row) {
9691
+ if (row.uploadedAt) {
9692
+ const d = new Date(row.uploadedAt);
9693
+ if (!Number.isNaN(d.getTime())) {
9694
+ return d.toLocaleDateString("en-MY", { year: "numeric", month: "short", day: "numeric" });
9695
+ }
9696
+ }
9697
+ return row.periodLabel ?? EM_DASH;
9698
+ }
9536
9699
  function isFileOrFinancialColumn(fieldName, fileColumnSet) {
9537
9700
  return fileColumnSet.has(fieldName) || fieldName.startsWith("financials") || fieldName.startsWith("bank_statement");
9538
9701
  }
@@ -10456,12 +10619,16 @@ function assertAdapterCategory(id) {
10456
10619
  applyAggregation,
10457
10620
  applyDynamicTitles,
10458
10621
  assertAdapterCategory,
10622
+ buildDocumentRows,
10459
10623
  buildFileFieldTables,
10460
10624
  categoryFieldsOf,
10461
10625
  categoryForField,
10462
10626
  categorySchemaOf,
10463
10627
  evaluateExpression,
10464
10628
  extractTimePeriods,
10629
+ formatDocumentSize,
10630
+ formatDocumentType,
10631
+ formatDocumentUploaded,
10465
10632
  generateRHFSchema,
10466
10633
  generateSurveyJson,
10467
10634
  getBaseCategories,
@@ -10471,9 +10638,12 @@ function assertAdapterCategory(id) {
10471
10638
  getCategoryName,
10472
10639
  getDisplayName,
10473
10640
  getDisplayNames,
10641
+ getDocDisplayNames,
10642
+ getExtractableDocTypes,
10474
10643
  getGroupDisplayNames,
10475
10644
  getPastMonthLabel,
10476
10645
  getPastYearLabel,
10646
+ getReuploadableDocTypes,
10477
10647
  getStepDefaultValues,
10478
10648
  getStepSchema,
10479
10649
  groupColumnsByTimePeriod,
@@ -10484,6 +10654,7 @@ function assertAdapterCategory(id) {
10484
10654
  isFailureIhsStatus,
10485
10655
  isTerminalIhsStatus,
10486
10656
  isValidIhsStatus,
10657
+ parseFileField,
10487
10658
  processIhsDetails,
10488
10659
  resolveExtractionStatus,
10489
10660
  resolvePageFields,