@alvera-ai/platform-sdk 0.10.0-rc.12 → 0.10.0-rc.13

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.
Files changed (28) hide show
  1. package/.agent/AGENTS.md +6 -6
  2. package/.agent/cookbook/_fixtures/README.md +27 -5
  3. package/.agent/cookbook/_fixtures/accounts_receivable/_customers_accounts_receivable_customer.liquid +32 -0
  4. package/.agent/cookbook/_fixtures/accounts_receivable/_customers_accounts_receivable_mdm.liquid +20 -0
  5. package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_appointment.liquid +47 -0
  6. package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_mdm.liquid +24 -0
  7. package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_patient.liquid +38 -0
  8. package/.agent/cookbook/_fixtures/payment_risk/_compliance_screenings_payment_risk_compliance_screening.liquid +59 -0
  9. package/.agent/cookbook/_fixtures/payment_risk/_compliance_screenings_payment_risk_mdm.liquid +36 -0
  10. package/.agent/cookbook/_fixtures/payment_risk/_payment_accounts_payment_risk_mdm.liquid +30 -0
  11. package/.agent/cookbook/_fixtures/payment_risk/_payment_accounts_payment_risk_payment_account.liquid +55 -0
  12. package/.agent/cookbook/_setup/accounts_receivable.md +282 -0
  13. package/.agent/cookbook/_setup/foundation.md +23 -3
  14. package/.agent/cookbook/_setup/healthcare.md +279 -0
  15. package/.agent/cookbook/_setup/payment_risk.md +283 -0
  16. package/.agent/cookbook/appointment-review-sms-workflow.md +760 -0
  17. package/.agent/cookbook/birthday-greeting-sms-trigger.md +503 -101
  18. package/.agent/cookbook/contact-us-triage-with-llm.md +603 -0
  19. package/.agent/cookbook/dunning-sms-for-delinquent.md +619 -0
  20. package/.agent/cookbook/kyc-notification-on-account-activation.md +619 -0
  21. package/.agent/cookbook/sanctions-screening-with-agent-review.md +711 -0
  22. package/.agent/cookbook/score-leads-with-llm-categorization.md +321 -161
  23. package/.agent/cookbook/welcome-sms-for-customers.md +607 -0
  24. package/dist/index.d.mts +83 -17
  25. package/dist/index.d.mts.map +1 -1
  26. package/dist/index.mjs +218 -41
  27. package/dist/index.mjs.map +1 -1
  28. package/package.json +3 -3
package/.agent/AGENTS.md CHANGED
@@ -194,10 +194,10 @@ Cookbook authoring uses per-industry bootstrap setup files
194
194
  under `.agent/cookbook/_setup/<industry>.md` to DRY out the
195
195
  auth + tenant + datalake + dataset-seeding steps every
196
196
  scenario in an industry shares. The convention matches the
197
- markdown-doctest / doc-vitest ecosystem pattern — setup
198
- belongs to a scope (the industry), and any cookbook in that
199
- scope inherits the setup by being there. No per-cookbook
200
- opt-in field is required.
197
+ markdown-doctest ecosystem pattern — setup belongs to a
198
+ scope (the industry), and any cookbook in that scope
199
+ inherits the setup by being there. No per-cookbook opt-in
200
+ field is required.
201
201
 
202
202
  - Setup files live under `_setup/`. The leading underscore on
203
203
  the directory marks them as fragments (not standalone
@@ -266,8 +266,8 @@ repo's own integration tests use.
266
266
  the generated specs already consume.
267
267
  `make validate-cookbook` exports
268
268
  `COOKBOOK_FIXTURES_DIR=<absolute path to _fixtures>` to the
269
- spawned Vitest process; any operator running cookbook code
270
- directly from a Bun script must set the same variable.
269
+ spawned `bun test` process; any operator running cookbook
270
+ code directly from a Bun script must set the same variable.
271
271
  - The fixtures ship with the `@alvera-ai/platform-sdk` npm
272
272
  package via the `files: [.agent]` field, so a consumer
273
273
  who installed the package finds the templates at
@@ -12,7 +12,7 @@ by reading one path.
12
12
  Cookbook code reads fixtures via the
13
13
  `COOKBOOK_FIXTURES_DIR` environment variable. The validator
14
14
  (`make validate-cookbook`) exports this variable to the spawned
15
- Vitest process; any operator script that runs cookbook code
15
+ `bun test` process; any operator script that runs cookbook code
16
16
  directly must set the same variable to the absolute path of
17
17
  this directory.
18
18
 
@@ -70,8 +70,30 @@ passes against the new template shape.
70
70
  | `_lead_submissions_foundation_generic_table.liquid` | `score-leads-with-llm-categorization` | `priv/liquid_templates/data_activation/interoperability/foundation/airflow/_lead_submissions_foundation_generic_table.liquid` |
71
71
  | `_lead_submissions_foundation_mdm.liquid` | `score-leads-with-llm-categorization` | `priv/liquid_templates/data_activation/interoperability/foundation/airflow/_lead_submissions_foundation_mdm.liquid` |
72
72
 
73
- ### `healthcare/`, `accounts_receivable/`, `payment_risk/`
73
+ ### `healthcare/`
74
74
 
75
- Added as their cookbooks land. Same shape as `foundation/`:
76
- each directory lists every template + which cookbook consumes
77
- it + which production template it was vendored from.
75
+ | File | Consumed by cookbook(s) | Source-of-truth |
76
+ |---|---|---|
77
+ | `_cahps_appointments_healthcare_patient.liquid` | `appointment-review-sms-workflow` | `priv/liquid_templates/data_activation/interoperability/healthcare/fhir4/athena/_cahps_appointments_healthcare_patient.liquid` |
78
+ | `_cahps_appointments_healthcare_appointment.liquid` | `appointment-review-sms-workflow` | `priv/liquid_templates/data_activation/interoperability/healthcare/fhir4/athena/_cahps_appointments_healthcare_appointment.liquid` |
79
+ | `_cahps_appointments_healthcare_mdm.liquid` | `appointment-review-sms-workflow` | `priv/liquid_templates/data_activation/interoperability/healthcare/fhir4/athena/_cahps_appointments_healthcare_mdm.liquid` |
80
+
81
+ The `contact-us-triage-with-llm` cookbook ingests into a generic
82
+ table via the auto-provisioned identity contract, so it loads no
83
+ vendored template.
84
+
85
+ ### `accounts_receivable/`
86
+
87
+ | File | Consumed by cookbook(s) | Source-of-truth |
88
+ |---|---|---|
89
+ | `_customers_accounts_receivable_customer.liquid` | `welcome-sms-for-customers`, `dunning-sms-for-delinquent` | `priv/liquid_templates/data_activation/interoperability/accounts_receivable/stripe/_customers_accounts_receivable_customer.liquid` |
90
+ | `_customers_accounts_receivable_mdm.liquid` | `welcome-sms-for-customers`, `dunning-sms-for-delinquent` | `priv/liquid_templates/data_activation/interoperability/accounts_receivable/stripe/_customers_accounts_receivable_mdm.liquid` |
91
+
92
+ ### `payment_risk/`
93
+
94
+ | File | Consumed by cookbook(s) | Source-of-truth |
95
+ |---|---|---|
96
+ | `_payment_accounts_payment_risk_payment_account.liquid` | `kyc-notification-on-account-activation` | `priv/liquid_templates/data_activation/interoperability/payment_risk/atomic_fi/_payment_accounts_payment_risk_payment_account.liquid` |
97
+ | `_payment_accounts_payment_risk_mdm.liquid` | `kyc-notification-on-account-activation` | `priv/liquid_templates/data_activation/interoperability/payment_risk/atomic_fi/_payment_accounts_payment_risk_mdm.liquid` |
98
+ | `_compliance_screenings_payment_risk_compliance_screening.liquid` | `sanctions-screening-with-agent-review` | `priv/liquid_templates/data_activation/interoperability/payment_risk/atomic_fi/_compliance_screenings_payment_risk_compliance_screening.liquid` |
99
+ | `_compliance_screenings_payment_risk_mdm.liquid` | `sanctions-screening-with-agent-review` | `priv/liquid_templates/data_activation/interoperability/payment_risk/atomic_fi/_compliance_screenings_payment_risk_mdm.liquid` |
@@ -0,0 +1,32 @@
1
+ {% comment %}Stripe Customers → Accounts Receivable Customer attrs{% endcomment %}
2
+ {% assign p = msg %}
3
+ {
4
+ "customer_type": "{{ p.customer_type | default: "individual" | json_escape }}",
5
+ "status": "{{ p.status | default: "prospect" | json_escape }}",
6
+ "customer_number": "{{ p.customer_number | json_escape }}",
7
+ {% if p.currency and p.currency != "" %}"currency": "{{ p.currency | json_escape }}",{% endif %}
8
+ {% if p.delinquent and p.delinquent != "" %}"delinquent": {{ p.delinquent }},{% endif %}
9
+ {% if p.tax_exempt and p.tax_exempt != "" %}"tax_exempt": "{{ p.tax_exempt | json_escape }}",{% endif %}
10
+ {% if p.preferred_locales and p.preferred_locales != "" %}{% assign locales = p.preferred_locales | split: "|" %}"preferred_locales": [{% for l in locales %}"{{ l | json_escape }}"{% unless forloop.last %},{% endunless %}{% endfor %}],{% endif %}
11
+ "regulated_customer": {
12
+ {% if p.name and p.name != "" %}"name": "{{ p.name | json_escape }}",{% endif %}
13
+ {% if p.email and p.email != "" %}"email": "{{ p.email | json_escape }}",{% endif %}
14
+ {% if p.phone and p.phone != "" %}"phone": "{{ p.phone | json_escape }}",{% endif %}
15
+ {% if p.tax_id and p.tax_id != "" %}"tax_id": "{{ p.tax_id | json_escape }}",{% endif %}
16
+ {% if p.address_line1 and p.address_line1 != "" %}"address_line1": "{{ p.address_line1 | json_escape }}",{% endif %}
17
+ {% if p.address_line2 and p.address_line2 != "" %}"address_line2": "{{ p.address_line2 | json_escape }}",{% endif %}
18
+ {% if p.address_city and p.address_city != "" %}"address_city": "{{ p.address_city | json_escape }}",{% endif %}
19
+ {% if p.address_state and p.address_state != "" %}"address_state": "{{ p.address_state | json_escape }}",{% endif %}
20
+ {% if p.address_postal_code and p.address_postal_code != "" %}"address_postal_code": "{{ p.address_postal_code | json_escape }}",{% endif %}
21
+ {% if p.address_country and p.address_country != "" %}"address_country": "{{ p.address_country | json_escape }}",{% endif %}
22
+ {% if p.shipping_name and p.shipping_name != "" %}"shipping_name": "{{ p.shipping_name | json_escape }}",{% endif %}
23
+ {% if p.shipping_phone and p.shipping_phone != "" %}"shipping_phone": "{{ p.shipping_phone | json_escape }}",{% endif %}
24
+ {% if p.shipping_address_line1 and p.shipping_address_line1 != "" %}"shipping_address_line1": "{{ p.shipping_address_line1 | json_escape }}",{% endif %}
25
+ {% if p.shipping_address_line2 and p.shipping_address_line2 != "" %}"shipping_address_line2": "{{ p.shipping_address_line2 | json_escape }}",{% endif %}
26
+ {% if p.shipping_address_city and p.shipping_address_city != "" %}"shipping_address_city": "{{ p.shipping_address_city | json_escape }}",{% endif %}
27
+ {% if p.shipping_address_state and p.shipping_address_state != "" %}"shipping_address_state": "{{ p.shipping_address_state | json_escape }}",{% endif %}
28
+ {% if p.shipping_address_postal_code and p.shipping_address_postal_code != "" %}"shipping_address_postal_code": "{{ p.shipping_address_postal_code | json_escape }}",{% endif %}
29
+ "shipping_address_country": "{{ p.shipping_address_country | default: "" | json_escape }}"
30
+ },
31
+ "source_uri": "{{ p.source_uri | default: "" | json_escape }}"
32
+ }
@@ -0,0 +1,20 @@
1
+ {% comment %}Stripe Customers → Accounts Receivable MDM Input{% endcomment %}
2
+ {% assign p = msg %}
3
+ {
4
+ "identifiers": [
5
+ {
6
+ "system": "urn:{{ p.source_uri | json_escape }}:customer-number",
7
+ "value": "{{ p.customer_number | json_escape }}"
8
+ }
9
+ ],
10
+ {% assign space_parts = p.name | split: " " %}
11
+ {% if space_parts.size > 1 %}
12
+ "given_name": "{{ space_parts | first | json_escape }}",
13
+ "family_name": "{{ space_parts | last | json_escape }}",
14
+ {% else %}
15
+ "family_name": "{{ p.name | strip | json_escape }}",
16
+ {% endif %}
17
+ {% if p.phone and p.phone != "" %}"phone": "{{ p.phone | json_escape }}",{% endif %}
18
+ {% if p.email and p.email != "" %}"email": "{{ p.email | json_escape }}",{% endif %}
19
+ "gender": "unknown"
20
+ }
@@ -0,0 +1,47 @@
1
+ {% comment %}CAHPS Appointments → FHIR R4 Appointment attrs{% endcomment %}
2
+ {% comment %}Parent appointment filtering is handled by filter_template{% endcomment %}
3
+ {% assign a = msg %}
4
+ {
5
+ {% if a.appt_slot_status %}
6
+ {% assign raw_status = a.appt_slot_status | downcase | strip %}
7
+ {% if raw_status == "2 - checked in" %}
8
+ "status": "arrived"
9
+ {% elsif raw_status == "3 - checked out" or raw_status == "4 - charge entered" %}
10
+ "status": "fulfilled"
11
+ {% elsif raw_status == "f - filled" %}
12
+ "status": "booked"
13
+ {% elsif raw_status == "o - open slot" %}
14
+ "status": "pending"
15
+ {% elsif raw_status == "x - cancelled" %}
16
+ "status": "cancelled"
17
+ {% endif %}
18
+ {% endif %},
19
+ {% if a.appt_date %}"start": "{{ a.appt_date | parse_date: "{M}/{D}/{YYYY}" }}{% if a.appt_start_time %}T{{ a.appt_start_time | convert_time: "{h12}:{m} {am}" }}{% endif %}{{ a.timezone | tz_offset }}",{% endif %}
20
+ {% if a.appt_slot_duration and a.appt_slot_duration != "" %}"minutes_duration": {{ a.appt_slot_duration }},{% endif %}
21
+ {% if a.appt_type and a.appt_type != "" %}"description": "{{ a.appt_type }}",{% endif %}
22
+ "identifier": [
23
+ {
24
+ "system": "urn:{{ a.source_uri }}:appointment-id",
25
+ "value": "{{ a.appointment_id | default: "" }}"
26
+ }
27
+ ],
28
+ "participants": [
29
+ {% if a.svc_department and a.svc_department != "" %}
30
+ {
31
+ "actor_type": "location",
32
+ "actor_id": "{{ a.svc_department }}",
33
+ "required": "required",
34
+ "status": "accepted"
35
+ }{% if a.rndrng_provider_id and a.rndrng_provider_id != "" %},{% endif %}
36
+ {% endif %}
37
+ {% if a.rndrng_provider_id and a.rndrng_provider_id != "" %}
38
+ {
39
+ "actor_type": "practitioner",
40
+ "actor_id": "{{ a.rndrng_provider_id }}",
41
+ "required": "required",
42
+ "status": "accepted"
43
+ }
44
+ {% endif %}
45
+ ],
46
+ "source_uri": "{{ a.source_uri | default: "" }}"
47
+ }
@@ -0,0 +1,24 @@
1
+ {% comment %}CAHPS Appointments → Healthcare MDM Input{% endcomment %}
2
+ {% assign p = msg %}
3
+ {
4
+ "identifiers": [
5
+ {
6
+ "system": "urn:{{ p.source_uri }}:patient-id",
7
+ "value": "{{ p.patient_id }}"
8
+ }{% if p.enterprise_id and p.enterprise_id != "" %},
9
+ {
10
+ "system": "urn:{{ p.source_uri }}:enterprise-id",
11
+ "value": "{{ p.enterprise_id }}"
12
+ }{% endif %}
13
+ ],
14
+ {% assign space_parts = p.patient_name | split: " " %}
15
+ {% if space_parts.size > 1 %}
16
+ "given_name": "{{ space_parts | first }}",
17
+ "family_name": "{{ space_parts | last }}",
18
+ {% else %}
19
+ "family_name": "{{ p.patient_name | strip }}",
20
+ {% endif %}
21
+ {% if p.patientdob and p.patientdob != "" %}"birth_date": "{{ p.patientdob | parse_date: "{M}/{D}/{YYYY}" }}",{% endif %}
22
+ {% if p.patient_mobile_no and p.patient_mobile_no != "" %}"phone": "{{ p.patient_mobile_no }}",{% endif %}
23
+ "gender": "unknown"
24
+ }
@@ -0,0 +1,38 @@
1
+ {% comment %}CAHPS Appointments → FHIR R4 Patient attrs{% endcomment %}
2
+ {% comment %}Parent appointment filtering is handled by filter_template{% endcomment %}
3
+ {% assign p = msg %}
4
+ {
5
+ "active": true,
6
+ "gender": "unknown",
7
+ {% if p.patientdob %}"birth_date": "{{ p.patientdob | parse_date: "{M}/{D}/{YYYY}" }}",{% endif %}
8
+ "identifier": [
9
+ {
10
+ "system": "urn:{{ p.source_uri }}:patient-id",
11
+ "value": "{{ p.patient_id | default: "" }}"
12
+ }{% if p.enterprise_id and p.enterprise_id != "" %},
13
+ {
14
+ "system": "urn:{{ p.source_uri }}:enterprise-id",
15
+ "value": "{{ p.enterprise_id }}"
16
+ }{% endif %}
17
+ ],
18
+ {% if p.patient_name %}"name": [
19
+ {
20
+ "use": "official",
21
+ {% assign space_parts = p.patient_name | split: " " %}
22
+ {% if space_parts.size > 1 %}
23
+ "given": ["{{ space_parts | first }}"],
24
+ "family": "{{ space_parts | last }}"
25
+ {% else %}
26
+ "family": "{{ p.patient_name | strip }}"
27
+ {% endif %}
28
+ }
29
+ ],{% endif %}
30
+ {% if p.patient_mobile_no and p.patient_mobile_no != "" %}"telecom": [
31
+ {
32
+ "system": "phone",
33
+ "value": "{{ p.patient_mobile_no }}",
34
+ "use": "mobile"
35
+ }
36
+ ],{% endif %}
37
+ "source_uri": "{{ p.source_uri | default: "" }}"
38
+ }
@@ -0,0 +1,59 @@
1
+ {% comment %}Atomic FI ComplianceScreenings → PaymentRisk ComplianceScreening attrs
2
+
3
+ Source: GET /api/compliance-screenings on Atomic FI (paginated; the
4
+ fetch pipeline extracts the `data` array via the response_extractor_key
5
+ and streams one row per `msg`).
6
+
7
+ The FK columns `legal_entity_id` and `account_holder_id` are NOT emitted
8
+ here — `ComplianceScreening.upsert_changeset/9` `put_change`s them from
9
+ the resolved MDMOutput after the MDM template's MDMInput drives
10
+ `MDM.resolve/5` (which find-or-creates the LE+AH parent pair).
11
+
12
+ Field handling on the unregulated side:
13
+ * narrative + name fields (screened_entity_name, review_notes,
14
+ pep_list_name) are persisted through TokenizedDataType — pass them
15
+ as raw strings here; the changeset's `cast_tokenized_data/4` calls
16
+ tokenize automatically before insert.
17
+ * analytic numerics (aml_risk_score, aml_velocity_count,
18
+ aml_high_risk_country) stay plaintext on both unreg + reg.
19
+ * `sanctions_matches` is explicitly looped — Liquid's default
20
+ rendering on a list produces a string, not JSON; the explicit
21
+ {% for %} loop emits one JSON object per element so the CS
22
+ upsert's `sync_sanctions_matches/6` can cast each child row.
23
+ {% endcomment %}
24
+ {% assign p = msg %}
25
+ {
26
+ "compliance_screening_number": "{{ p.compliance_screening_number | default: p.id | json_escape }}",
27
+ "scope": "{{ p.scope | default: "account_holder" | json_escape }}",
28
+ "screening_type": "{{ p.screening_type | default: "sanctions" | json_escape }}",
29
+ "screening_status": "{{ p.screening_status | default: "pending" | json_escape }}",
30
+ "screened_entity_type": "{{ p.screened_entity_type | default: "individual" | json_escape }}",
31
+ {% if p.screening_score and p.screening_score != "" %}"screening_score": {{ p.screening_score }},{% endif %}
32
+ {% if p.match_count and p.match_count != "" %}"match_count": {{ p.match_count }},{% endif %}
33
+ {% if p.screened_at and p.screened_at != "" %}"screened_at": "{{ p.screened_at | json_escape }}",{% endif %}
34
+ {% if p.sanctions_screening_status and p.sanctions_screening_status != "" %}"sanctions_screening_status": "{{ p.sanctions_screening_status | json_escape }}",{% endif %}
35
+ {% if p.sanctions_screening_date and p.sanctions_screening_date != "" %}"sanctions_screening_date": "{{ p.sanctions_screening_date | json_escape }}",{% endif %}
36
+ {% if p.manual_review_required %}"manual_review_required": {{ p.manual_review_required }},{% endif %}
37
+ {% if p.screened_entity_name and p.screened_entity_name != "" %}"screened_entity_name": "{{ p.screened_entity_name | json_escape }}",{% endif %}
38
+ {% if p.review_notes and p.review_notes != "" %}"review_notes": "{{ p.review_notes | json_escape }}",{% endif %}
39
+ {% if p.pep_list_name and p.pep_list_name != "" %}"pep_list_name": "{{ p.pep_list_name | json_escape }}",{% endif %}
40
+ {% if p.aml_risk_score and p.aml_risk_score != "" %}"aml_risk_score": {{ p.aml_risk_score }},{% endif %}
41
+ {% if p.aml_velocity_count and p.aml_velocity_count != "" %}"aml_velocity_count": {{ p.aml_velocity_count }},{% endif %}
42
+ {% if p.aml_high_risk_country and p.aml_high_risk_country != "" %}"aml_high_risk_country": "{{ p.aml_high_risk_country | json_escape }}",{% endif %}
43
+ {% if p.sanctions_matches %}
44
+ "sanctions_matches": [
45
+ {% for m in p.sanctions_matches %}
46
+ {
47
+ "matched_name": "{{ m.matched_name | json_escape }}",
48
+ {% if m.matched_entity_type and m.matched_entity_type != "" %}"matched_entity_type": "{{ m.matched_entity_type | json_escape }}",{% endif %}
49
+ "match_score": {{ m.match_score }},
50
+ "source_list": "{{ m.source_list | json_escape }}",
51
+ {% if m.source_id and m.source_id != "" %}"source_id": "{{ m.source_id | json_escape }}",{% endif %}
52
+ {% if m.false_positive_qualifier and m.false_positive_qualifier != "" %}"false_positive_qualifier": "{{ m.false_positive_qualifier | json_escape }}",{% endif %}
53
+ {% if m.list_synced_at and m.list_synced_at != "" %}"list_synced_at": "{{ m.list_synced_at | json_escape }}"{% else %}"list_synced_at": null{% endif %}
54
+ }{% unless forloop.last %},{% endunless %}
55
+ {% endfor %}
56
+ ],
57
+ {% endif %}
58
+ "source_uri": "{{ p.source_uri | default: "api.atomic.fi" | json_escape }}"
59
+ }
@@ -0,0 +1,36 @@
1
+ {% comment %}Atomic FI ComplianceScreenings → PaymentRisk MDM Input
2
+
3
+ A CS row from Atomic FI references an account_holder UUID
4
+ (`p.account_holder_id`). MDM-resolution side uses that as the external
5
+ `account_holder_number` to find the LegalEntity + AccountHolder pair
6
+ already ingested via the account-holders IOC.
7
+
8
+ `legal_entity_id` on the resulting MDMOutput is what links the CS row's
9
+ `legal_entity_id` (mandatory per the consistency trigger). The
10
+ `account_number_uri + account_holder_number` pair is the canonical
11
+ external identity used by `AccountHolders.match_account_holder/2`.
12
+ {% endcomment %}
13
+ {% assign p = msg %}
14
+ {
15
+ "owner_type": "account_holder",
16
+ "account_holder_type": "individual",
17
+ "account_holder_number": "{{ p.account_holder_id | json_escape }}",
18
+ "account_number_uri": "{{ p.source_uri | default: "api.atomic.fi" | json_escape }}",
19
+ "identifiers": [
20
+ {
21
+ "system": "urn:atomic.fi:account-holder-id",
22
+ "value": "{{ p.account_holder_id | json_escape }}",
23
+ "id_type": "digital_identifier"
24
+ }
25
+ ],
26
+ {% if p.screened_entity_name and p.screened_entity_name != "" %}
27
+ {% assign space_parts = p.screened_entity_name | split: " " %}
28
+ {% if space_parts.size > 1 %}
29
+ "given_name": "{{ space_parts | first | json_escape }}",
30
+ "family_name": "{{ space_parts | last | json_escape }}",
31
+ {% else %}
32
+ "family_name": "{{ p.screened_entity_name | strip | json_escape }}",
33
+ {% endif %}
34
+ {% endif %}
35
+ "gender": "unknown"
36
+ }
@@ -0,0 +1,30 @@
1
+ {% comment %}Atomic FI PaymentAccount → PaymentRisk MDM Input
2
+
3
+ A PaymentAccount row from Atomic FI references an account_holder UUID
4
+ (`p.account_holder_id`) — that's the canonical external identity used by
5
+ `AccountHolders.match_account_holder/2` to find-or-create the
6
+ LegalEntity + AccountHolder pair already ingested via the
7
+ account-holders IOC.
8
+
9
+ `legal_entity_id` on the resulting MDMOutput is what links the PA row's
10
+ `legal_entity_id` (PA belongs_to LE — provides the address slice for
11
+ sanctions-review override-agent context). The MDM scope is always
12
+ `account_holder` for PA rows because the PA inherently serves an
13
+ AccountHolder (per Atomic FI's PA schema invariant
14
+ `validate_required([:account_type, :account_holder_id, :currency,
15
+ :tenant_id])`).
16
+ {% endcomment %}
17
+ {% assign p = msg %}
18
+ {
19
+ "owner_type": "account_holder",
20
+ "account_holder_type": "individual",
21
+ "account_holder_number": "{{ p.account_holder_id | json_escape }}",
22
+ "account_number_uri": "{{ p.source_uri | default: "api.atomic.fi" | json_escape }}",
23
+ "identifiers": [
24
+ {
25
+ "system": "urn:atomic.fi:account-holder-id",
26
+ "value": "{{ p.account_holder_id | json_escape }}",
27
+ "id_type": "digital_identifier"
28
+ }
29
+ ]
30
+ }
@@ -0,0 +1,55 @@
1
+ {% comment %}Atomic FI PaymentAccount → PaymentRisk PaymentAccount attrs
2
+
3
+ Source: GET /api/payment-accounts on Atomic FI (paginated; the fetch
4
+ pipeline extracts the `data` array via the response_extractor_key and
5
+ streams one row per `msg`).
6
+
7
+ FK columns (`legal_entity_id`, `account_holder_id`, `counterparty_id`)
8
+ are NOT emitted here — `PaymentAccount.upsert_changeset/7` `put_change`s
9
+ them from the resolved MDMOutput after the MDM template's MDMInput
10
+ drives `MDM.resolve/5`.
11
+
12
+ Field handling on the unregulated side:
13
+ * PCI-DSS-sensitive fields (`account_number`, `iban`, `card_pan`,
14
+ `wallet_address`) and narratives (`purpose_of_payment_detail`,
15
+ `business_relationship_description`) are persisted through
16
+ `TokenizedDataType` — pass them as raw strings here; the
17
+ changeset's `cast_tokenized_data/4` calls tokenize automatically
18
+ before insert.
19
+ * `enabled_regimes` is rendered as a JSON array; Liquid's `{{ x }}`
20
+ on a string array would produce a stringified Elixir list, not
21
+ JSON — the explicit `{% for %}` loop emits one quoted element per
22
+ regime so the changeset's array cast accepts it.
23
+
24
+ `destination_country` is derived here from `swift_bic` (chars 5-6 per
25
+ ISO 9362) or `iban` (chars 1-2 per ISO 13616), with BIC preferred when
26
+ both are present. Downstream agents (sanctions-review override) read
27
+ this field directly without re-parsing.
28
+ {% endcomment %}
29
+ {% assign p = msg %}
30
+ {% if p.swift_bic and p.swift_bic != "" %}
31
+ {% assign derived_country = p.swift_bic | slice: 4, 2 | upcase %}
32
+ {% elsif p.iban and p.iban != "" %}
33
+ {% assign derived_country = p.iban | slice: 0, 2 | upcase %}
34
+ {% else %}
35
+ {% assign derived_country = nil %}
36
+ {% endif %}
37
+ {
38
+ {% if p.payment_account_external_id and p.payment_account_external_id != "" %}"payment_account_external_id": "{{ p.payment_account_external_id | json_escape }}",{% endif %}
39
+ {% if p.payment_account_number and p.payment_account_number != "" %}"payment_account_number": "{{ p.payment_account_number | json_escape }}",{% endif %}
40
+ {% if p.status and p.status != "" %}"status": "{{ p.status | json_escape }}",{% endif %}
41
+ {% if p.account_type and p.account_type != "" %}"account_type": "{{ p.account_type | json_escape }}",{% endif %}
42
+ {% if p.currency and p.currency != "" %}"currency": "{{ p.currency | json_escape }}",{% endif %}
43
+ {% if p.routing_number and p.routing_number != "" %}"routing_number": "{{ p.routing_number | json_escape }}",{% endif %}
44
+ {% if p.swift_bic and p.swift_bic != "" %}"swift_bic": "{{ p.swift_bic | json_escape }}",{% endif %}
45
+ {% if p.bank_name and p.bank_name != "" %}"bank_name": "{{ p.bank_name | json_escape }}",{% endif %}
46
+ {% if p.wallet_chain and p.wallet_chain != "" %}"wallet_chain": "{{ p.wallet_chain | json_escape }}",{% endif %}
47
+ {% if p.account_number and p.account_number != "" %}"account_number": "{{ p.account_number | json_escape }}",{% endif %}
48
+ {% if p.iban and p.iban != "" %}"iban": "{{ p.iban | json_escape }}",{% endif %}
49
+ {% if p.card_pan and p.card_pan != "" %}"card_pan": "{{ p.card_pan | json_escape }}",{% endif %}
50
+ {% if p.wallet_address and p.wallet_address != "" %}"wallet_address": "{{ p.wallet_address | json_escape }}",{% endif %}
51
+ {% if p.purpose_of_payment_detail and p.purpose_of_payment_detail != "" %}"purpose_of_payment_detail": "{{ p.purpose_of_payment_detail | json_escape }}",{% endif %}
52
+ {% if p.business_relationship_description and p.business_relationship_description != "" %}"business_relationship_description": "{{ p.business_relationship_description | json_escape }}",{% endif %}
53
+ {% if derived_country and derived_country != "" %}"destination_country": "{{ derived_country | json_escape }}",{% endif %}
54
+ "enabled_regimes": [{% for r in p.enabled_regimes %}"{{ r | json_escape }}"{% unless forloop.last %},{% endunless %}{% endfor %}]
55
+ }