@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.
- package/.agent/AGENTS.md +6 -6
- package/.agent/cookbook/_fixtures/README.md +27 -5
- package/.agent/cookbook/_fixtures/accounts_receivable/_customers_accounts_receivable_customer.liquid +32 -0
- package/.agent/cookbook/_fixtures/accounts_receivable/_customers_accounts_receivable_mdm.liquid +20 -0
- package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_appointment.liquid +47 -0
- package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_mdm.liquid +24 -0
- package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_patient.liquid +38 -0
- package/.agent/cookbook/_fixtures/payment_risk/_compliance_screenings_payment_risk_compliance_screening.liquid +59 -0
- package/.agent/cookbook/_fixtures/payment_risk/_compliance_screenings_payment_risk_mdm.liquid +36 -0
- package/.agent/cookbook/_fixtures/payment_risk/_payment_accounts_payment_risk_mdm.liquid +30 -0
- package/.agent/cookbook/_fixtures/payment_risk/_payment_accounts_payment_risk_payment_account.liquid +55 -0
- package/.agent/cookbook/_setup/accounts_receivable.md +282 -0
- package/.agent/cookbook/_setup/foundation.md +23 -3
- package/.agent/cookbook/_setup/healthcare.md +279 -0
- package/.agent/cookbook/_setup/payment_risk.md +283 -0
- package/.agent/cookbook/appointment-review-sms-workflow.md +760 -0
- package/.agent/cookbook/birthday-greeting-sms-trigger.md +503 -101
- package/.agent/cookbook/contact-us-triage-with-llm.md +603 -0
- package/.agent/cookbook/dunning-sms-for-delinquent.md +619 -0
- package/.agent/cookbook/kyc-notification-on-account-activation.md +619 -0
- package/.agent/cookbook/sanctions-screening-with-agent-review.md +711 -0
- package/.agent/cookbook/score-leads-with-llm-categorization.md +321 -161
- package/.agent/cookbook/welcome-sms-for-customers.md +607 -0
- package/dist/index.d.mts +83 -17
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +218 -41
- package/dist/index.mjs.map +1 -1
- 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
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
73
|
+
### `healthcare/`
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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` |
|
package/.agent/cookbook/_fixtures/accounts_receivable/_customers_accounts_receivable_customer.liquid
ADDED
|
@@ -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
|
+
}
|
package/.agent/cookbook/_fixtures/accounts_receivable/_customers_accounts_receivable_mdm.liquid
ADDED
|
@@ -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
|
+
}
|
package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_appointment.liquid
ADDED
|
@@ -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
|
+
}
|
package/.agent/cookbook/_fixtures/payment_risk/_payment_accounts_payment_risk_payment_account.liquid
ADDED
|
@@ -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
|
+
}
|