As an ERP, invoicing platform, or trade-credit ledger provider, your system carries real-time accounts receivable and payable history. This playbook helps your team create secure lookup APIs and write adapters conformant with FinHero's B2B underwriting standards.
Expose AR/AP aging buckets, debtor concentrations, and cash conversion cycles to support B2B loan assessments.
Implement the Javascript adapter querying the accounting ledger API and mapping outputs to schema fields.
Validate the adapter mapping scripts offline using schema checkers and sandbox test files.
The B2B scoring model utilizes Days Sales Outstanding (DSO), debtor concentration top-5 ratios, and cash conversion cycles. This provides a direct assessment of supplier payment hygiene.
How business identities map to trade ledger metrics. The process runs locally within the secure FinSys execution stack.
Your API doesn't need to match this structure directly. The adapter maps ledger outputs into these 10 canonical fields.
| Field Key | Data Type | Constraint / Range | Credit Scoring Interpretation |
|---|---|---|---|
| arDaysSalesOutstanding | number | days [0 - 400] | Days Sales Outstanding โ average days taken to collect receivables. Lower indicates efficiency. |
| apDaysPayableOutstanding | number | days [0 - 400] | Days Payable Outstanding โ average days taken to pay suppliers. Very high suggests cash flow strain. |
| arTotalOutstanding | number | MYR [0.0 - 1e9] | Total accounts receivable balance outstanding. Shows the size of the debtor book. |
| arCurrentRatio | number | ratio [0.0 - 1.0] | Fraction of the receivables book that is current. Closer to 1.0 is healthier. |
| arOverdue90PlusRatio | number | ratio [0.0 - 1.0] | Fraction of receivables aged 90+ days overdue. Flag for bad-debt risk. |
| debtorConcentrationTop5Ratio | number | ratio [0.0 - 1.0] | Concentration share of top 5 debtors. Above 0.60 signals customer risk. |
| tradeReferenceDefaults12m | number | count [0 - 1000] | Supplier-reported payment defaults or returned payments. Direct distress indicator. |
| accountingRevenue12m | number | MYR [0.0 - 1e9] | Trailing 12-month turnover per general ledger. Cross-referenced against bank statements. |
| grossMarginPct | number | ratio [0.0 - 1.0] | Gross margin (gross profit / revenue). Predicts profitability quality. |
| cashConversionCycleDays | number | days [-200 - 600] | Cash Conversion Cycle. Lower is more capital efficient. |
Select a B2B applicant persona, or customize the values in the control panel to see live mapping calculations for trade ledger outputs.
Complete template files for the Trade Credit mapping adapter. Implement `fetch` and `extract` methods conformant to the `trade-credit` category schema.
// manifest.json: Declares entrypoint & outputs metadata
{
"manifestVersion": 1,
"id": "trade-credit-adapter-v1",
"displayName": "B2B Ledger Ingestion",
"category": "trade-credit",
"version": 1,
"produces": [
"arDaysSalesOutstanding",
"apDaysPayableOutstanding",
"arTotalOutstanding",
"arCurrentRatio",
"arOverdue90PlusRatio",
"debtorConcentrationTop5Ratio",
"tradeReferenceDefaults12m",
"accountingRevenue12m",
"grossMarginPct",
"cashConversionCycleDays"
],
"requiredIdentityFields": [],
"implementation": {
"type": "typescript",
"entryPoint": "extract.mjs"
}
}
// extract.mjs: Queries API, transforms metrics to canonical fields
const API_URL = process.env.FAKE_TRADE_CREDIT_API_URL ?? "http://fake-trade-credit-api:4300";
const API_KEY = process.env.FAKE_TRADE_CREDIT_API_KEY ?? "demo-key";
const round4 = (n) => Number(n.toFixed(4));
const ratio = (num, den) => (den > 0 ? round4(num / den) : 0);
const adapter = {
id: "trade-credit-adapter-v1",
category: "trade-credit",
version: 1,
produces: [
"arDaysSalesOutstanding",
"apDaysPayableOutstanding",
"arTotalOutstanding",
"arCurrentRatio",
"arOverdue90PlusRatio",
"debtorConcentrationTop5Ratio",
"tradeReferenceDefaults12m",
"accountingRevenue12m",
"grossMarginPct",
"cashConversionCycleDays"
],
async fetch(identity) {
if (!identity?.ic || !identity?.fullName) {
throw new Error("fake-trade-credit-v1: identity.ic and identity.fullName are required");
}
const res = await fetch(`${API_URL}/v1/accounts/lookup`, {
method: "POST",
headers: { "content-type": "application/json", "x-api-key": API_KEY },
body: JSON.stringify({ ic: identity.ic, fullName: identity.fullName })
});
if (!res.ok) {
throw new Error(`fake-trade-credit-v1 fetch failed: HTTP ${res.status}`);
}
return res.json();
},
async extract(raw) {
const ar = raw.accountsReceivable ?? {};
const aging = ar.aging ?? {};
const total = Number(ar.totalMyr ?? 0);
const pnl = raw.incomeStatement12m ?? {};
const revenue = Number(pnl.revenueMyr ?? 0);
const cogs = Number(pnl.cogsMyr ?? 0);
return [{
instanceKey: "default",
observedAt: new Date().toISOString(),
values: {
arDaysSalesOutstanding: Number(raw.metrics?.daysSalesOutstanding ?? 0),
apDaysPayableOutstanding: Number(raw.accountsPayable?.avgDaysOutstanding ?? 0),
arTotalOutstanding: total,
arCurrentRatio: ratio(Number(aging.current ?? 0), total),
arOverdue90PlusRatio: ratio(Number(aging.d90plus ?? 0), total),
debtorConcentrationTop5Ratio: round4(Number(ar.top5DebtorShareRatio ?? 0)),
tradeReferenceDefaults12m: Number(raw.tradeReferences?.defaults12m ?? 0),
accountingRevenue12m: revenue,
grossMarginPct: ratio(revenue - cogs, revenue),
cashConversionCycleDays: Number(raw.metrics?.cashConversionCycleDays ?? 0)
}
}];
}
};
export default adapter;
// test.mjs: Offline verification tests
import { runFixtures } from "@finsys/adapter-toolkit";
import adapter from "./extract.mjs";
const fixtures = [
{
name: "aiman-hassan-ledger",
identity: { ihsId: 1, ic: "850101015432", fullName: "Aiman bin Hassan" },
expected: [
{
instanceKey: "default",
values: {
arDaysSalesOutstanding: 28,
accountingRevenue12m: 2400000
}
}
]
}
];
const results = await runFixtures(adapter, fixtures);
console.log(results);
if (results.some(r => !r.ok)) {
console.error("โ Trade Credit tests failed");
process.exit(1);
} else {
console.log("โ
Trade Credit validation tests passed!");
}
Validate manifest schemas and test mapping logic locally using the CLI or docker configurations.