@finsys/core 2.5.0 → 2.7.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.js CHANGED
@@ -9,7 +9,7 @@ function getPastYearLabel(yearsAgo) {
9
9
  const date = /* @__PURE__ */ new Date();
10
10
  return (date.getFullYear() - yearsAgo).toString();
11
11
  }
12
- function evaluateExpression(expression, data) {
12
+ function evaluateExpression(expression, data2) {
13
13
  if (!expression) return true;
14
14
  try {
15
15
  let jsExpression = expression.replace(/\{([^}]+)\}/g, (_, key) => {
@@ -33,7 +33,7 @@ function evaluateExpression(expression, data) {
33
33
  (_, ref, val) => `!(${ref} || []).includes('${val}')`
34
34
  );
35
35
  const func = new Function(`return ${jsExpression};`);
36
- return !!func.call(data || {});
36
+ return !!func.call(data2 || {});
37
37
  } catch (e) {
38
38
  return false;
39
39
  }
@@ -179,16 +179,16 @@ function groupFieldsByCategory(fields, categories) {
179
179
  // src/rhf-generator.ts
180
180
  import { z } from "zod";
181
181
  function applyVisibilityValidation(baseObject, fields) {
182
- return baseObject.superRefine((data, ctx) => {
182
+ return baseObject.superRefine((data2, ctx) => {
183
183
  fields.forEach((field) => {
184
184
  const isRequired = field.isRequired !== false && field.required !== false && field.type !== "html";
185
185
  if (!isRequired) return;
186
186
  let isVisible = field.visible !== false;
187
187
  if (isVisible && field.visibleIf) {
188
- isVisible = evaluateExpression(field.visibleIf, data);
188
+ isVisible = evaluateExpression(field.visibleIf, data2);
189
189
  }
190
190
  if (isVisible) {
191
- const value = data[field.name];
191
+ const value = data2[field.name];
192
192
  const isEmpty = value === void 0 || value === null || value === "" || Array.isArray(value) && value.length === 0;
193
193
  if (isEmpty) {
194
194
  ctx.addIssue({
@@ -649,22 +649,22 @@ var ajv = new Ajv({ allErrors: true, strict: false });
649
649
  var addFormatsFn = addFormats.default || addFormats;
650
650
  addFormatsFn(ajv);
651
651
  ajv.addSchema(unified_form_schema_default, "unified-form.schema.json");
652
- function validateFormConfig(data) {
652
+ function validateFormConfig(data2) {
653
653
  const validate = ajv.getSchema("unified-form.schema.json");
654
654
  if (!validate) {
655
655
  return { valid: false, message: "Could not load unified form schema" };
656
656
  }
657
- const valid = validate(data);
657
+ const valid = validate(data2);
658
658
  return {
659
659
  valid: !!valid,
660
660
  errors: validate.errors || void 0
661
661
  };
662
662
  }
663
- function validateFormSpec(data) {
664
- return validateFormConfig(data);
663
+ function validateFormSpec(data2) {
664
+ return validateFormConfig(data2);
665
665
  }
666
- function validatePagesConfig(data) {
667
- return validateFormConfig(data);
666
+ function validatePagesConfig(data2) {
667
+ return validateFormConfig(data2);
668
668
  }
669
669
 
670
670
  // src/form-field-category.ts
@@ -1121,10 +1121,10 @@ var _FormSpec = class _FormSpec {
1121
1121
  if (Array.isArray(jsonData.fields)) {
1122
1122
  fieldObjects = jsonData.fields;
1123
1123
  } else if (typeof jsonData.fields === "object" && jsonData.fields !== null) {
1124
- fieldObjects = Object.entries(jsonData.fields).map(([name, data]) => {
1124
+ fieldObjects = Object.entries(jsonData.fields).map(([name, data2]) => {
1125
1125
  return {
1126
1126
  name,
1127
- ...data
1127
+ ...data2
1128
1128
  };
1129
1129
  });
1130
1130
  }
@@ -9382,18 +9382,18 @@ function buildTableForGroup(groupName, fields, ihsData) {
9382
9382
  const items = [];
9383
9383
  for (const [baseName, periodMap] of Object.entries(columnGroups)) {
9384
9384
  const numeric = isNumericField(baseName);
9385
- const data = {};
9385
+ const data2 = {};
9386
9386
  const formattedData = {};
9387
9387
  for (const period of periods) {
9388
9388
  const colName = periodMap[period];
9389
9389
  const value = colName ? ihsData[colName] ?? null : null;
9390
- data[period] = value;
9390
+ data2[period] = value;
9391
9391
  formattedData[period] = formatValue(value, numeric);
9392
9392
  }
9393
9393
  items.push({
9394
9394
  displayName: getDisplayName(baseName),
9395
9395
  timePeriods: periods,
9396
- data,
9396
+ data: data2,
9397
9397
  formattedData,
9398
9398
  type: tableType,
9399
9399
  isNumeric: numeric
@@ -9750,7 +9750,253 @@ function resolveExtractionStatus(ihsRecord, jobRecords) {
9750
9750
  );
9751
9751
  return { documents, summary };
9752
9752
  }
9753
+
9754
+ // src/adapter.ts
9755
+ var AdapterError = class extends Error {
9756
+ constructor(reason, message, cause) {
9757
+ super(message);
9758
+ this.name = "AdapterError";
9759
+ this.reason = reason;
9760
+ this.cause = cause;
9761
+ }
9762
+ };
9763
+
9764
+ // src/adapter-aggregation.ts
9765
+ var ALL_AGGREGATION_OPS = [
9766
+ "sum",
9767
+ "mean",
9768
+ "latest",
9769
+ "max",
9770
+ "count"
9771
+ ];
9772
+ function applyAggregation(op, instances) {
9773
+ if (instances.length === 0) {
9774
+ return op === "count" ? 0 : null;
9775
+ }
9776
+ if (op === "count") {
9777
+ return instances.reduce(
9778
+ (acc, i) => i.value !== null && i.value !== void 0 ? acc + 1 : acc,
9779
+ 0
9780
+ );
9781
+ }
9782
+ if (op === "latest") {
9783
+ let pick = instances[0];
9784
+ for (let i = 1; i < instances.length; i++) {
9785
+ if (instances[i].observedAt > pick.observedAt) pick = instances[i];
9786
+ }
9787
+ return pick.value;
9788
+ }
9789
+ const nums = [];
9790
+ for (const i of instances) {
9791
+ if (typeof i.value === "number" && Number.isFinite(i.value)) {
9792
+ nums.push(i.value);
9793
+ } else if (i.value !== null && typeof i.value !== "undefined") {
9794
+ throw new Error(
9795
+ `applyAggregation: '${op}' requires numeric values; got ${typeof i.value}`
9796
+ );
9797
+ }
9798
+ }
9799
+ if (nums.length === 0) return null;
9800
+ switch (op) {
9801
+ case "sum":
9802
+ return nums.reduce((a, b) => a + b, 0);
9803
+ case "mean":
9804
+ return nums.reduce((a, b) => a + b, 0) / nums.length;
9805
+ case "max":
9806
+ return nums.reduce((a, b) => a > b ? a : b);
9807
+ }
9808
+ }
9809
+
9810
+ // src/data/adapter-categories.json
9811
+ var adapter_categories_default = {
9812
+ schemaVersion: "1.0.0",
9813
+ categories: [
9814
+ {
9815
+ id: "telco-carrier",
9816
+ displayName: "Telco Carrier",
9817
+ description: "Mobile carrier bill-payment + account-history signals. Any telco vendor (Celcom, Maxis, DiGi, U-Mobile, etc.) implementing this category produces the same canonical field set.",
9818
+ canonicalTable: "ihs_alt_data_telco",
9819
+ fields: [
9820
+ {
9821
+ name: "telcoOnTimePaymentRatio24m",
9822
+ type: "number",
9823
+ unit: "ratio",
9824
+ range: [0, 1],
9825
+ description: "Fraction of bills paid on time over the last 24 months. Strongest single telco predictor; \u22650.95 is the clean-history signal."
9826
+ },
9827
+ {
9828
+ name: "telcoTenureMonths",
9829
+ type: "number",
9830
+ unit: "months",
9831
+ range: [0, 600],
9832
+ description: "Account age. \u226548 months is the thin-file uplift trigger."
9833
+ },
9834
+ {
9835
+ name: "telcoSuspensionsCount24m",
9836
+ type: "number",
9837
+ unit: "count",
9838
+ range: [0, 100],
9839
+ description: "Non-payment-driven account suspensions in the last 24 months. \u22653 is a strong distress signal."
9840
+ },
9841
+ {
9842
+ name: "telcoLateDays24m",
9843
+ type: "number",
9844
+ unit: "days",
9845
+ range: [0, 800],
9846
+ description: "Cumulative days late across all bills in the trailing 24-month window."
9847
+ },
9848
+ {
9849
+ name: "telcoHandsetFinancingActive",
9850
+ type: "boolean",
9851
+ description: "Has an active handset-EMI account currently. Proxy for financing capacity already extended."
9852
+ },
9853
+ {
9854
+ name: "telcoHandsetFinancingDelinquent",
9855
+ type: "boolean",
9856
+ description: "Recent handset-EMI delinquency in the last 24 months. Distress signal even with otherwise clean bill payment."
9857
+ },
9858
+ {
9859
+ name: "telcoArpuMyr",
9860
+ type: "number",
9861
+ unit: "MYR",
9862
+ range: [0, 1e4],
9863
+ description: "Average Revenue Per User (monthly) in Malaysian Ringgit. Coarse spending-capacity proxy."
9864
+ }
9865
+ ]
9866
+ },
9867
+ {
9868
+ id: "payment-network",
9869
+ displayName: "Payment Network",
9870
+ description: "Merchant-side payment-flow signals from POS / gateway networks (iPay88, GHL, etc.). Captures actual transaction velocity rather than self-reported revenue.",
9871
+ canonicalTable: "ihs_alt_data_payments",
9872
+ fields: [
9873
+ {
9874
+ name: "paymentsMonthlyVolumeMyrT3",
9875
+ type: "number",
9876
+ unit: "MYR",
9877
+ range: [0, 1e8],
9878
+ description: "Mean monthly inbound transaction volume (RM) over the trailing 3 months."
9879
+ },
9880
+ {
9881
+ name: "paymentsMonthlyVolumeMyrT12",
9882
+ type: "number",
9883
+ unit: "MYR",
9884
+ range: [0, 1e8],
9885
+ description: "Mean monthly inbound transaction volume (RM) over the trailing 12 months. Pair with T3 for trend direction."
9886
+ },
9887
+ {
9888
+ name: "paymentsArpuStability12m",
9889
+ type: "number",
9890
+ unit: "ratio",
9891
+ range: [0, 1],
9892
+ description: "Coefficient-of-variation inverse over monthly ARPU in the trailing 12 months. Closer to 1 = steadier; closer to 0 = volatile."
9893
+ },
9894
+ {
9895
+ name: "paymentsDisputeRate12m",
9896
+ type: "number",
9897
+ unit: "ratio",
9898
+ range: [0, 1],
9899
+ description: "Fraction of transactions disputed or refunded in the trailing 12 months."
9900
+ },
9901
+ {
9902
+ name: "paymentsCustomerConcentrationTop5Pct",
9903
+ type: "number",
9904
+ unit: "ratio",
9905
+ range: [0, 1],
9906
+ description: "Revenue share from the top-5 recurring customers. Above ~0.7 is concentration risk."
9907
+ },
9908
+ {
9909
+ name: "paymentsActiveTenureMonths",
9910
+ type: "number",
9911
+ unit: "months",
9912
+ range: [0, 600],
9913
+ description: "Months since first transaction on the payment network. Establishment / continuity proxy."
9914
+ }
9915
+ ]
9916
+ },
9917
+ {
9918
+ id: "bank-statement",
9919
+ displayName: "Bank Statement",
9920
+ description: "Per-month bank-statement extractions. Naturally multi-instance: one statement per (account, month). Eval components typically aggregate across instances (sum closing balance, max debit, count of bounced transactions).",
9921
+ canonicalTable: "ihs_alt_data_bank_statements",
9922
+ fields: [
9923
+ {
9924
+ name: "bankStatementMonth",
9925
+ type: "string",
9926
+ description: "Statement period in YYYY-MM format. Used as the instance_key discriminator + by the `latest` aggregation operator."
9927
+ },
9928
+ {
9929
+ name: "bankClosingBalanceMyr",
9930
+ type: "number",
9931
+ unit: "MYR",
9932
+ range: [-1e8, 1e8],
9933
+ description: "Closing balance for the statement period."
9934
+ },
9935
+ {
9936
+ name: "bankTotalCreditsMyr",
9937
+ type: "number",
9938
+ unit: "MYR",
9939
+ range: [0, 1e8],
9940
+ description: "Sum of credit transactions during the period."
9941
+ },
9942
+ {
9943
+ name: "bankTotalDebitsMyr",
9944
+ type: "number",
9945
+ unit: "MYR",
9946
+ range: [0, 1e8],
9947
+ description: "Sum of debit transactions during the period."
9948
+ },
9949
+ {
9950
+ name: "bankLargestSingleCreditMyr",
9951
+ type: "number",
9952
+ unit: "MYR",
9953
+ range: [0, 1e8],
9954
+ description: "Largest single inbound transaction in the period. Useful for spotting one-off injections vs steady revenue."
9955
+ },
9956
+ {
9957
+ name: "bankBouncedTransactionsCount",
9958
+ type: "number",
9959
+ unit: "count",
9960
+ range: [0, 1e3],
9961
+ description: "Bounced / returned transactions in the period. Distress signal at counts > 0."
9962
+ }
9963
+ ]
9964
+ }
9965
+ ]
9966
+ };
9967
+
9968
+ // src/adapter-categories.ts
9969
+ var data = adapter_categories_default;
9970
+ function categorySchemaOf(id) {
9971
+ const found = data.categories.find((c) => c.id === id);
9972
+ if (!found) {
9973
+ throw new Error(
9974
+ `Unknown adapter category: ${id}. Available: ${data.categories.map((c) => c.id).join(", ")}`
9975
+ );
9976
+ }
9977
+ return found;
9978
+ }
9979
+ function categoryFieldsOf(id) {
9980
+ return categorySchemaOf(id).fields.map((f) => f.name);
9981
+ }
9982
+ function allCategories() {
9983
+ return data.categories;
9984
+ }
9985
+ var fieldToCategory = (() => {
9986
+ const m = /* @__PURE__ */ new Map();
9987
+ for (const cat of data.categories) {
9988
+ for (const f of cat.fields) {
9989
+ m.set(f.name, cat.id);
9990
+ }
9991
+ }
9992
+ return m;
9993
+ })();
9994
+ function categoryForField(field) {
9995
+ return fieldToCategory.get(field) ?? null;
9996
+ }
9753
9997
  export {
9998
+ ALL_AGGREGATION_OPS,
9999
+ AdapterError,
9754
10000
  BASE_FIELD_SPECS,
9755
10001
  BasicFormField,
9756
10002
  DEFAULT_VALIDATOR_DEFINITIONS,
@@ -9771,8 +10017,13 @@ export {
9771
10017
  IhsStatus,
9772
10018
  IhsValueFormat,
9773
10019
  Role,
10020
+ allCategories,
10021
+ applyAggregation,
9774
10022
  applyDynamicTitles,
9775
10023
  buildFileFieldTables,
10024
+ categoryFieldsOf,
10025
+ categoryForField,
10026
+ categorySchemaOf,
9776
10027
  evaluateExpression,
9777
10028
  extractTimePeriods,
9778
10029
  generateRHFSchema,