@finsys/core 3.1.0 → 3.3.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.
@@ -291,6 +291,99 @@
291
291
  "description": "Cash Conversion Cycle (DSO + days-inventory-outstanding − DPO). Working-capital efficiency; negative values (collect before paying suppliers) are strongest, very high values indicate cash tied up in operations."
292
292
  }
293
293
  ]
294
+ },
295
+ {
296
+ "id": "geolocation",
297
+ "displayName": "Geolocation",
298
+ "description": "Hourly-granularity movement track plus derived mobility signals for an applicant, sourced from any location-capable provider (telco network location, mobile-SDK GPS, GIS / address-verification services). Two instance kinds share this category: point instances (instanceKey 'pt:<ISO-hour>') carry one observed position per hourly bucket, mirroring the bank-statement multi-instance pattern; a single summary instance (instanceKey 'summary') carries adapter-derived behaviour signals — most importantly work-pattern regularity (sustained weekday dwell at a stable work anchor corroborates income reliability), residential stability, vacation windows, and dwell in operator-flagged hotspot zones. Vendor-agnostic: the source classifies or the adapter infers place labels; no provider-specific semantics leak into the field set. Raw coordinates are sensitive personal data — product-plane persistence is gated on PDPA consent + CRA Act 710 s25 retention review.",
299
+ "canonicalTable": "ihs_alt_data_geolocation",
300
+ "fields": [
301
+ {
302
+ "name": "geoLatitude",
303
+ "type": "number",
304
+ "unit": "deg",
305
+ "range": [-90, 90],
306
+ "description": "Observed latitude for this hourly bucket (point instances only)."
307
+ },
308
+ {
309
+ "name": "geoLongitude",
310
+ "type": "number",
311
+ "unit": "deg",
312
+ "range": [-180, 180],
313
+ "description": "Observed longitude for this hourly bucket (point instances only)."
314
+ },
315
+ {
316
+ "name": "geoAccuracyM",
317
+ "type": "number",
318
+ "unit": "meters",
319
+ "range": [0, 100000],
320
+ "description": "Source-reported horizontal accuracy radius of the observation. Cell-tower fixes are typically hundreds of meters; GPS fixes tens."
321
+ },
322
+ {
323
+ "name": "geoBucket",
324
+ "type": "string",
325
+ "description": "ISO-8601 hour bucket of the observation, e.g. '2026-06-01T08'. Redundant with the instance key (pt:<bucket>) so consumers can query without parsing keys."
326
+ },
327
+ {
328
+ "name": "geoPlaceLabel",
329
+ "type": "string",
330
+ "description": "Classified place for the bucket: home | work | commute | leisure | travel | hotspot | other. Classified by the source or inferred by the adapter from anchor dwell."
331
+ },
332
+ {
333
+ "name": "geoWorkAttendanceRatio30d",
334
+ "type": "number",
335
+ "unit": "ratio",
336
+ "range": [0, 1],
337
+ "description": "Fraction of the last 30 weekdays with >= 6 hours of dwell at the inferred work anchor. The income-reliability headline: regular full-time work patterns corroborate declared employment income (summary instance only)."
338
+ },
339
+ {
340
+ "name": "geoWorkDailyHoursAvg30d",
341
+ "type": "number",
342
+ "unit": "hours",
343
+ "range": [0, 24],
344
+ "description": "Mean daily hours of work-anchor dwell across the last 30 weekdays (summary instance only)."
345
+ },
346
+ {
347
+ "name": "geoLocationStabilityScore",
348
+ "type": "number",
349
+ "unit": "score",
350
+ "range": [0, 1],
351
+ "description": "Residential stability: share of nights spent at the primary home anchor over the observation window. 1 = every night at home (summary instance only)."
352
+ },
353
+ {
354
+ "name": "geoCommuteRegularityRatio",
355
+ "type": "number",
356
+ "unit": "ratio",
357
+ "range": [0, 1],
358
+ "description": "Consistency of the weekday home->work->home rhythm: fraction of weekdays matching the dominant commute pattern within an hour's tolerance (summary instance only)."
359
+ },
360
+ {
361
+ "name": "geoVacationDays90d",
362
+ "type": "number",
363
+ "unit": "days",
364
+ "range": [0, 90],
365
+ "description": "Days in the last 90 spent fully away from both home and work anchors (contiguous travel windows; summary instance only)."
366
+ },
367
+ {
368
+ "name": "geoHotspotDwellRatio",
369
+ "type": "number",
370
+ "unit": "ratio",
371
+ "range": [0, 1],
372
+ "description": "Share of observed buckets spent inside operator-flagged hotspot zones (known fraud / illicit-activity locations supplied to the adapter). Non-zero values warrant review, not automatic decline (summary instance only)."
373
+ },
374
+ {
375
+ "name": "geoPrimaryStateCode",
376
+ "type": "string",
377
+ "description": "Malaysian state / federal-territory code of the primary home anchor, e.g. PNG, KUL, JHR (summary instance only)."
378
+ },
379
+ {
380
+ "name": "geoAddressMatchScore",
381
+ "type": "number",
382
+ "unit": "score",
383
+ "range": [0, 1],
384
+ "description": "Agreement between the inferred home anchor and the applicant's registered residential address. 1 = same premises, 0 = different state (summary instance only)."
385
+ }
386
+ ]
294
387
  }
295
388
  ]
296
389
  }
package/dist/index.cjs CHANGED
@@ -9461,7 +9461,7 @@ function formatValue(value, numeric) {
9461
9461
  }
9462
9462
  return String(value);
9463
9463
  }
9464
- function buildTableForGroup(groupName, fields, ihsData) {
9464
+ function buildTableForGroup(groupName, fields, ihsData, fieldProvenance) {
9465
9465
  const allColumns = [];
9466
9466
  for (const field of fields) {
9467
9467
  if (field.ihs_column_names) {
@@ -9480,11 +9480,20 @@ function buildTableForGroup(groupName, fields, ihsData) {
9480
9480
  const numeric = isNumericField(baseName);
9481
9481
  const data = {};
9482
9482
  const formattedData = {};
9483
+ const confidence = {};
9484
+ const provenance = {};
9483
9485
  for (const period of periods) {
9484
9486
  const colName = periodMap[period];
9485
9487
  const value = colName ? ihsData[colName] ?? null : null;
9486
9488
  data[period] = value;
9487
9489
  formattedData[period] = formatValue(value, numeric);
9490
+ const prov = colName ? fieldProvenance?.[colName] : void 0;
9491
+ if (prov) {
9492
+ provenance[period] = prov;
9493
+ if (prov.origin === "extracted" && typeof prov.confidence === "number" && !Number.isNaN(prov.confidence)) {
9494
+ confidence[period] = prov.confidence;
9495
+ }
9496
+ }
9488
9497
  }
9489
9498
  items.push({
9490
9499
  displayName: getDisplayName(baseName),
@@ -9492,7 +9501,9 @@ function buildTableForGroup(groupName, fields, ihsData) {
9492
9501
  data,
9493
9502
  formattedData,
9494
9503
  type: tableType,
9495
- isNumeric: numeric
9504
+ isNumeric: numeric,
9505
+ ...Object.keys(confidence).length ? { confidence } : {},
9506
+ ...Object.keys(provenance).length ? { provenance } : {}
9496
9507
  });
9497
9508
  }
9498
9509
  const hasData = items.some(
@@ -9504,13 +9515,16 @@ function buildTableForGroup(groupName, fields, ihsData) {
9504
9515
  for (const colName of allColumns) {
9505
9516
  const value = ihsData[colName] ?? null;
9506
9517
  const numeric = isNumericField(colName);
9518
+ const prov = fieldProvenance?.[colName];
9507
9519
  items.push({
9508
9520
  displayName: getDisplayName(colName),
9509
9521
  timePeriods: [],
9510
9522
  data: { value },
9511
9523
  formattedData: { value: formatValue(value, numeric) },
9512
9524
  type: tableType,
9513
- isNumeric: numeric
9525
+ isNumeric: numeric,
9526
+ ...prov && prov.origin === "extracted" && typeof prov.confidence === "number" && !Number.isNaN(prov.confidence) ? { confidence: { value: prov.confidence } } : {},
9527
+ ...prov ? { provenance: { value: prov } } : {}
9514
9528
  });
9515
9529
  }
9516
9530
  const hasData = items.some((item) => {
@@ -9520,13 +9534,13 @@ function buildTableForGroup(groupName, fields, ihsData) {
9520
9534
  return { name: groupName, displayName: groupDisplayName, type: tableType, items, hasData };
9521
9535
  }
9522
9536
  }
9523
- function buildFileFieldTables(ihsData) {
9537
+ function buildFileFieldTables(ihsData, fieldProvenance) {
9524
9538
  const specs = getBaseFieldSpecs();
9525
9539
  const fileFields = specs.filter((f) => f.type === "file" && f.ihs_column_names?.length);
9526
9540
  const grouped = groupFieldsByPattern(fileFields);
9527
9541
  const tables = {};
9528
9542
  for (const [groupName, fields] of Object.entries(grouped)) {
9529
- const table = buildTableForGroup(groupName, fields, ihsData);
9543
+ const table = buildTableForGroup(groupName, fields, ihsData, fieldProvenance);
9530
9544
  if (table && table.hasData) {
9531
9545
  tables[groupName] = table;
9532
9546
  }
@@ -10197,6 +10211,99 @@ var adapter_categories_default = {
10197
10211
  description: "Cash Conversion Cycle (DSO + days-inventory-outstanding \u2212 DPO). Working-capital efficiency; negative values (collect before paying suppliers) are strongest, very high values indicate cash tied up in operations."
10198
10212
  }
10199
10213
  ]
10214
+ },
10215
+ {
10216
+ id: "geolocation",
10217
+ displayName: "Geolocation",
10218
+ description: "Hourly-granularity movement track plus derived mobility signals for an applicant, sourced from any location-capable provider (telco network location, mobile-SDK GPS, GIS / address-verification services). Two instance kinds share this category: point instances (instanceKey 'pt:<ISO-hour>') carry one observed position per hourly bucket, mirroring the bank-statement multi-instance pattern; a single summary instance (instanceKey 'summary') carries adapter-derived behaviour signals \u2014 most importantly work-pattern regularity (sustained weekday dwell at a stable work anchor corroborates income reliability), residential stability, vacation windows, and dwell in operator-flagged hotspot zones. Vendor-agnostic: the source classifies or the adapter infers place labels; no provider-specific semantics leak into the field set. Raw coordinates are sensitive personal data \u2014 product-plane persistence is gated on PDPA consent + CRA Act 710 s25 retention review.",
10219
+ canonicalTable: "ihs_alt_data_geolocation",
10220
+ fields: [
10221
+ {
10222
+ name: "geoLatitude",
10223
+ type: "number",
10224
+ unit: "deg",
10225
+ range: [-90, 90],
10226
+ description: "Observed latitude for this hourly bucket (point instances only)."
10227
+ },
10228
+ {
10229
+ name: "geoLongitude",
10230
+ type: "number",
10231
+ unit: "deg",
10232
+ range: [-180, 180],
10233
+ description: "Observed longitude for this hourly bucket (point instances only)."
10234
+ },
10235
+ {
10236
+ name: "geoAccuracyM",
10237
+ type: "number",
10238
+ unit: "meters",
10239
+ range: [0, 1e5],
10240
+ description: "Source-reported horizontal accuracy radius of the observation. Cell-tower fixes are typically hundreds of meters; GPS fixes tens."
10241
+ },
10242
+ {
10243
+ name: "geoBucket",
10244
+ type: "string",
10245
+ description: "ISO-8601 hour bucket of the observation, e.g. '2026-06-01T08'. Redundant with the instance key (pt:<bucket>) so consumers can query without parsing keys."
10246
+ },
10247
+ {
10248
+ name: "geoPlaceLabel",
10249
+ type: "string",
10250
+ description: "Classified place for the bucket: home | work | commute | leisure | travel | hotspot | other. Classified by the source or inferred by the adapter from anchor dwell."
10251
+ },
10252
+ {
10253
+ name: "geoWorkAttendanceRatio30d",
10254
+ type: "number",
10255
+ unit: "ratio",
10256
+ range: [0, 1],
10257
+ description: "Fraction of the last 30 weekdays with >= 6 hours of dwell at the inferred work anchor. The income-reliability headline: regular full-time work patterns corroborate declared employment income (summary instance only)."
10258
+ },
10259
+ {
10260
+ name: "geoWorkDailyHoursAvg30d",
10261
+ type: "number",
10262
+ unit: "hours",
10263
+ range: [0, 24],
10264
+ description: "Mean daily hours of work-anchor dwell across the last 30 weekdays (summary instance only)."
10265
+ },
10266
+ {
10267
+ name: "geoLocationStabilityScore",
10268
+ type: "number",
10269
+ unit: "score",
10270
+ range: [0, 1],
10271
+ description: "Residential stability: share of nights spent at the primary home anchor over the observation window. 1 = every night at home (summary instance only)."
10272
+ },
10273
+ {
10274
+ name: "geoCommuteRegularityRatio",
10275
+ type: "number",
10276
+ unit: "ratio",
10277
+ range: [0, 1],
10278
+ description: "Consistency of the weekday home->work->home rhythm: fraction of weekdays matching the dominant commute pattern within an hour's tolerance (summary instance only)."
10279
+ },
10280
+ {
10281
+ name: "geoVacationDays90d",
10282
+ type: "number",
10283
+ unit: "days",
10284
+ range: [0, 90],
10285
+ description: "Days in the last 90 spent fully away from both home and work anchors (contiguous travel windows; summary instance only)."
10286
+ },
10287
+ {
10288
+ name: "geoHotspotDwellRatio",
10289
+ type: "number",
10290
+ unit: "ratio",
10291
+ range: [0, 1],
10292
+ description: "Share of observed buckets spent inside operator-flagged hotspot zones (known fraud / illicit-activity locations supplied to the adapter). Non-zero values warrant review, not automatic decline (summary instance only)."
10293
+ },
10294
+ {
10295
+ name: "geoPrimaryStateCode",
10296
+ type: "string",
10297
+ description: "Malaysian state / federal-territory code of the primary home anchor, e.g. PNG, KUL, JHR (summary instance only)."
10298
+ },
10299
+ {
10300
+ name: "geoAddressMatchScore",
10301
+ type: "number",
10302
+ unit: "score",
10303
+ range: [0, 1],
10304
+ description: "Agreement between the inferred home anchor and the applicant's registered residential address. 1 = same premises, 0 = different state (summary instance only)."
10305
+ }
10306
+ ]
10200
10307
  }
10201
10308
  ]
10202
10309
  };