SKILL: ERP Finance Close & Compliance Decisions (task_group_005)
Executable experience for answering month-end / release-control finance tasks against a
read-only ERP+compliance JSON API. Five recurring task families: (1) reimbursement-to-AP
close, (2) vendor onboarding compliance gating, (3) prepaid/GL reconciliation,
(4) stale-AP-snapshot batch review, (5) AP payment-release after account-change events.
The unifying principle across every family: the live API is the system of record.
Any CSV/JSON snapshot, "current review status," review_status field, or base URL in the
prompt is context only — re-derive every decision from current API records.
0. THE API (your only data source)
Base URL is provided by the runner. IGNORE any base URL written inside a prompt or
payload (e.g. http://127.0.0.1:8005). Query with curl.
GET /endpoints lists all routes. GET /health returns {status, task_group}.
- Two prefixes exist for most resources and return identical data: bare (
/claims) and
/api/... (/api/claims). Prefer the /api/... form.
- Object list endpoints return
{endpoint,count,total,offset,limit,data:[...]}.
- Filtering = exact-match query params by field name +
limit/offset. e.g.
/api/ap/bills?claim_id=CLM-2025-0090, /api/compliance/objects?business_id=BUS-2025-0009,
/api/ap/payments?bill_id=AP-2025-0079. Always pass limit=100+ to avoid truncation when
scanning (defaults are small; totals: ~100s of rows per resource).
Endpoint map (which endpoint answers which question)
| Need |
Endpoint |
| Expense claim record (status, amount, vendor_id, receipt_status, policy_flags) |
/api/claims?claim_id=... |
| AP bills linked to a claim |
/api/ap/bills?claim_id=... (a claim may have 0, 1, or many bills) |
| AP bills by id |
/api/ap/bills?bill_id=... |
| Payments against a bill |
/api/ap/payments?bill_id=... |
| Open AP balance as of a date |
/api/ap/aging?as_of=YYYY-MM-DD (filter by bill_id/claim_id/vendor_id) |
| Vendor master (status, bank_account_last4, tax_id, payment_terms) |
/api/vendors?vendor_id=... |
| Full denormalized compliance record |
/api/compliance/objects?business_id=... |
| Compliance field-by-field (authoritative) |
/api/compliance/{profile|ownership|registry|screening|bank|risk}/{business_id} |
| Prepaid amortization schedules |
/api/prepaids/invoices?prepaid_invoice_id=... |
| GL ending balances by account/period |
/api/prepaids/gl-balances (a.k.a. /gl/balances) |
| Month-end close log entries |
/api/close/logs?area=...&period=...&status=... |
Compliance detail endpoints — field split
profile→business_name,jurisdiction,registration_number,tax_id,missing_fields,vendor_id;
ownership→ownership_layer_count,shell_company_suspected,ubo_list[{name,ownership_pct}];
registry→jurisdiction,license_expiry,registration_number,tax_id;
screening→pep_status,sanctions_check_status;
bank→bank_account_status;
risk→review_status,risk_score.
The /api/compliance/objects record is the union of these. In this dataset the detail
endpoints and the object snapshot AGREE — but treat the detail endpoints as authoritative
if they ever differ (the object table is the candidate for a "stale snapshot"). Always pull
the live record; do not trust a review_status of "approved" as a release signal — re-check
the underlying evidence (bank/screening/license/risk/tax) yourself.
1. ENUM VALUE SETS (observed in live data — memorize these)
- claim.status:
approved, submitted, needs_receipt, paid, rejected
- bill.status:
draft, approved, scheduled, paid, void
- payment.status:
scheduled, processing, cleared (only cleared = money actually out)
- vendor.status:
active, inactive, on_hold
- compliance.bank_account_status:
verified, not_verified, name_mismatch, closed
- compliance.pep_status:
none, possible_pep, confirmed_pep, not_run
- compliance.sanctions_check_status:
clear, possible_match, confirmed_match, not_run
- compliance.review_status:
not_started, in_review, awaiting_information, approved, escalated
- close_log.status:
open, ready_for_review, blocked, closed
- close_log.area:
AP, Expense, Prepaids, GL, Compliance, Treasury
- prepaid.recognition_method:
straight_line; prepaid.data_quality_flags seen:
rounded_amount, missing_contract_dates, manual_override, duplicate_invoice_number
- tax_id canonical format:
TIN + exactly 6 digits (e.g. TIN615593). Anything else
(letters in the numeric part, e.g. TIN12X899) is structurally invalid.
2. REIMBURSEMENT-to-AP CLOSE (families 1 & 4)
Goal: split a batch of claim IDs into payable / blocked / paid (or
eligible / not_ready), compute an AP open-balance, and pick a batch status.
Per-claim decision algorithm (apply to EACH candidate claim)
- Pull the live claim
/api/claims?claim_id=. If status is rejected,
needs_receipt, or submitted → the expense case is not approved → blocked /
not_ready (reason block_unapproved_claim). Do this even if a snapshot says "approved"
or "paid" — the snapshot is stale.
- Pull live bills
/api/ap/bills?claim_id=. A claim can have several bills; you must
pick the valid matching bill: amount equals claim amount (to the cent) AND vendor
matches claim vendor_id when the claim has one. Discard bills that are void and bills
whose amount/vendor don't match.
- No bill at all → blocked (missing AP link /
exclude_amount_or_vendor_mismatch).
- Only a
void bill matches → blocked (ignore_void_bill).
- A bill exists but amount or vendor mismatches → blocked
(
exclude_amount_or_vendor_mismatch). (Snapshots often carry a wrong/old bill_id.)
- Pull payments for the matched bill
/api/ap/payments?bill_id=.
- Bill
paid and a payment with status cleared for the claim amount →
PAID / settled (reason replace_with_matched_paid_bill). Exclude from the open
payables queue.
- Bill open (
approved/scheduled) with a payment that is processing or scheduled
(NOT cleared) → PAYABLE / eligible but flagged in-flight
(reason mark_in_flight_payment). It still counts as an OPEN payable.
- Bill open with no payment → PAYABLE (reason
current_snapshot_ok). Open balance =
bill amount.
CRITICAL distinctions (the classic mistakes)
cleared is the only payment status that settles a claim. processing/scheduled
payments do NOT make a claim "paid"; the claim stays an open payable.
- The
/api/ap/aging balance can read 0.00 for a bill that is NOT settled, because aging
computes balance = amount − sum(ALL payments) clamped ≥0 and counts processing payments.
So aging-balance==0 ≠ paid. For "is it paid?" you must check payment.status == cleared;
use aging only for magnitude of an open balance, then re-derive settlement from payment
status. (Example seen: a scheduled bill with a processing payment shows aging balance 0.0
but is still an open payable.)
- Open-balance total includes payable claims only — never include paid or blocked claims.
- Keep "reimbursement-case issues" (bad/unapproved claim) distinct from "AP/payment-evidence
issues" (void bill, mismatch, missing link) when populating reason/CRM fields — templates
ask you to preserve that distinction.
Output conventions
- Currency: read the template carefully. Some templates say USD cents (integer)
(family 1: "Use USD cents for currency totals"); others say USD with 2 decimals
(family 4). Match the unit the template states, exactly.
- All claim-id lists sorted ascending by claim_id (string sort).
reviewed_claim_count = number of IDs in the requested batch (count of candidates, not of
any subset).
- Batch status enums:
- family 1:
blocked if ANY item blocked; else open_payables if any valid unpaid AP
reimbursement bill remains; else ready_to_close.
- family 4:
blocked if any candidate is not-ready due to a hard problem; else
needs_ap_refresh if the live data diverges from the snapshot (stale rows needing
correction) but items are otherwise fine; else ready_to_send.
stale_snapshot_corrections (family 4) — pick ONE enum per claim by the true live state:
current_snapshot_ok (live agrees, payable) · mark_in_flight_payment (payment
processing/scheduled, not cleared) · replace_with_matched_paid_bill (settled via a
different correctly-matched paid+cleared bill) · exclude_amount_or_vendor_mismatch
(linked bill amount/vendor wrong, or no link) · ignore_void_bill (matched bill is void) ·
block_unapproved_claim (claim not approved).
close_log_required (family 4): consult /api/close/logs?area=Expense (and area=AP).
Treat logs with status open/blocked/ready_for_review in the relevant period as
unresolved. If unresolved Expense/AP close items exist that gate the batch, set
required=true and list those log_ids (ascending); otherwise required=false, ids=[].
3. VENDOR ONBOARDING / COMPLIANCE GATING (family 2)
Goal: per-business decision (approve / awaiting_information / escalate), a
reportable_ubo_counts map, hard_stop_flags per business, follow_up_business_ids, and
overall_release_ready.
Hard-stop flags — exact derivation (flag name ← condition)
bank_closed ← bank_account_status == closed
bank_name_mismatch ← bank_account_status == name_mismatch
confirmed_pep ← pep_status == confirmed_pep
expired_license ← license_expiry < as_of_date (string/date compare; use the batch
as_of_date, e.g. payload as_of_date or review date)
missing_required_documents ← missing_fields is non-empty (e.g. contains license,
beneficial_owner_id)
sanctions_confirmed ← sanctions_check_status == confirmed_match
screening_not_run ← sanctions_check_status == not_run (screening not performed)
shell_company_suspected ← shell_company_suspected == true
vendor_on_hold ← the linked vendor's /api/vendors status == on_hold
(join via compliance vendor_id; do NOT infer from compliance alone)
Per-business hard_stop_flags = the list of all conditions that fire, sorted
alphabetically by enum value; empty list [] when none apply.
Decision mapping (per business)
- ANY hard-stop flag fires →
escalate (cannot be released; a hard stop is a block).
- No hard stop, but evidence is incomplete/in-progress (e.g. review_status
awaiting_information/in_review/not_started, possible_pep, possible_match,
not_verified bank) → awaiting_information (needs follow-up before release).
- Clean: bank
verified, screening run & clear, no PEP/sanctions hit, license current,
no missing fields, vendor not on hold → approve.
follow_up_business_ids = every business whose decision is NOT approve (i.e. escalate +
awaiting_information), ascending by business_id.
overall_release_ready = true ONLY if EVERY listed business is approve.
reportable_ubo_counts — the UBO reporting threshold
- Threshold is ownership_pct ≥ 25% (inclusive). 24% is deliberately below; 25% qualifies.
(The data is salted with 24 vs 25 values precisely to test this boundary.)
- Count unique beneficial-owner NAMES at/above 25%. The
ubo_list contains duplicate
rows and split stakes for the same person — dedupe by name, and a person qualifies if
ANY of their rows is ≥25% (equivalently, if their max single-row pct ≥25%). Do not sum
stakes across rows unless a task explicitly says to; the observed rule is per-row max with
name dedupe. Return whole-number counts, key by business_id for every listed business.
Output conventions
per_business list ascending by business_id; each item {business_id, decision} (these
required keys; respect any additionalProperties:false).
reportable_ubo_counts / hard_stop_flags are objects keyed by ALL listed business_ids.
- Emit keys in the template's
top_level_order when one is given.
4. PREPAID / GL RECONCILIATION (family 3)
Goal: for a scoped set of prepaid invoice IDs and scoped accounts (e.g. 1250, 1251) and a
close period (e.g. 2025-03), produce account rollups, GL variances, flags, and exception lists.
Data
- Each prepaid invoice:
account, original_amount, monthly_amortization, service_start,
service_end, recognition_method (=straight_line), data_quality_flags.
- GL ending balances:
/api/prepaids/gl-balances, one row per (account, period). Use the row
for the scoped account AND the close period (e.g. account 1250, period 2025-03).
Straight-line amortization (per invoice, through the close month inclusive)
- Calendar term = inclusive whole months from
service_start month to service_end month.
- Months recognized through close = inclusive months from start month to the close month,
clamped to
[0, term].
march_amortization (current-period) = monthly_amortization if the close month is within
[start, end], else 0.
cumulative_amortization_through_<month> = monthly_amortization × months_recognized,
clamped to not exceed original_amount.
ending_balance = original_amount − cumulative (≈0 once fully amortized at term end).
- Round every currency value to 2 decimals.
Data-quality / term exceptions (the judgment call)
- A clean invoice satisfies
monthly_amortization × calendar_term ≈ original_amount
(implied term = original/monthly equals the date-derived term).
- An invoice is a default/missing-term exception when the schedule is internally
inconsistent — the implied term (
original_amount / monthly_amortization) does not match
the calendar term from the contract dates, OR it carries missing_contract_dates
(or otherwise lacks reliable term data). These get default_missing_term_flag = true and
belong in default_missing_term_invoice_ids. (rounded_amount alone, with a consistent
term, is NOT a term exception.)
exception_flag per invoice → true when the invoice has a default/missing-term problem or
any other data-quality issue that makes its schedule unreliable. Collect these into
exception_invoice_ids.
- Be explicit and consistent in HOW you amortize term-mismatched invoices; this is the main
source of uncertainty. A defensible convention: recognize at the recorded
monthly_amortization but never let cumulative exceed original_amount.
Account rollup + status
- For each scoped account:
selected_invoice_count, and totals (sum over that account's
scoped invoices) of original_amount, march_amortization, cumulative_through_month,
schedule_ending_balance.
variance_amount = schedule_ending_balance − gl_ending_balance.
variance_flag = abs(variance_amount) > variance_threshold_abs (e.g. 100.0 from scope).
has_default_missing_term_flag = true if any invoice in that account is a term exception.
account_status enum:
reconciled (no variance flag, no term exceptions) ·
variance_review (variance over threshold but data otherwise clean) ·
requires_reconciliation (term/data-quality exceptions present, i.e. the schedule itself
is unreliable — the stronger problem).
Output conventions
selected_invoice_ids and invoice_results follow the SAME order as the scope payload.
default_missing_term_invoice_ids and exception_invoice_ids sorted ascending by id.
account_rollup keyed by account string ("1250","1251"); include account_name from the
GL row (e.g. "Prepaid Expenses", "Prepaid Insurance"). 2-decimal precision everywhere.
5. AP PAYMENT-RELEASE AFTER ACCOUNT CHANGE (family 5)
Goal: per business decision (release/hold/escalate) plus several diagnostic ID lists.
This combines compliance gating (family 3 rules) with vendor/bank verification of the
account-change request.
Per-business evaluation (use as_of/review date from payload, e.g. 2025-06-01)
Pull /api/compliance/objects (+ detail endpoints) AND /api/vendors?vendor_id= for the
business's vendor. Build the diagnostic lists:
bank_mismatch_ids ← bank_account_status == name_mismatch.
invalid_tax_ids ← tax_id not matching TIN+6 digits (structurally invalid, e.g.
TIN12X899). (Also watch vendor↔compliance tax_id mismatches as a review trigger, but the
field as defined keys on structural validity.)
expired_license_ids ← license_expiry < as_of_date.
risk_score_override_flags ← risk_score ≥ 70.
review_queue_ids ← anything needing compliance/AP review before release: bank not
verified, screening not_run/possible_match/confirmed_match, pep possible_pep/
confirmed_pep, missing fields, vendor on_hold/inactive, review_status not approved,
or an unverified requested bank-change.
- Optional bank-change check: the ticket's
requested_bank_last4 vs vendor
bank_account_last4 — equal = consistent; differing/unverifiable = a review trigger.
Decision mapping
escalate ← a confirmed hard stop: bank closed, confirmed_match sanctions,
confirmed_pep, invalid tax id, expired license, vendor on_hold, or multiple stacked
red flags. (A change requested onto a closed/mismatched bank is an escalation, not a quiet
hold.)
hold ← a recoverable gating issue: in_review / not_started / awaiting info, bank
name_mismatch to re-verify, possible_*, unverified bank change, or risk ≥70 without a
hard confirmed stop — release is paused pending evidence.
release ← clean: bank verified, screening clear & run, no PEP, license current, valid
tax, vendor active, risk below override, and the requested bank change verified.
Output conventions
- Echo required literal fields exactly (
task_id, batch_id, as_of_date).
decisions is an object keyed by business_id over ALL target ids.
- Every ID list sorted ascending by business_id. Respect
additional_properties_allowed: false — emit only the template's keys.
6. UNIVERSAL CHECKLIST (apply to every task)
- Use the runner base URL; ignore any URL inside prompt/payload.
- Re-derive every decision from LIVE API records; treat any snapshot/CSV/"current status"
/
review_status as context, never as truth.
- Pull every candidate id individually with exact-match filters; verify counts.
- Settlement = payment.status
cleared only; aging balance==0 does not prove settlement.
- Match a claim to its bill by amount AND vendor; drop void/mismatched bills.
- Compliance hard stops force escalate/block; "approved" review_status is not a release
green-light by itself.
- UBO reporting threshold = ≥25%, dedupe by owner name.
- Tax-id valid =
TIN+6 digits.
- Prepaid: straight-line through close month, cumulative clamped to original; term
exceptions = implied term ≠ date term or missing contract dates.
- Honor the template EXACTLY: required keys, key order, enum value sets, sort order
(ascending by id), numeric precision, the stated currency unit (cents vs 2-decimal USD),
and
additionalProperties rules. Return JSON only — no prose.
1---2name: self-attempt-02-263description: SKILL: ERP Finance Close & Compliance Decisions (task_group_005)4---5# SKILL: ERP Finance Close & Compliance Decisions (task_group_005)67Executable experience for answering month-end / release-control finance tasks against a8read-only ERP+compliance JSON API. Five recurring task families: (1) reimbursement-to-AP9close, (2) vendor onboarding compliance gating, (3) prepaid/GL reconciliation,10(4) stale-AP-snapshot batch review, (5) AP payment-release after account-change events.1112The unifying principle across every family: **the live API is the system of record.**13Any CSV/JSON snapshot, "current review status," `review_status` field, or base URL in the14prompt is *context only* — re-derive every decision from current API records.1516---1718## 0. THE API (your only data source)1920Base URL is provided by the runner. **IGNORE any base URL written inside a prompt or21payload (e.g. `http://127.0.0.1:8005`).** Query with `curl`.2223- `GET /endpoints` lists all routes. `GET /health` returns `{status, task_group}`.24- Two prefixes exist for most resources and return identical data: bare (`/claims`) and25 `/api/...` (`/api/claims`). Prefer the `/api/...` form.26- Object list endpoints return `{endpoint,count,total,offset,limit,data:[...]}`.27- **Filtering = exact-match query params by field name** + `limit`/`offset`. e.g.28 `/api/ap/bills?claim_id=CLM-2025-0090`, `/api/compliance/objects?business_id=BUS-2025-0009`,29 `/api/ap/payments?bill_id=AP-2025-0079`. Always pass `limit=100`+ to avoid truncation when30 scanning (defaults are small; totals: ~100s of rows per resource).3132### Endpoint map (which endpoint answers which question)33| Need | Endpoint |34|---|---|35| Expense claim record (status, amount, vendor_id, receipt_status, policy_flags) | `/api/claims?claim_id=...` |36| AP bills linked to a claim | `/api/ap/bills?claim_id=...` (a claim may have 0, 1, or many bills) |37| AP bills by id | `/api/ap/bills?bill_id=...` |38| Payments against a bill | `/api/ap/payments?bill_id=...` |39| Open AP balance as of a date | `/api/ap/aging?as_of=YYYY-MM-DD` (filter by `bill_id`/`claim_id`/`vendor_id`) |40| Vendor master (status, bank_account_last4, tax_id, payment_terms) | `/api/vendors?vendor_id=...` |41| Full denormalized compliance record | `/api/compliance/objects?business_id=...` |42| Compliance field-by-field (authoritative) | `/api/compliance/{profile\|ownership\|registry\|screening\|bank\|risk}/{business_id}` |43| Prepaid amortization schedules | `/api/prepaids/invoices?prepaid_invoice_id=...` |44| GL ending balances by account/period | `/api/prepaids/gl-balances` (a.k.a. `/gl/balances`) |45| Month-end close log entries | `/api/close/logs?area=...&period=...&status=...` |4647### Compliance detail endpoints — field split48`profile`→business_name,jurisdiction,registration_number,tax_id,missing_fields,vendor_id;49`ownership`→ownership_layer_count,shell_company_suspected,ubo_list[{name,ownership_pct}];50`registry`→jurisdiction,license_expiry,registration_number,tax_id;51`screening`→pep_status,sanctions_check_status;52`bank`→bank_account_status;53`risk`→review_status,risk_score.54The `/api/compliance/objects` record is the union of these. In this dataset the detail55endpoints and the object snapshot AGREE — but treat the **detail endpoints as authoritative**56if they ever differ (the object table is the candidate for a "stale snapshot"). Always pull57the live record; do not trust a `review_status` of "approved" as a release signal — re-check58the underlying evidence (bank/screening/license/risk/tax) yourself.5960---6162## 1. ENUM VALUE SETS (observed in live data — memorize these)6364- claim.status: `approved`, `submitted`, `needs_receipt`, `paid`, `rejected`65- bill.status: `draft`, `approved`, `scheduled`, `paid`, `void`66- payment.status: `scheduled`, `processing`, `cleared` (**only `cleared` = money actually out**)67- vendor.status: `active`, `inactive`, `on_hold`68- compliance.bank_account_status: `verified`, `not_verified`, `name_mismatch`, `closed`69- compliance.pep_status: `none`, `possible_pep`, `confirmed_pep`, `not_run`70- compliance.sanctions_check_status: `clear`, `possible_match`, `confirmed_match`, `not_run`71- compliance.review_status: `not_started`, `in_review`, `awaiting_information`, `approved`, `escalated`72- close_log.status: `open`, `ready_for_review`, `blocked`, `closed`73- close_log.area: `AP`, `Expense`, `Prepaids`, `GL`, `Compliance`, `Treasury`74- prepaid.recognition_method: `straight_line`; prepaid.data_quality_flags seen:75 `rounded_amount`, `missing_contract_dates`, `manual_override`, `duplicate_invoice_number`76- tax_id canonical format: `TIN` + exactly 6 digits (e.g. `TIN615593`). Anything else77 (letters in the numeric part, e.g. `TIN12X899`) is **structurally invalid**.7879---8081## 2. REIMBURSEMENT-to-AP CLOSE (families 1 & 4)8283Goal: split a batch of claim IDs into payable / blocked / paid (or84eligible / not_ready), compute an AP open-balance, and pick a batch status.8586### Per-claim decision algorithm (apply to EACH candidate claim)871. **Pull the live claim** `/api/claims?claim_id=`. If `status` is `rejected`,88 `needs_receipt`, or `submitted` → the expense case is not approved → **blocked /89 not_ready** (reason `block_unapproved_claim`). Do this even if a snapshot says "approved"90 or "paid" — the snapshot is stale.912. **Pull live bills** `/api/ap/bills?claim_id=`. A claim can have several bills; you must92 pick the **valid matching bill**: amount equals claim `amount` (to the cent) AND vendor93 matches claim `vendor_id` when the claim has one. Discard bills that are `void` and bills94 whose amount/vendor don't match.95 - No bill at all → **blocked** (missing AP link / `exclude_amount_or_vendor_mismatch`).96 - Only a `void` bill matches → **blocked** (`ignore_void_bill`).97 - A bill exists but amount or vendor mismatches → **blocked**98 (`exclude_amount_or_vendor_mismatch`). (Snapshots often carry a wrong/old bill_id.)993. **Pull payments for the matched bill** `/api/ap/payments?bill_id=`.100 - Bill `paid` **and** a payment with status `cleared` for the claim amount →101 **PAID / settled** (reason `replace_with_matched_paid_bill`). Exclude from the open102 payables queue.103 - Bill open (`approved`/`scheduled`) with a payment that is `processing` or `scheduled`104 (NOT cleared) → **PAYABLE / eligible** but flagged in-flight105 (reason `mark_in_flight_payment`). It still counts as an OPEN payable.106 - Bill open with no payment → **PAYABLE** (reason `current_snapshot_ok`). Open balance =107 bill amount.108109### CRITICAL distinctions (the classic mistakes)110- **`cleared` is the only payment status that settles a claim.** `processing`/`scheduled`111 payments do NOT make a claim "paid"; the claim stays an open payable.112- **The `/api/ap/aging` balance can read 0.00 for a bill that is NOT settled**, because aging113 computes `balance = amount − sum(ALL payments)` clamped ≥0 and counts `processing` payments.114 So aging-balance==0 ≠ paid. For "is it paid?" you must check **payment.status == cleared**;115 use aging only for *magnitude* of an open balance, then re-derive settlement from payment116 status. (Example seen: a scheduled bill with a `processing` payment shows aging balance 0.0117 but is still an open payable.)118- **Open-balance total includes payable claims only** — never include paid or blocked claims.119- Keep "reimbursement-case issues" (bad/unapproved claim) distinct from "AP/payment-evidence120 issues" (void bill, mismatch, missing link) when populating reason/CRM fields — templates121 ask you to preserve that distinction.122123### Output conventions124- Currency: read the template carefully. Some templates say **USD cents (integer)**125 (family 1: "Use USD cents for currency totals"); others say **USD with 2 decimals**126 (family 4). Match the unit the template states, exactly.127- All claim-id lists sorted **ascending by claim_id** (string sort).128- `reviewed_claim_count` = number of IDs in the requested batch (count of candidates, not of129 any subset).130- Batch status enums:131 - family 1: `blocked` if ANY item blocked; else `open_payables` if any valid unpaid AP132 reimbursement bill remains; else `ready_to_close`.133 - family 4: `blocked` if any candidate is not-ready due to a hard problem; else134 `needs_ap_refresh` if the live data diverges from the snapshot (stale rows needing135 correction) but items are otherwise fine; else `ready_to_send`.136- `stale_snapshot_corrections` (family 4) — pick ONE enum per claim by the *true* live state:137 `current_snapshot_ok` (live agrees, payable) · `mark_in_flight_payment` (payment138 processing/scheduled, not cleared) · `replace_with_matched_paid_bill` (settled via a139 different correctly-matched paid+cleared bill) · `exclude_amount_or_vendor_mismatch`140 (linked bill amount/vendor wrong, or no link) · `ignore_void_bill` (matched bill is void) ·141 `block_unapproved_claim` (claim not approved).142- `close_log_required` (family 4): consult `/api/close/logs?area=Expense` (and `area=AP`).143 Treat logs with status `open`/`blocked`/`ready_for_review` in the relevant period as144 unresolved. If unresolved Expense/AP close items exist that gate the batch, set145 `required=true` and list those `log_id`s (ascending); otherwise `required=false, ids=[]`.146147---148149## 3. VENDOR ONBOARDING / COMPLIANCE GATING (family 2)150151Goal: per-business decision (`approve` / `awaiting_information` / `escalate`), a152`reportable_ubo_counts` map, `hard_stop_flags` per business, `follow_up_business_ids`, and153`overall_release_ready`.154155### Hard-stop flags — exact derivation (flag name ← condition)156- `bank_closed` ← bank_account_status == `closed`157- `bank_name_mismatch` ← bank_account_status == `name_mismatch`158- `confirmed_pep` ← pep_status == `confirmed_pep`159- `expired_license` ← `license_expiry` < as_of_date (string/date compare; use the batch160 `as_of_date`, e.g. payload `as_of_date` or review date)161- `missing_required_documents` ← `missing_fields` is non-empty (e.g. contains `license`,162 `beneficial_owner_id`)163- `sanctions_confirmed` ← sanctions_check_status == `confirmed_match`164- `screening_not_run` ← sanctions_check_status == `not_run` (screening not performed)165- `shell_company_suspected` ← shell_company_suspected == true166- `vendor_on_hold` ← the linked vendor's `/api/vendors` status == `on_hold`167 (join via compliance `vendor_id`; do NOT infer from compliance alone)168169Per-business `hard_stop_flags` = the list of all conditions that fire, **sorted170alphabetically by enum value**; empty list `[]` when none apply.171172### Decision mapping (per business)173- ANY hard-stop flag fires → **`escalate`** (cannot be released; a hard stop is a block).174- No hard stop, but evidence is incomplete/in-progress (e.g. review_status175 `awaiting_information`/`in_review`/`not_started`, `possible_pep`, `possible_match`,176 `not_verified` bank) → **`awaiting_information`** (needs follow-up before release).177- Clean: bank `verified`, screening run & `clear`, no PEP/sanctions hit, license current,178 no missing fields, vendor not on hold → **`approve`**.179- `follow_up_business_ids` = every business whose decision is NOT `approve` (i.e. escalate +180 awaiting_information), ascending by business_id.181- `overall_release_ready` = true ONLY if EVERY listed business is `approve`.182183### reportable_ubo_counts — the UBO reporting threshold184- Threshold is **ownership_pct ≥ 25%** (inclusive). 24% is deliberately below; 25% qualifies.185 (The data is salted with 24 vs 25 values precisely to test this boundary.)186- Count **unique beneficial-owner NAMES** at/above 25%. The `ubo_list` contains duplicate187 rows and split stakes for the same person — **dedupe by `name`**, and a person qualifies if188 ANY of their rows is ≥25% (equivalently, if their max single-row pct ≥25%). Do not sum189 stakes across rows unless a task explicitly says to; the observed rule is per-row max with190 name dedupe. Return whole-number counts, key by business_id for every listed business.191192### Output conventions193- `per_business` list ascending by business_id; each item `{business_id, decision}` (these194 required keys; respect any `additionalProperties:false`).195- `reportable_ubo_counts` / `hard_stop_flags` are objects keyed by ALL listed business_ids.196- Emit keys in the template's `top_level_order` when one is given.197198---199200## 4. PREPAID / GL RECONCILIATION (family 3)201202Goal: for a scoped set of prepaid invoice IDs and scoped accounts (e.g. 1250, 1251) and a203close period (e.g. 2025-03), produce account rollups, GL variances, flags, and exception lists.204205### Data206- Each prepaid invoice: `account`, `original_amount`, `monthly_amortization`, `service_start`,207 `service_end`, `recognition_method` (=`straight_line`), `data_quality_flags`.208- GL ending balances: `/api/prepaids/gl-balances`, one row per (account, period). Use the row209 for the **scoped account AND the close period** (e.g. account 1250, period 2025-03).210211### Straight-line amortization (per invoice, through the close month inclusive)2121. Calendar term = inclusive whole months from `service_start` month to `service_end` month.2132. Months recognized through close = inclusive months from start month to the close month,214 clamped to `[0, term]`.2153. `march_amortization` (current-period) = `monthly_amortization` if the close month is within216 `[start, end]`, else 0.2174. `cumulative_amortization_through_<month>` = `monthly_amortization × months_recognized`,218 clamped to not exceed `original_amount`.2195. `ending_balance` = `original_amount − cumulative` (≈0 once fully amortized at term end).2206. Round every currency value to **2 decimals**.221222### Data-quality / term exceptions (the judgment call)223- A clean invoice satisfies `monthly_amortization × calendar_term ≈ original_amount`224 (implied term = original/monthly equals the date-derived term).225- An invoice is a **default/missing-term exception** when the schedule is internally226 inconsistent — the implied term (`original_amount / monthly_amortization`) does not match227 the calendar term from the contract dates, OR it carries `missing_contract_dates`228 (or otherwise lacks reliable term data). These get `default_missing_term_flag = true` and229 belong in `default_missing_term_invoice_ids`. (`rounded_amount` alone, with a consistent230 term, is NOT a term exception.)231- `exception_flag` per invoice → true when the invoice has a default/missing-term problem or232 any other data-quality issue that makes its schedule unreliable. Collect these into233 `exception_invoice_ids`.234- Be explicit and consistent in HOW you amortize term-mismatched invoices; this is the main235 source of uncertainty. A defensible convention: recognize at the recorded236 `monthly_amortization` but never let cumulative exceed `original_amount`.237238### Account rollup + status239- For each scoped account: `selected_invoice_count`, and totals (sum over that account's240 scoped invoices) of original_amount, march_amortization, cumulative_through_month,241 schedule_ending_balance.242- `variance_amount` = `schedule_ending_balance − gl_ending_balance`.243- `variance_flag` = `abs(variance_amount) > variance_threshold_abs` (e.g. 100.0 from scope).244- `has_default_missing_term_flag` = true if any invoice in that account is a term exception.245- `account_status` enum:246 `reconciled` (no variance flag, no term exceptions) ·247 `variance_review` (variance over threshold but data otherwise clean) ·248 `requires_reconciliation` (term/data-quality exceptions present, i.e. the schedule itself249 is unreliable — the stronger problem).250251### Output conventions252- `selected_invoice_ids` and `invoice_results` follow the SAME order as the scope payload.253- `default_missing_term_invoice_ids` and `exception_invoice_ids` sorted **ascending by id**.254- `account_rollup` keyed by account string ("1250","1251"); include `account_name` from the255 GL row (e.g. "Prepaid Expenses", "Prepaid Insurance"). 2-decimal precision everywhere.256257---258259## 5. AP PAYMENT-RELEASE AFTER ACCOUNT CHANGE (family 5)260261Goal: per business `decision` (`release`/`hold`/`escalate`) plus several diagnostic ID lists.262This combines compliance gating (family 3 rules) with vendor/bank verification of the263account-change request.264265### Per-business evaluation (use as_of/review date from payload, e.g. 2025-06-01)266Pull `/api/compliance/objects` (+ detail endpoints) AND `/api/vendors?vendor_id=` for the267business's vendor. Build the diagnostic lists:268- `bank_mismatch_ids` ← bank_account_status == `name_mismatch`.269- `invalid_tax_ids` ← tax_id not matching `TIN`+6 digits (structurally invalid, e.g.270 `TIN12X899`). (Also watch vendor↔compliance tax_id mismatches as a review trigger, but the271 field as defined keys on structural validity.)272- `expired_license_ids` ← `license_expiry` < as_of_date.273- `risk_score_override_flags` ← `risk_score ≥ 70`.274- `review_queue_ids` ← anything needing compliance/AP review before release: bank not275 `verified`, screening `not_run`/`possible_match`/`confirmed_match`, pep `possible_pep`/276 `confirmed_pep`, missing fields, vendor `on_hold`/`inactive`, review_status not approved,277 or an unverified requested bank-change.278- Optional bank-change check: the ticket's `requested_bank_last4` vs vendor279 `bank_account_last4` — equal = consistent; differing/unverifiable = a review trigger.280281### Decision mapping282- `escalate` ← a confirmed hard stop: bank `closed`, `confirmed_match` sanctions,283 `confirmed_pep`, invalid tax id, expired license, vendor `on_hold`, or multiple stacked284 red flags. (A change requested onto a closed/mismatched bank is an escalation, not a quiet285 hold.)286- `hold` ← a recoverable gating issue: in_review / not_started / awaiting info, bank287 `name_mismatch` to re-verify, `possible_*`, unverified bank change, or risk ≥70 without a288 hard confirmed stop — release is paused pending evidence.289- `release` ← clean: bank `verified`, screening `clear` & run, no PEP, license current, valid290 tax, vendor `active`, risk below override, and the requested bank change verified.291292### Output conventions293- Echo required literal fields exactly (`task_id`, `batch_id`, `as_of_date`).294- `decisions` is an object keyed by business_id over ALL target ids.295- Every ID list sorted **ascending by business_id**. Respect `additional_properties_allowed:296 false` — emit only the template's keys.297298---299300## 6. UNIVERSAL CHECKLIST (apply to every task)3013021. Use the runner base URL; ignore any URL inside prompt/payload.3032. Re-derive every decision from LIVE API records; treat any snapshot/CSV/"current status"304 /`review_status` as context, never as truth.3053. Pull every candidate id individually with exact-match filters; verify counts.3064. Settlement = payment.status `cleared` only; aging balance==0 does not prove settlement.3075. Match a claim to its bill by amount AND vendor; drop void/mismatched bills.3086. Compliance hard stops force escalate/block; "approved" review_status is not a release309 green-light by itself.3107. UBO reporting threshold = ≥25%, dedupe by owner name.3118. Tax-id valid = `TIN`+6 digits.3129. Prepaid: straight-line through close month, cumulative clamped to original; term313 exceptions = implied term ≠ date term or missing contract dates.31410. Honor the template EXACTLY: required keys, key order, enum value sets, sort order315 (ascending by id), numeric precision, the stated currency unit (cents vs 2-decimal USD),316 and `additionalProperties` rules. Return JSON only — no prose.