ERP Finance Expense-Control Skill (task_group_005)
Self-contained operating guide for solving finance expense-control tasks against the shared ERP
finance API. A solver needs only the API base URL (provided by the runner) plus this document.
1. API discovery and contract
Base URL and discovery
- The runner supplies the API base URL (the local form
http://127.0.0.1:8005 and the remote form
<remote-env-url> serve the same data). Always treat the remote API as the system of
record; never rely on local files or stale snapshots as truth.
- Discover every available path with
GET {base}/endpoints. It returns the path list and the
filtering contract.
- Health checks:
GET /health and GET /api/health.
Filtering contract (all list endpoints)
- Exact-match query parameters by field name (e.g.
?claim_id=CLM-2025-OPS-017,
?account=1250&period=2025-03, ?business_id=BUS-2025-0009, ?vendor_id=VEN-0064,
?bill_id=AP-2025-0068).
limit and offset for pagination. Default page size is 100; total count is in the total
field. When you need all rows, paginate with a large limit (e.g. 200) until offset+count >= total.
- Every response envelope:
{ "count": N, "data": [ ... ], "endpoint": "...", "limit": ..., "offset": ..., "total": ... }.
Resource paths (each resource has BOTH a namespaced /api/... path and a bare path; both work)
| Resource |
Namespaced path (preferred) |
Bare path |
Key filterable fields |
| Expense claims |
/api/claims |
/claims |
claim_id, status, vendor_id, category, department |
| AP bills |
/api/ap/bills |
/bills |
bill_id, claim_id, vendor_id, status, account, due_date |
| AP payments |
/api/ap/payments |
/payments |
payment_id, bill_id, vendor_id, status |
| AP aging (denormalized bill+payment view) |
/api/ap/aging |
(none) |
bill_id, claim_id, vendor_id, status, due_date, balance |
| Vendors |
/api/vendors |
/vendors |
vendor_id, status, tax_id |
| Compliance/KYC objects |
/api/compliance/objects |
/compliance/objects |
business_id, vendor_id, pep_status, sanctions_check_status, bank_account_status |
| Prepaid invoices |
/api/prepaids/invoices |
/prepaids/invoices |
prepaid_invoice_id, account, vendor_id |
| GL balances |
/api/prepaids/gl-balances |
/gl/balances |
account, period, entity |
| Close logs |
/api/close/logs |
/close/logs |
log_id, period, area, status, related_account |
Run GET /endpoints first on any new environment to confirm exact paths — the namespaced /api/...
forms are canonical.
2. Data shapes (field reference)
Claim (/api/claims)
claim_id, status, amount, currency, category, department, employee_name, submitted_date, approved_date, vendor_id (nullable), receipt_status, policy_flags[], notes
- Claim status values:
submitted, needs_receipt, approved, paid, rejected.
approved_date is null until approved. A needs_receipt/submitted/rejected claim is NOT approvable.
AP bill (/api/ap/bills)
bill_id, claim_id (nullable), vendor_id, account, amount, currency, bill_date, due_date, invoice_number, memo, status
- Bill status values:
draft, approved, scheduled, paid, void.
- Open (payable) bill statuses:
approved, scheduled. Settled: paid. Cancelled: void. draft is not yet committed.
- A bill is linked to a claim via
claim_id. Multiple bills can share one claim_id (some stale/wrong).
AP payment (/api/ap/payments)
payment_id, bill_id, vendor_id, amount, method, payment_date, bank_reference, status
- Payment status values:
scheduled, processing, cleared.
- Only
cleared payments reduce an AP open balance. scheduled and processing payments are
"in flight" and do NOT reduce the open balance yet (but must be flagged as in-flight).
AP aging (/api/ap/aging)
Denormalized per-bill row: bill_id, claim_id, vendor_id, amount, paid_amount, balance, status, bill_date, due_date, as_of. balance already nets cleared payments. Use this for payment-run
prioritization and quick balance lookups.
Vendor (/api/vendors)
vendor_id, vendor_name, legal_name, status, tax_id, bank_account_last4, default_account, payment_terms, industry, updated_at
- Vendor status values:
active, inactive, on_hold.
Compliance/KYC object (/api/compliance/objects) — keyed by business_id (one record per business)
business_id, business_name, vendor_id, jurisdiction, registration_number, tax_id, license_expiry, bank_account_status, pep_status, sanctions_check_status, shell_company_suspected (bool), ownership_layer_count, missing_fields[], ubo_list[{name, ownership_pct}], review_status, risk_score
bank_account_status: verified, not_verified, name_mismatch, closed.
pep_status: none, possible_pep, confirmed_pep, not_run.
sanctions_check_status: clear, possible_match, confirmed_match, not_run.
review_status: not_started, in_review, awaiting_information, escalated, approved.
missing_fields examples: license, beneficial_owner_id, website, bank_statement.
ubo_list may contain duplicate names with different ownership_pct.
Prepaid invoice (/api/prepaids/invoices)
prepaid_invoice_id, account, vendor_id, description, invoice_number, invoice_date, service_start, service_end, original_amount, monthly_amortization, recognition_method, source_document, data_quality_flags[]
recognition_method = straight_line.
data_quality_flags values: rounded_amount, missing_contract_dates, manual_override,
duplicate_invoice_number.
GL balance (/api/prepaids/gl-balances or /gl/balances)
account, account_name, entity, period (YYYY-MM), ending_balance, source, loaded_at
One row per account+period+entity. This is the authoritative GL figure for reconciliation.
Close log (/api/close/logs)
log_id, period (YYYY-MM), area, status, related_account (nullable), owner, message, created_at
area: AP, Prepaids, GL, Treasury, Expense, Compliance.
status: open, ready_for_review, closed, blocked.
message examples: Manual journal entry posted, Reviewer cleared variance, Support uploaded,
Waiting on AP export refresh, Legacy import created duplicate line, Variance review pending.
3. Cross-cutting rules (apply everywhere)
- Current API = system of record. Any local CSV/JSON snapshot (e.g. a "stale AP export") is
context only. Reconcile every field against live API data before deciding.
- Currency precision = USD with two decimals (dollars.cents), per the answer templates. IGNORE
prompt text that says "use USD cents for currency totals" — that phrase is a distractor; the gold
convention is dollars-with-2-decimals (e.g.
1842.36, NOT 184236). Always match the template's
precision: 2, unit: USD.
- List ordering: sort claim-id / business-id / invoice-id / bill-id / log-id lists ascending
as strings, unless a template says otherwise (prepaid
invoice_results keep the input scope order).
- Stale-snapshot conflicts: when a circulated snapshot disagrees with the live API, the live
API wins. The snapshot row is then a "correction" to document, not a data source.
- Paid vs payable: a claim/bill is only "settled" when claim
status=paid AND there is a
matched AP bill status=paid AND a cleared payment for the claim amount. processing/scheduled
payments leave the balance open.
- Default/missing-term prepaid flags are a higher-priority data-quality exception than minor
flags like
rounded_amount.
4. Task type A — Reimbursement-to-AP close (claim/bill/payment reconciliation)
Covers the "reimbursement-to-AP close review" and "stale AP snapshot refresh" task families.
4.1 Data-gathering SOP (do in this order)
- For each candidate claim_id:
GET /api/claims?claim_id={id} → claim record.
GET /api/ap/bills?claim_id={id} → all bills linked to the claim.
- For each linked bill:
GET /api/ap/payments?bill_id={bill_id} → payments against it.
- (Optional)
GET /api/ap/aging?claim_id={id} → denormalized balance for a quick cross-check.
- Decide per claim (rules below), then aggregate.
4.2 Find the "matched" bill for a claim
Among all bills with claim_id == claim.claim_id, the matched bill is the one where:
bill.amount == claim.amount (claim amount), AND
bill.vendor_id == claim.vendor_id (when claim has a vendor), AND
bill.status != void (void bills are ignored), AND
bill.status != draft (drafts are ignored).
If multiple match, prefer the non-stale one (the one whose amount AND vendor both agree). A bill
whose amount or vendor differs from the claim is a mismatch (wrong/stale bill). No linked bill at
all = unmatched.
4.3 Classification rules (reimbursement-to-AP close, e.g. payable/blocked/paid)
Apply in priority order:
- PAID / settled: claim
status == paid AND a matched bill exists with status == paid AND a
cleared payment exists for the claim amount. (These leave the AP queue — already settled.)
- PAYABLE / eligible-to-remain: claim
status == approved AND a matched OPEN bill exists
(status in {approved,scheduled}) that is not void/mismatched. Open balance remains.
- BLOCKED / not-ready (needs owner cleanup or AP-link remediation) when ANY of:
- claim
status is not approved and not paid (i.e. submitted/needs_receipt/rejected)
→ "block_unapproved_claim";
- no matched bill exists (no bill linked, OR all linked bills are void, OR all are amount/vendor
mismatches, OR only drafts);
- a linked bill is
void → "ignore_void_bill";
- a linked bill has amount or vendor mismatch with the claim → "exclude_amount_or_vendor_mismatch".
- CRM-required / owner-cleanup = the set of blocked claims (case-owner must fix the expense case or
AP link before AP release).
4.4 Open AP balance computation
For each claim: ap_balance = matched_bill.amount − Σ(cleared payment amounts on that bill).
- Only
cleared payments count. processing and scheduled payments do NOT reduce the balance.
- No matched bill, or claim not approved → balance
0.0.
- Matched bill fully paid with cleared payment for the full amount → balance
0.0.
- Matched open bill with an in-flight (
processing/scheduled) payment → balance stays = bill amount
(the payment has not cleared), AND the claim is still eligible/payable with correction
mark_in_flight_payment.
ap_open_balance_total (close-review variant) = sum of open balances over payable claims only
(not paid, not blocked).
4.5 Stale-snapshot correction codes (when a circulated snapshot must be reconciled to live API)
Map each candidate claim to exactly one of these enum values:
current_snapshot_ok — snapshot agrees with current API; no correction needed.
mark_in_flight_payment — snapshot shows no/scheduled payment but live API has a payment in flight
(processing or scheduled); claim still eligible, balance stays open.
replace_with_matched_paid_bill — snapshot referenced the wrong bill; live API has a matched PAID
bill (with cleared payment) that should replace it. Claim is settled/eligible.
exclude_amount_or_vendor_mismatch — live bill amount or vendor does not match the claim; exclude it.
ignore_void_bill — live bill is void; ignore it.
block_unapproved_claim — claim is not approved (status not in {approved,paid}) in live API; block.
4.6 Batch status enum
close-review variant (ready_to_close / open_payables / blocked):
blocked if ANY batch item is blocked;
- else
open_payables if valid unpaid AP reimbursement bills remain (payable claims exist);
- else
ready_to_close.
stale-snapshot refresh variant (ready_to_send / needs_ap_refresh / blocked):
ready_to_send if every claim is eligible and every correction is current_snapshot_ok;
needs_ap_refresh if at least one stale correction is required (snapshot diverges from API) but at
least one claim is eligible after refresh;
blocked if no claim can be released (all not-ready/blocked).
4.7 close_log_required
required: true when the batch is not ready_to_send (i.e. needs_ap_refresh or blocked) and a
relevant AP-area close log exists for the period(s) of the corrected bill/payment activity.
- Identify candidate logs via
GET /api/close/logs?area=AP&period={YYYY-MM} and prefer entries whose
message is Manual journal entry posted (the AP manual journal that documents the adjustment),
in the period overlapped by the matched bills of the eligible/refreshed claims. Sort ids ascending.
required: false (and empty ids) when the batch is ready_to_send.
- Heuristic derived from limited examples: when the eligible claims' matched bills fall in a single
month, reference that month's AP manual-journal close log. Examine AP-area close logs for the
affected period(s) rather than guessing.
5. Task type B — Vendor onboarding finance-risk release (KYC)
Covers "onboarding release call" tasks. Output schema uses per-business
decision ∈ {approve,awaiting_information,escalate} plus hard-stop flags and UBO counts.
5.1 Data-gathering SOP
- For each
business_id in the batch: GET /api/compliance/objects?business_id={id} → KYC record.
- Get the linked vendor:
GET /api/vendors?vendor_id={compliance.vendor_id} → vendor record.
- Compute hard-stop flags, UBO count, then decision.
5.2 Hard-stop flag generation (enum, output sorted ALPHABETICALLY by value; empty list if none)
confirmed_pep — pep_status == "confirmed_pep". (possible_pep and not_run do NOT set this.)
sanctions_confirmed — sanctions_check_status == "confirmed_match".
screening_not_run — sanctions_check_status == "not_run" OR pep_status == "not_run".
bank_closed — bank_account_status == "closed".
bank_name_mismatch — bank_account_status == "name_mismatch".
shell_company_suspected — shell_company_suspected == true.
vendor_on_hold — vendor record status == "on_hold" (from /api/vendors, NOT the compliance object).
missing_required_documents — missing_fields is non-empty.
expired_license — "license" is NOT in missing_fields AND year-month of license_expiry is
strictly before the as_of year-month. (Same-month expiry is NOT expired. When the license doc itself
is missing, missing_fields contains license; do NOT also raise expired_license — the missing
doc supersedes the stale expiry date.)
bank_account_status == "not_verified" does not map to any specific hard-stop flag (it is not
closed or name_mismatch).
5.3 reportable_ubo_counts
Count of distinct UBO names in ubo_list that have at least one ownership_pct >= 25
(reporting threshold, inclusive). De-duplicate by name (a name appearing in multiple entries counts
once if any of its ownership_pct entries is >= 25). Output integer >= 0.
5.4 Decision logic (onboarding)
approve — no hard-stop flags at all.
awaiting_information — hard-stop flags exist but are ALL "remediable gap" type:
{missing_required_documents, screening_not_run, expired_license} (vendor can cure by
submitting docs / renewing license / running screening).
escalate — ANY hard-stop flag is a "severe/definitive" type: {confirmed_pep,
sanctions_confirmed, bank_closed, bank_name_mismatch, shell_company_suspected,
vendor_on_hold}.
follow_up_business_ids = all businesses whose decision is NOT approve (i.e. escalate or
awaiting_information), ascending.
overall_release_ready = true only if EVERY business is approve; otherwise false.
- Decisions must be derived from current compliance+vendor evidence, NOT copied from the source
review_status field.
6. Task type C — Prepaid amortization close & GL reconciliation
Covers "prepaid close check" tasks for a scoped set of prepaid invoice IDs and a close period.
6.1 Data-gathering SOP
- Read the scope payload:
entity, close_period (YYYY-MM), accounts[],
selected_prepaid_invoice_ids[], variance_threshold_abs.
- For each invoice:
GET /api/prepaids/invoices?prepaid_invoice_id={id}.
- For each account:
GET /api/prepaids/gl-balances?account={acct}&period={close_period} (or
/gl/balances). Take ending_balance as the GL balance; also capture account_name.
6.2 Amortization computation (straight-line)
Let close period = close_period (e.g. 2025-03 → close month index 3 of 2025). For each invoice:
monthly_amortization is given in the record (authoritative per-month figure; do NOT recompute as
original/term).
- Period amortization (e.g. march_amortization) =
monthly_amortization if the invoice's service
is active during the close month (service_start month <= close month <= service_end month), else 0.
A mid-month service_start (e.g. 2025-03-15) still earns the FULL monthly amortization for that month.
- Cumulative amortization through the close period =
monthly_amortization × (number of months
from service_start's month through min(close_month, service_end's month), inclusive). Concretely:
months = max(0, (close_year − start_year)*12 + (close_month − start_month) + 1), capped so the count
never extends past service_end's month.
- Ending balance =
original_amount − cumulative_amortization_through_close. May be a small
non-zero residual (e.g. 0.01) due to per-month rounding vs original amount — that residual is correct.
- All amounts to 2 decimals.
6.3 Per-invoice flags
default_missing_term_flag = true iff "missing_contract_dates" ∈ data_quality_flags
(the contract term dates are missing/unreliable — the schedule may be defaulted).
exception_flag = true iff data_quality_flags is non-empty (ANY flag, including
rounded_amount, missing_contract_dates, manual_override, duplicate_invoice_number).
6.4 Account rollup (per scoped account)
Sum across the account's scoped invoices (2 decimals, round each component consistently):
selected_invoice_count, original_amount_total, {period}_amortization_total,
cumulative_amortization_through_{period}, schedule_ending_balance (sum of invoice ending balances).
gl_ending_balance = GL ending_balance for that account+period.
variance_amount = schedule_ending_balance − gl_ending_balance (signed: positive ⇒ schedule
above GL, i.e. GL over-amortized / under-stated balance; negative ⇒ schedule below GL, i.e. GL
under-amortized / has unrecorded additions). This signed direction is the "close-impact direction".
variance_flag = true iff abs(variance_amount) > variance_threshold_abs (scope payload gives
threshold, e.g. 100.0).
has_default_missing_term_flag = true if ANY scoped invoice in the account has
default_missing_term_flag == true.
account_status ∈ {reconciled,variance_review,requires_reconciliation}:
requires_reconciliation when variance_flag == true (GL and schedule disagree beyond threshold);
variance_review when variance_flag == false but has_default_missing_term_flag == true
(terms questionable even though the balance ties — re-check the term);
reconciled when variance_flag == false and has_default_missing_term_flag == false.
6.5 Output lists
invoice_results and selected_invoice_ids: keep the same order as the input scope file
(prepaid_close_scope.json), NOT sorted.
default_missing_term_invoice_ids: scoped invoices with default_missing_term_flag == true,
sorted ascending by invoice id.
exception_invoice_ids: scoped invoices with exception_flag == true, sorted ascending by
invoice id. Exception priority (for narrative, not ordering): missing_contract_dates
(default/missing term) is the highest-priority data-quality concern; rounded_amount is minor.
period = close_period (YYYY-MM); entity = scope entity.
7. Task type D — AP payment release after vendor account-change (release/hold/escalate)
Covers "payment release risk review" tasks after vendor account-change events. Output schema uses
per-business decision ∈ {release,hold,escalate} plus flag lists.
7.1 Data-gathering SOP
- Read the batch payload:
target_business_ids[], review_date (as_of_date, YYYY-MM-DD),
account_change_events[] (ticket_id, business_id, vendor_id, change_type, requested_bank_last4,
requested_release_amount_usd, priority).
- For each business:
GET /api/compliance/objects?business_id={id} and
GET /api/vendors?vendor_id={id's vendor} (use the compliance vendor_id, or the ticket's vendor_id).
- Compute the flag lists, then the decision.
7.2 Flag lists (each sorted ascending by business_id)
bank_mismatch_ids — businesses where compliance.bank_account_status == "name_mismatch".
(Only name_mismatch; closed is NOT included here — it is a separate hold reason.)
invalid_tax_ids — businesses where compliance.tax_id != vendor.tax_id (the compliance-recorded
tax id disagrees with the vendor master tax id). Both a format-invalid placeholder
(e.g. TIN999999) and a letter-containing id (TIN12X899) manifest as a mismatch vs the vendor
record; the reliable test is the cross-record mismatch.
expired_license_ids — businesses where year-month of license_expiry is strictly before the
as_of year-month (same-month is NOT expired). Use as_of_date / review_date as the comparison date.
risk_score_override_flags — businesses where compliance.risk_score >= 70.
review_queue_ids — ALL businesses whose decision is NOT release (i.e. hold or escalate),
ascending. (Release decisions are not queued for review.)
7.3 Decision logic (payment release) — DIFFERENT from onboarding
release — no blocking issues at all (bank verified, tax matches, license current, sanctions clear,
pep not confirmed/not-run-free, risk_score < 70, no missing docs).
escalate — a severe identity/legal-fraud issue: invalid_tax_ids contains the business.
(By analogy, confirmed_pep/sanctions_confirmed/shell_company_suspected would also escalate;
these were not present in the reference data — treat them as escalate triggers.)
hold — a remediable/operational blocking issue and NO escalate trigger: bank name_mismatch
or closed, expired_license, screening_not_run (sanctions/pep not_run), missing_fields
non-empty, or risk_score >= 70. The payment cannot be released until the vendor remediates; it is
queued for AP/compliance review.
KEY CONTRAST with onboarding (type B): in onboarding, bank_closed/bank_name_mismatch/vendor_on_hold
are escalate triggers; in payment release they are hold triggers (operational). And in payment
release, expired_license and screening_not_run are hold, not escalate. Only identity-fraud
issues (invalid tax id, confirmed pep/sanctions/shell) escalate a payment release.
8. Common misjudgments and exclusion rules
- "Use USD cents" distractor:Templates say precision 2 USD → output dollars-with-cents
(1842.36). Do not emit integer cents (184236).
- Stale-snapshot conflicts:Never trust the circulated snapshot over the live API. The snapshot is
only mentioned to compute the "correction" code. A snapshot
status=scheduled with a live paid
matched bill is replace_with_matched_paid_bill, not current_snapshot_ok.
- Paid vs payable:
processing/scheduled payments do NOT settle a bill. Only cleared payments
reduce the open balance. A bill with an in-flight payment is still payable/eligible with an open
balance and correction mark_in_flight_payment.
- Claim-vs-AP alignment:A bill linked by
claim_id is only "matched" if amount AND vendor agree
with the claim. Wrong-amount/wrong-vendor linked bills are mismatches (exclude_amount_or_vendor_mismatch),
not matched bills. Void linked bills are ignored (ignore_void_bill).
- Default/missing-term prepaid flag:
default_missing_term_flag is specifically
missing_contract_dates; other data_quality_flags (rounded_amount, manual_override,
duplicate_invoice_number) set exception_flag but NOT default_missing_term_flag.
- Exception priority ranking: when summarizing,
missing_contract_dates (default/missing term) is
the highest-priority exception; rounded_amount is the lowest. The output exception_invoice_ids
list is still sorted ascending by id, not by priority.
- Expired-license boundary: expiry is judged by year-month strictly before the as_of year-month
(same month = not expired), and is suppressed when
"license" is in missing_fields (use
missing_required_documents instead). Getting this boundary wrong mis-classifies onboarding and
payment-release businesses.
- Signed close-impact direction (prepaid):
variance_amount = schedule − GL (signed). A negative
variance means GL > schedule (under-amortized / unrecorded additions); positive means GL < schedule
(over-amortized / unrecorded release). Keep the sign.
- Copy-from-source trap: for onboarding and payment-release decisions, derive from current
compliance+vendor evidence — do NOT copy the source
review_status or a stale decision.
- Invoice result ordering: prepaid
invoice_results and selected_invoice_ids keep INPUT scope
order; only default_missing_term_invoice_ids and exception_invoice_ids are sorted ascending.
- Bill status
draft: draft bills are not committed; treat as not-a-valid-matched-bill.
bank_account_status == not_verified: does not map to any onboarding hard-stop flag (only
closed and name_mismatch do).
9. Controlled vocabularies (exact enum values required by answer templates)
- Reimbursement-AP close
batch_status: ready_to_close, open_payables, blocked.
- Stale-snapshot refresh
batch_status: ready_to_send, needs_ap_refresh, blocked.
- Stale-snapshot
corrections enum: current_snapshot_ok, mark_in_flight_payment,
replace_with_matched_paid_bill, exclude_amount_or_vendor_mismatch, ignore_void_bill,
block_unapproved_claim.
- Onboarding
decision: approve, awaiting_information, escalate.
- Onboarding
hard_stop_flags enum (alphabetical in output): bank_closed, bank_name_mismatch,
confirmed_pep, expired_license, missing_required_documents, sanctions_confirmed,
screening_not_run, shell_company_suspected, vendor_on_hold.
- Payment-release
decision: release, hold, escalate.
- Prepaid
account_status: reconciled, variance_review, requires_reconciliation.
- Prepaid
data_quality_flags: rounded_amount, missing_contract_dates, manual_override,
duplicate_invoice_number.
- Claim status:
submitted, needs_receipt, approved, paid, rejected.
- AP bill status:
draft, approved, scheduled, paid, void.
- AP payment status:
scheduled, processing, cleared.
- 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.
- Close log
area: AP, Prepaids, GL, Treasury, Expense, Compliance.
- Close log
status: open, ready_for_review, closed, blocked.
10. Concrete solver SOP (recommended order for any task in this group)
- Read the task prompt and the local payload (
answer_template.json is the source of truth for the
required output shape, field names, ordering, and enums; any input batch file lists candidates).
GET {base}/endpoints to confirm paths; GET {base}/api/health to confirm liveness.
- Identify the task type (A: reimbursement-AP close / stale AP refresh; B: vendor onboarding KYC;
C: prepaid amortization close; D: payment release after account-change) from the prompt + template.
- Gather live API data per the type-specific SOP in sections 4.1 / 5.1 / 6.1 / 7.1. Paginate to get
all rows; re-query by exact id rather than assuming.
- Apply the classification/decision rules (sections 4.2–4.6, 5.2–5.4, 6.2–6.5, 7.2–7.3). For
payment-run ordering needs (not always an output), rank open AP bills by
due_date ascending then
bill_id ascending.
- Double-check the cross-cutting rules (section 3) and the misjudgments (section 8), especially:
currency precision (dollars.cents), cleared-vs-in-flight payments, matched-bill amount+vendor
test, expired-license month boundary + missing-license suppression, signed prepaid variance, and
the onboarding-vs-payment-release escalate/hold contrast.
- Emit ONE JSON object matching the template exactly: correct top-level keys, correct ordering, exact
enum spellings, 2-decimal USD numbers. No narrative text outside the JSON.
Quick decision tables
Onboarding (type B) — hard-stop → decision
| Severe (→ escalate) |
Remediable (→ awaiting_information if alone) |
| confirmed_pep, sanctions_confirmed, bank_closed, bank_name_mismatch, shell_company_suspected, vendor_on_hold |
missing_required_documents, screening_not_run, expired_license |
| (approve = no hard stops at all) |
|
Payment release (type D) — issue → decision
| Severe (→ escalate) |
Remediable (→ hold) |
| invalid_tax_id (compliance.tax_id ≠ vendor.tax_id); confirmed_pep; sanctions_confirmed; shell_company_suspected |
bank name_mismatch/closed; expired_license; screening_not_run; missing_fields non-empty; risk_score ≥ 70 |
| (release = no blocking issues) |
|
Reimbursement-AP (type A) — claim classification
| Class |
Condition |
| paid/settled |
claim status=paid AND matched bill paid AND cleared payment = claim amount |
| payable/eligible |
claim status=approved AND matched open (approved/scheduled) bill exists |
| blocked/not-ready |
claim not approved, OR no matched bill (void/mismatch/none/draft-only) |
1---2name: fewshot-attempt-01-63description: ERP Finance Expense-Control Skill (task_group_005)4---5# ERP Finance Expense-Control Skill (task_group_005)67Self-contained operating guide for solving finance expense-control tasks against the shared ERP8finance API. A solver needs only the API base URL (provided by the runner) plus this document.910## 1. API discovery and contract1112### Base URL and discovery13- The runner supplies the API base URL (the local form `http://127.0.0.1:8005` and the remote form14 `<remote-env-url>` serve the same data). Always treat the **remote API as the system of15 record**; never rely on local files or stale snapshots as truth.16- Discover every available path with `GET {base}/endpoints`. It returns the path list and the17 filtering contract.18- Health checks: `GET /health` and `GET /api/health`.1920### Filtering contract (all list endpoints)21- Exact-match query parameters by field name (e.g. `?claim_id=CLM-2025-OPS-017`,22 `?account=1250&period=2025-03`, `?business_id=BUS-2025-0009`, `?vendor_id=VEN-0064`,23 `?bill_id=AP-2025-0068`).24- `limit` and `offset` for pagination. Default page size is 100; total count is in the `total`25 field. When you need all rows, paginate with a large `limit` (e.g. 200) until `offset+count >= total`.26- Every response envelope: `{ "count": N, "data": [ ... ], "endpoint": "...", "limit": ..., "offset": ..., "total": ... }`.2728### Resource paths (each resource has BOTH a namespaced `/api/...` path and a bare path; both work)29| Resource | Namespaced path (preferred) | Bare path | Key filterable fields |30|---|---|---|---|31| Expense claims | `/api/claims` | `/claims` | `claim_id`, `status`, `vendor_id`, `category`, `department` |32| AP bills | `/api/ap/bills` | `/bills` | `bill_id`, `claim_id`, `vendor_id`, `status`, `account`, `due_date` |33| AP payments | `/api/ap/payments` | `/payments` | `payment_id`, `bill_id`, `vendor_id`, `status` |34| AP aging (denormalized bill+payment view) | `/api/ap/aging` | (none) | `bill_id`, `claim_id`, `vendor_id`, `status`, `due_date`, `balance` |35| Vendors | `/api/vendors` | `/vendors` | `vendor_id`, `status`, `tax_id` |36| Compliance/KYC objects | `/api/compliance/objects` | `/compliance/objects` | `business_id`, `vendor_id`, `pep_status`, `sanctions_check_status`, `bank_account_status` |37| Prepaid invoices | `/api/prepaids/invoices` | `/prepaids/invoices` | `prepaid_invoice_id`, `account`, `vendor_id` |38| GL balances | `/api/prepaids/gl-balances` | `/gl/balances` | `account`, `period`, `entity` |39| Close logs | `/api/close/logs` | `/close/logs` | `log_id`, `period`, `area`, `status`, `related_account` |4041Run `GET /endpoints` first on any new environment to confirm exact paths — the namespaced `/api/...`42forms are canonical.4344## 2. Data shapes (field reference)4546### Claim (`/api/claims`)47`claim_id, status, amount, currency, category, department, employee_name, submitted_date,48approved_date, vendor_id (nullable), receipt_status, policy_flags[], notes`49- Claim status values: `submitted`, `needs_receipt`, `approved`, `paid`, `rejected`.50- `approved_date` is null until approved. A `needs_receipt`/`submitted`/`rejected` claim is NOT approvable.5152### AP bill (`/api/ap/bills`)53`bill_id, claim_id (nullable), vendor_id, account, amount, currency, bill_date, due_date,54invoice_number, memo, status`55- Bill status values: `draft`, `approved`, `scheduled`, `paid`, `void`.56- Open (payable) bill statuses: `approved`, `scheduled`. Settled: `paid`. Cancelled: `void`. `draft` is not yet committed.57- A bill is linked to a claim via `claim_id`. Multiple bills can share one `claim_id` (some stale/wrong).5859### AP payment (`/api/ap/payments`)60`payment_id, bill_id, vendor_id, amount, method, payment_date, bank_reference, status`61- Payment status values: `scheduled`, `processing`, `cleared`.62- **Only `cleared` payments reduce an AP open balance.** `scheduled` and `processing` payments are63 "in flight" and do NOT reduce the open balance yet (but must be flagged as in-flight).6465### AP aging (`/api/ap/aging`)66Denormalized per-bill row: `bill_id, claim_id, vendor_id, amount, paid_amount, balance, status,67bill_date, due_date, as_of`. `balance` already nets cleared payments. Use this for payment-run68prioritization and quick balance lookups.6970### Vendor (`/api/vendors`)71`vendor_id, vendor_name, legal_name, status, tax_id, bank_account_last4, default_account,72payment_terms, industry, updated_at`73- Vendor status values: `active`, `inactive`, `on_hold`.7475### Compliance/KYC object (`/api/compliance/objects`) — keyed by `business_id` (one record per business)76`business_id, business_name, vendor_id, jurisdiction, registration_number, tax_id, license_expiry,77bank_account_status, pep_status, sanctions_check_status, shell_company_suspected (bool),78ownership_layer_count, missing_fields[], ubo_list[{name, ownership_pct}], review_status, risk_score`79- `bank_account_status`: `verified`, `not_verified`, `name_mismatch`, `closed`.80- `pep_status`: `none`, `possible_pep`, `confirmed_pep`, `not_run`.81- `sanctions_check_status`: `clear`, `possible_match`, `confirmed_match`, `not_run`.82- `review_status`: `not_started`, `in_review`, `awaiting_information`, `escalated`, `approved`.83- `missing_fields` examples: `license`, `beneficial_owner_id`, `website`, `bank_statement`.84- `ubo_list` may contain duplicate names with different ownership_pct.8586### Prepaid invoice (`/api/prepaids/invoices`)87`prepaid_invoice_id, account, vendor_id, description, invoice_number, invoice_date,88service_start, service_end, original_amount, monthly_amortization, recognition_method,89source_document, data_quality_flags[]`90- `recognition_method` = `straight_line`.91- `data_quality_flags` values: `rounded_amount`, `missing_contract_dates`, `manual_override`,92 `duplicate_invoice_number`.9394### GL balance (`/api/prepaids/gl-balances` or `/gl/balances`)95`account, account_name, entity, period (YYYY-MM), ending_balance, source, loaded_at`96One row per account+period+entity. This is the *authoritative* GL figure for reconciliation.9798### Close log (`/api/close/logs`)99`log_id, period (YYYY-MM), area, status, related_account (nullable), owner, message, created_at`100- `area`: `AP`, `Prepaids`, `GL`, `Treasury`, `Expense`, `Compliance`.101- `status`: `open`, `ready_for_review`, `closed`, `blocked`.102- `message` examples: `Manual journal entry posted`, `Reviewer cleared variance`, `Support uploaded`,103 `Waiting on AP export refresh`, `Legacy import created duplicate line`, `Variance review pending`.104105## 3. Cross-cutting rules (apply everywhere)1061071. **Current API = system of record.** Any local CSV/JSON snapshot (e.g. a "stale AP export") is108 context only. Reconcile every field against live API data before deciding.1092. **Currency precision = USD with two decimals** (dollars.cents), per the answer templates. IGNORE110 prompt text that says "use USD cents for currency totals" — that phrase is a distractor; the gold111 convention is dollars-with-2-decimals (e.g. `1842.36`, NOT `184236`). Always match the template's112 `precision: 2, unit: USD`.1133. **List ordering**: sort claim-id / business-id / invoice-id / bill-id / log-id lists **ascending**114 as strings, unless a template says otherwise (prepaid `invoice_results` keep the input scope order).1154. **Stale-snapshot conflicts**: when a circulated snapshot disagrees with the live API, the live116 API wins. The snapshot row is then a "correction" to document, not a data source.1175. **Paid vs payable**: a claim/bill is only "settled" when claim `status=paid` AND there is a118 matched AP bill `status=paid` AND a `cleared` payment for the claim amount. `processing`/`scheduled`119 payments leave the balance open.1206. **Default/missing-term prepaid flags** are a higher-priority data-quality exception than minor121 flags like `rounded_amount`.122123## 4. Task type A — Reimbursement-to-AP close (claim/bill/payment reconciliation)124125Covers the "reimbursement-to-AP close review" and "stale AP snapshot refresh" task families.126127### 4.1 Data-gathering SOP (do in this order)1281. For each candidate claim_id: `GET /api/claims?claim_id={id}` → claim record.1292. `GET /api/ap/bills?claim_id={id}` → all bills linked to the claim.1303. For each linked bill: `GET /api/ap/payments?bill_id={bill_id}` → payments against it.1314. (Optional) `GET /api/ap/aging?claim_id={id}` → denormalized balance for a quick cross-check.1325. Decide per claim (rules below), then aggregate.133134### 4.2 Find the "matched" bill for a claim135Among all bills with `claim_id == claim.claim_id`, the **matched bill** is the one where:136- `bill.amount == claim.amount` (claim amount), AND137- `bill.vendor_id == claim.vendor_id` (when claim has a vendor), AND138- `bill.status != void` (void bills are ignored), AND139- `bill.status != draft` (drafts are ignored).140If multiple match, prefer the non-stale one (the one whose amount AND vendor both agree). A bill141whose amount or vendor differs from the claim is a **mismatch** (wrong/stale bill). No linked bill at142all = unmatched.143144### 4.3 Classification rules (reimbursement-to-AP close, e.g. payable/blocked/paid)145Apply in priority order:146- **PAID / settled**: claim `status == paid` AND a matched bill exists with `status == paid` AND a147 `cleared` payment exists for the claim amount. (These leave the AP queue — already settled.)148- **PAYABLE / eligible-to-remain**: claim `status == approved` AND a matched OPEN bill exists149 (`status` in {`approved`,`scheduled`}) that is not void/mismatched. Open balance remains.150- **BLOCKED / not-ready** (needs owner cleanup or AP-link remediation) when ANY of:151 - claim `status` is not `approved` and not `paid` (i.e. `submitted`/`needs_receipt`/`rejected`)152 → "block_unapproved_claim";153 - no matched bill exists (no bill linked, OR all linked bills are void, OR all are amount/vendor154 mismatches, OR only drafts);155 - a linked bill is `void` → "ignore_void_bill";156 - a linked bill has amount or vendor mismatch with the claim → "exclude_amount_or_vendor_mismatch".157- CRM-required / owner-cleanup = the set of blocked claims (case-owner must fix the expense case or158 AP link before AP release).159160### 4.4 Open AP balance computation161For each claim: `ap_balance = matched_bill.amount − Σ(cleared payment amounts on that bill)`.162- Only `cleared` payments count. `processing` and `scheduled` payments do NOT reduce the balance.163- No matched bill, or claim not approved → balance `0.0`.164- Matched bill fully paid with cleared payment for the full amount → balance `0.0`.165- Matched open bill with an in-flight (`processing`/`scheduled`) payment → balance stays = bill amount166 (the payment has not cleared), AND the claim is still eligible/payable with correction167 `mark_in_flight_payment`.168- `ap_open_balance_total` (close-review variant) = sum of open balances over **payable** claims only169 (not paid, not blocked).170171### 4.5 Stale-snapshot correction codes (when a circulated snapshot must be reconciled to live API)172Map each candidate claim to exactly one of these enum values:173- `current_snapshot_ok` — snapshot agrees with current API; no correction needed.174- `mark_in_flight_payment` — snapshot shows no/scheduled payment but live API has a payment in flight175 (`processing` or `scheduled`); claim still eligible, balance stays open.176- `replace_with_matched_paid_bill` — snapshot referenced the wrong bill; live API has a matched PAID177 bill (with cleared payment) that should replace it. Claim is settled/eligible.178- `exclude_amount_or_vendor_mismatch` — live bill amount or vendor does not match the claim; exclude it.179- `ignore_void_bill` — live bill is `void`; ignore it.180- `block_unapproved_claim` — claim is not approved (`status` not in {approved,paid}) in live API; block.181182### 4.6 Batch status enum183**close-review variant** (`ready_to_close` / `open_payables` / `blocked`):184- `blocked` if ANY batch item is blocked;185- else `open_payables` if valid unpaid AP reimbursement bills remain (payable claims exist);186- else `ready_to_close`.187188**stale-snapshot refresh variant** (`ready_to_send` / `needs_ap_refresh` / `blocked`):189- `ready_to_send` if every claim is eligible and every correction is `current_snapshot_ok`;190- `needs_ap_refresh` if at least one stale correction is required (snapshot diverges from API) but at191 least one claim is eligible after refresh;192- `blocked` if no claim can be released (all not-ready/blocked).193194### 4.7 close_log_required195- `required: true` when the batch is not `ready_to_send` (i.e. `needs_ap_refresh` or `blocked`) and a196 relevant AP-area close log exists for the period(s) of the corrected bill/payment activity.197- Identify candidate logs via `GET /api/close/logs?area=AP&period={YYYY-MM}` and prefer entries whose198 `message` is `Manual journal entry posted` (the AP manual journal that documents the adjustment),199 in the period overlapped by the matched bills of the eligible/refreshed claims. Sort `ids` ascending.200- `required: false` (and empty `ids`) when the batch is `ready_to_send`.201- Heuristic derived from limited examples: when the eligible claims' matched bills fall in a single202 month, reference that month's AP manual-journal close log. Examine AP-area close logs for the203 affected period(s) rather than guessing.204205## 5. Task type B — Vendor onboarding finance-risk release (KYC)206207Covers "onboarding release call" tasks. Output schema uses per-business208`decision` ∈ {`approve`,`awaiting_information`,`escalate`} plus hard-stop flags and UBO counts.209210### 5.1 Data-gathering SOP2111. For each `business_id` in the batch: `GET /api/compliance/objects?business_id={id}` → KYC record.2122. Get the linked vendor: `GET /api/vendors?vendor_id={compliance.vendor_id}` → vendor record.2133. Compute hard-stop flags, UBO count, then decision.214215### 5.2 Hard-stop flag generation (enum, output sorted ALPHABETICALLY by value; empty list if none)216- `confirmed_pep` — `pep_status == "confirmed_pep"`. (`possible_pep` and `not_run` do NOT set this.)217- `sanctions_confirmed` — `sanctions_check_status == "confirmed_match"`.218- `screening_not_run` — `sanctions_check_status == "not_run"` OR `pep_status == "not_run"`.219- `bank_closed` — `bank_account_status == "closed"`.220- `bank_name_mismatch` — `bank_account_status == "name_mismatch"`.221- `shell_company_suspected` — `shell_company_suspected == true`.222- `vendor_on_hold` — vendor record `status == "on_hold"` (from `/api/vendors`, NOT the compliance object).223- `missing_required_documents` — `missing_fields` is non-empty.224- `expired_license` — `"license"` is NOT in `missing_fields` AND year-month of `license_expiry` is225 strictly before the as_of year-month. (Same-month expiry is NOT expired. When the license doc itself226 is missing, `missing_fields` contains `license`; do NOT also raise `expired_license` — the missing227 doc supersedes the stale expiry date.)228229`bank_account_status == "not_verified"` does not map to any specific hard-stop flag (it is not230`closed` or `name_mismatch`).231232### 5.3 reportable_ubo_counts233Count of **distinct UBO names** in `ubo_list` that have at least one `ownership_pct >= 25`234(reporting threshold, inclusive). De-duplicate by name (a name appearing in multiple entries counts235once if any of its `ownership_pct` entries is >= 25). Output integer >= 0.236237### 5.4 Decision logic (onboarding)238- `approve` — no hard-stop flags at all.239- `awaiting_information` — hard-stop flags exist but are ALL "remediable gap" type:240 {`missing_required_documents`, `screening_not_run`, `expired_license`} (vendor can cure by241 submitting docs / renewing license / running screening).242- `escalate` — ANY hard-stop flag is a "severe/definitive" type: {`confirmed_pep`,243 `sanctions_confirmed`, `bank_closed`, `bank_name_mismatch`, `shell_company_suspected`,244 `vendor_on_hold`}.245- `follow_up_business_ids` = all businesses whose decision is NOT `approve` (i.e. `escalate` or246 `awaiting_information`), ascending.247- `overall_release_ready` = `true` only if EVERY business is `approve`; otherwise `false`.248- Decisions must be derived from current compliance+vendor evidence, NOT copied from the source249 `review_status` field.250251## 6. Task type C — Prepaid amortization close & GL reconciliation252253Covers "prepaid close check" tasks for a scoped set of prepaid invoice IDs and a close period.254255### 6.1 Data-gathering SOP2561. Read the scope payload: `entity`, `close_period` (YYYY-MM), `accounts[]`,257 `selected_prepaid_invoice_ids[]`, `variance_threshold_abs`.2582. For each invoice: `GET /api/prepaids/invoices?prepaid_invoice_id={id}`.2593. For each account: `GET /api/prepaids/gl-balances?account={acct}&period={close_period}` (or260 `/gl/balances`). Take `ending_balance` as the GL balance; also capture `account_name`.261262### 6.2 Amortization computation (straight-line)263Let close period = `close_period` (e.g. 2025-03 → close month index 3 of 2025). For each invoice:264- `monthly_amortization` is given in the record (authoritative per-month figure; do NOT recompute as265 original/term).266- **Period amortization** (e.g. march_amortization) = `monthly_amortization` if the invoice's service267 is active during the close month (service_start month <= close month <= service_end month), else 0.268 A mid-month service_start (e.g. 2025-03-15) still earns the FULL monthly amortization for that month.269- **Cumulative amortization through the close period** = `monthly_amortization` × (number of months270 from service_start's month through min(close_month, service_end's month), inclusive). Concretely:271 months = max(0, (close_year − start_year)*12 + (close_month − start_month) + 1), capped so the count272 never extends past service_end's month.273- **Ending balance** = `original_amount − cumulative_amortization_through_close`. May be a small274 non-zero residual (e.g. 0.01) due to per-month rounding vs original amount — that residual is correct.275- All amounts to 2 decimals.276277### 6.3 Per-invoice flags278- `default_missing_term_flag` = `true` iff `"missing_contract_dates"` ∈ `data_quality_flags`279 (the contract term dates are missing/unreliable — the schedule may be defaulted).280- `exception_flag` = `true` iff `data_quality_flags` is non-empty (ANY flag, including281 `rounded_amount`, `missing_contract_dates`, `manual_override`, `duplicate_invoice_number`).282283### 6.4 Account rollup (per scoped account)284Sum across the account's scoped invoices (2 decimals, round each component consistently):285- `selected_invoice_count`, `original_amount_total`, `{period}_amortization_total`,286 `cumulative_amortization_through_{period}`, `schedule_ending_balance` (sum of invoice ending balances).287- `gl_ending_balance` = GL `ending_balance` for that account+period.288- `variance_amount` = `schedule_ending_balance − gl_ending_balance` (**signed**: positive ⇒ schedule289 above GL, i.e. GL over-amortized / under-stated balance; negative ⇒ schedule below GL, i.e. GL290 under-amortized / has unrecorded additions). This signed direction is the "close-impact direction".291- `variance_flag` = `true` iff `abs(variance_amount) > variance_threshold_abs` (scope payload gives292 threshold, e.g. 100.0).293- `has_default_missing_term_flag` = `true` if ANY scoped invoice in the account has294 `default_missing_term_flag == true`.295- `account_status` ∈ {`reconciled`,`variance_review`,`requires_reconciliation`}:296 - `requires_reconciliation` when `variance_flag == true` (GL and schedule disagree beyond threshold);297 - `variance_review` when `variance_flag == false` but `has_default_missing_term_flag == true`298 (terms questionable even though the balance ties — re-check the term);299 - `reconciled` when `variance_flag == false` and `has_default_missing_term_flag == false`.300301### 6.5 Output lists302- `invoice_results` and `selected_invoice_ids`: keep the **same order as the input scope file**303 (`prepaid_close_scope.json`), NOT sorted.304- `default_missing_term_invoice_ids`: scoped invoices with `default_missing_term_flag == true`,305 sorted **ascending** by invoice id.306- `exception_invoice_ids`: scoped invoices with `exception_flag == true`, sorted **ascending** by307 invoice id. Exception priority (for narrative, not ordering): `missing_contract_dates`308 (default/missing term) is the highest-priority data-quality concern; `rounded_amount` is minor.309- `period` = close_period (YYYY-MM); `entity` = scope entity.310311## 7. Task type D — AP payment release after vendor account-change (release/hold/escalate)312313Covers "payment release risk review" tasks after vendor account-change events. Output schema uses314per-business `decision` ∈ {`release`,`hold`,`escalate`} plus flag lists.315316### 7.1 Data-gathering SOP3171. Read the batch payload: `target_business_ids[]`, `review_date` (as_of_date, YYYY-MM-DD),318 `account_change_events[]` (ticket_id, business_id, vendor_id, change_type, requested_bank_last4,319 requested_release_amount_usd, priority).3202. For each business: `GET /api/compliance/objects?business_id={id}` and321 `GET /api/vendors?vendor_id={id's vendor}` (use the compliance `vendor_id`, or the ticket's `vendor_id`).3223. Compute the flag lists, then the decision.323324### 7.2 Flag lists (each sorted ascending by business_id)325- `bank_mismatch_ids` — businesses where `compliance.bank_account_status == "name_mismatch"`.326 (Only name_mismatch; `closed` is NOT included here — it is a separate hold reason.)327- `invalid_tax_ids` — businesses where `compliance.tax_id != vendor.tax_id` (the compliance-recorded328 tax id disagrees with the vendor master tax id). Both a format-invalid placeholder329 (e.g. `TIN999999`) and a letter-containing id (`TIN12X899`) manifest as a mismatch vs the vendor330 record; the reliable test is the cross-record mismatch.331- `expired_license_ids` — businesses where year-month of `license_expiry` is strictly before the332 as_of year-month (same-month is NOT expired). Use `as_of_date` / `review_date` as the comparison date.333- `risk_score_override_flags` — businesses where `compliance.risk_score >= 70`.334- `review_queue_ids` — ALL businesses whose decision is NOT `release` (i.e. `hold` or `escalate`),335 ascending. (Release decisions are not queued for review.)336337### 7.3 Decision logic (payment release) — DIFFERENT from onboarding338- `release` — no blocking issues at all (bank verified, tax matches, license current, sanctions clear,339 pep not confirmed/not-run-free, risk_score < 70, no missing docs).340- `escalate` — a **severe identity/legal-fraud** issue: `invalid_tax_ids` contains the business.341 (By analogy, `confirmed_pep`/`sanctions_confirmed`/`shell_company_suspected` would also escalate;342 these were not present in the reference data — treat them as escalate triggers.)343- `hold` — a **remediable/operational** blocking issue and NO escalate trigger: bank `name_mismatch`344 or `closed`, `expired_license`, `screening_not_run` (sanctions/pep `not_run`), `missing_fields`345 non-empty, or `risk_score >= 70`. The payment cannot be released until the vendor remediates; it is346 queued for AP/compliance review.347348KEY CONTRAST with onboarding (type B): in onboarding, `bank_closed`/`bank_name_mismatch`/`vendor_on_hold`349are **escalate** triggers; in payment release they are **hold** triggers (operational). And in payment350release, `expired_license` and `screening_not_run` are **hold**, not escalate. Only identity-fraud351issues (invalid tax id, confirmed pep/sanctions/shell) escalate a payment release.352353## 8. Common misjudgments and exclusion rules354355- **"Use USD cents" distractor**:Templates say precision 2 USD → output dollars-with-cents356 (1842.36). Do not emit integer cents (184236).357- **Stale-snapshot conflicts**:Never trust the circulated snapshot over the live API. The snapshot is358 only mentioned to compute the "correction" code. A snapshot `status=scheduled` with a live `paid`359 matched bill is `replace_with_matched_paid_bill`, not `current_snapshot_ok`.360- **Paid vs payable**:`processing`/`scheduled` payments do NOT settle a bill. Only `cleared` payments361 reduce the open balance. A bill with an in-flight payment is still payable/eligible with an open362 balance and correction `mark_in_flight_payment`.363- **Claim-vs-AP alignment**:A bill linked by `claim_id` is only "matched" if amount AND vendor agree364 with the claim. Wrong-amount/wrong-vendor linked bills are mismatches (`exclude_amount_or_vendor_mismatch`),365 not matched bills. Void linked bills are ignored (`ignore_void_bill`).366- **Default/missing-term prepaid flag**:`default_missing_term_flag` is specifically367 `missing_contract_dates`; other data_quality_flags (rounded_amount, manual_override,368 duplicate_invoice_number) set `exception_flag` but NOT `default_missing_term_flag`.369- **Exception priority ranking**: when summarizing, `missing_contract_dates` (default/missing term) is370 the highest-priority exception; `rounded_amount` is the lowest. The output `exception_invoice_ids`371 list is still sorted ascending by id, not by priority.372- **Expired-license boundary**: expiry is judged by year-month strictly before the as_of year-month373 (same month = not expired), and is suppressed when `"license"` is in `missing_fields` (use374 `missing_required_documents` instead). Getting this boundary wrong mis-classifies onboarding and375 payment-release businesses.376- **Signed close-impact direction** (prepaid): `variance_amount = schedule − GL` (signed). A negative377 variance means GL > schedule (under-amortized / unrecorded additions); positive means GL < schedule378 (over-amortized / unrecorded release). Keep the sign.379- **Copy-from-source trap**: for onboarding and payment-release decisions, derive from current380 compliance+vendor evidence — do NOT copy the source `review_status` or a stale decision.381- **Invoice result ordering**: prepaid `invoice_results` and `selected_invoice_ids` keep INPUT scope382 order; only `default_missing_term_invoice_ids` and `exception_invoice_ids` are sorted ascending.383- **Bill status `draft`**: draft bills are not committed; treat as not-a-valid-matched-bill.384- **`bank_account_status == not_verified`**: does not map to any onboarding hard-stop flag (only385 `closed` and `name_mismatch` do).386387## 9. Controlled vocabularies (exact enum values required by answer templates)388389- Reimbursement-AP close `batch_status`: `ready_to_close`, `open_payables`, `blocked`.390- Stale-snapshot refresh `batch_status`: `ready_to_send`, `needs_ap_refresh`, `blocked`.391- Stale-snapshot `corrections` enum: `current_snapshot_ok`, `mark_in_flight_payment`,392 `replace_with_matched_paid_bill`, `exclude_amount_or_vendor_mismatch`, `ignore_void_bill`,393 `block_unapproved_claim`.394- Onboarding `decision`: `approve`, `awaiting_information`, `escalate`.395- Onboarding `hard_stop_flags` enum (alphabetical in output): `bank_closed`, `bank_name_mismatch`,396 `confirmed_pep`, `expired_license`, `missing_required_documents`, `sanctions_confirmed`,397 `screening_not_run`, `shell_company_suspected`, `vendor_on_hold`.398- Payment-release `decision`: `release`, `hold`, `escalate`.399- Prepaid `account_status`: `reconciled`, `variance_review`, `requires_reconciliation`.400- Prepaid `data_quality_flags`: `rounded_amount`, `missing_contract_dates`, `manual_override`,401 `duplicate_invoice_number`.402- Claim status: `submitted`, `needs_receipt`, `approved`, `paid`, `rejected`.403- AP bill status: `draft`, `approved`, `scheduled`, `paid`, `void`.404- AP payment status: `scheduled`, `processing`, `cleared`.405- Vendor status: `active`, `inactive`, `on_hold`.406- Compliance `bank_account_status`: `verified`, `not_verified`, `name_mismatch`, `closed`.407- Compliance `pep_status`: `none`, `possible_pep`, `confirmed_pep`, `not_run`.408- Compliance `sanctions_check_status`: `clear`, `possible_match`, `confirmed_match`, `not_run`.409- Close log `area`: `AP`, `Prepaids`, `GL`, `Treasury`, `Expense`, `Compliance`.410- Close log `status`: `open`, `ready_for_review`, `closed`, `blocked`.411412## 10. Concrete solver SOP (recommended order for any task in this group)4134141. Read the task prompt and the local payload (`answer_template.json` is the source of truth for the415 required output shape, field names, ordering, and enums; any input batch file lists candidates).4162. `GET {base}/endpoints` to confirm paths; `GET {base}/api/health` to confirm liveness.4173. Identify the task type (A: reimbursement-AP close / stale AP refresh; B: vendor onboarding KYC;418 C: prepaid amortization close; D: payment release after account-change) from the prompt + template.4194. Gather live API data per the type-specific SOP in sections 4.1 / 5.1 / 6.1 / 7.1. Paginate to get420 all rows; re-query by exact id rather than assuming.4215. Apply the classification/decision rules (sections 4.2–4.6, 5.2–5.4, 6.2–6.5, 7.2–7.3). For422 payment-run ordering needs (not always an output), rank open AP bills by `due_date` ascending then423 `bill_id` ascending.4246. Double-check the cross-cutting rules (section 3) and the misjudgments (section 8), especially:425 currency precision (dollars.cents), cleared-vs-in-flight payments, matched-bill amount+vendor426 test, expired-license month boundary + missing-license suppression, signed prepaid variance, and427 the onboarding-vs-payment-release escalate/hold contrast.4287. Emit ONE JSON object matching the template exactly: correct top-level keys, correct ordering, exact429 enum spellings, 2-decimal USD numbers. No narrative text outside the JSON.430431### Quick decision tables432433**Onboarding (type B) — hard-stop → decision**434| Severe (→ escalate) | Remediable (→ awaiting_information if alone) |435|---|---|436| confirmed_pep, sanctions_confirmed, bank_closed, bank_name_mismatch, shell_company_suspected, vendor_on_hold | missing_required_documents, screening_not_run, expired_license |437(approve = no hard stops at all)438439**Payment release (type D) — issue → decision**440| Severe (→ escalate) | Remediable (→ hold) |441|---|---|442| invalid_tax_id (compliance.tax_id ≠ vendor.tax_id); confirmed_pep; sanctions_confirmed; shell_company_suspected | bank name_mismatch/closed; expired_license; screening_not_run; missing_fields non-empty; risk_score ≥ 70 |443(release = no blocking issues)444445**Reimbursement-AP (type A) — claim classification**446| Class | Condition |447|---|---|448| paid/settled | claim status=paid AND matched bill paid AND cleared payment = claim amount |449| payable/eligible | claim status=approved AND matched open (approved/scheduled) bill exists |450| blocked/not-ready | claim not approved, OR no matched bill (void/mismatch/none/draft-only) |