lodgeit labs

Fano classifier available

A trial-balance line classifier for the LodgeiT chart of accounts. Send ledger lines with an entity type and a section; get back an SBRM code with a status of accepted_fact or draft_fact. Rules run first, the model second — and a draft is the engine refusing to guess.

Unlike the deterministic calculators, Fano has a model in the path. It is not there to be trusted blindly: a small language-free classifier proposes a code, a layer of hand-authored LodgeiT word rules and structural firewalls check that proposal against the section and entity you declared, and anything the two do not agree on is returned as a draft for a human to confirm in one click — never silently accepted. The classifier is diagnostic; the rules and the refusals are the contract.

Where it runs

https://fano-engine-79859053141.australia-southeast1.run.app
This host will change. A stable custom domain is coming; until then you are calling the underlying Cloud Run URL directly. Put the base URL in configuration rather than in your code, and re-check this page before assuming it still resolves.

Endpoint

POST /ingest/trial_balance
GET  /v1/info
GET  /openapi.json

Post a whole trial balance in one call: an entity_structure for the payload and a list of lines. Each line carries its description, the source_topology (the ledger section you are filing it under), and an upstream prediction (predicted_code, confidence) plus an amount. The declared entity and section are inputs, not decoration — the model is masked to leaves within the section you declared, and the firewall checks the returned code against that section and entity. GET /v1/info returns the version, the access mode, the limits, and the reason codes; GET /openapi.json is the full schema.

Input

entity_structure
One of company, trust, partnership, sole_trader, super_fund. Declared once for the whole payload.
lines[].description
The ledger line description, as it reads on the trial balance.
lines[].source_topology
The section you are filing the line under: current_assets, non_current_assets, current_liabilities, non_current_liabilities, equity, revenue, expenses. The model is masked to leaves within it.
lines[].predicted_code
The upstream / prior code for the line as sbrm_<n> (use sbrm_0000 if you have none). Echoed back as operator_hint_predicted_code; it does not steer the classification.
lines[].confidence
The upstream confidence, 0–1. Echoed as operator_hint_confidence.
lines[].amount
Signed amount. The payload must net to zero (assets/expenses positive, liabilities/equity/revenue negative) or the gateway returns 400 on the equilibrium check.

Try two calls

One sole-trader line that accepts, one that drafts — both from a single balanced payload. These are live responses from the service above.

curl -s -X POST \
  "https://fano-engine-79859053141.australia-southeast1.run.app/ingest/trial_balance" \
  -H "Content-Type: application/json" \
  -d '{
    "entity_structure": "sole_trader",
    "lines": [
      { "description": "Business Cheque Account", "predicted_code": "sbrm_0000",
        "source_topology": "current_assets", "confidence": 0.5, "amount": 5000.0 },
      { "description": "Owner Capital", "predicted_code": "sbrm_3100",
        "source_topology": "equity", "confidence": 0.5, "amount": -5000.0 }
    ]
  }'
{
  "status": "success",
  "equilibrium_valid": true,
  "results": [
    {
      "description": "Business Cheque Account",
      "predicted_code": "sbrm_1137",
      "confidence": 1.0,
      "cascade_topology": "current_assets",
      "operator_hint_predicted_code": "sbrm_0000",
      "operator_hint_source_topology": "current_assets",
      "operator_hint_confidence": 0.5,
      "fano_status": "accepted_fact",
      "quarantine_reason": "keyword_rule:sbrm_1137"
    },
    {
      "description": "Owner Capital",
      "predicted_code": "sbrm_3130",
      "confidence": "0.97",
      "cascade_topology": "equity",
      "operator_hint_predicted_code": "sbrm_3100",
      "operator_hint_source_topology": "equity",
      "operator_hint_confidence": 0.5,
      "fano_status": "draft_fact",
      "quarantine_reason": "rule_model_disagreement (rule=sbrm_3130, model=sbrm_3121)"
    }
  ]
}

Reading it back: Business Cheque Account is an accepted_fact — a LodgeiT word rule owned the line and returned sbrm_1137 (Cash > Bank Accounts), reason keyword_rule:sbrm_1137; you can post it. Owner Capital is a draft_fact — the rule leaf (sbrm_3130, Sole Trader – Capital Introduced) and the model leaf (sbrm_3121) disagree, so the engine shows the rule candidate, names the disagreement in quarantine_reason, and hands the line to an operator rather than guess between two plausible equity leaves. Both outcomes are correct behaviour; the draft is the point.

Reason codes

Every line carries a fano_status of accepted_fact or draft_fact, and a quarantine_reason that names why. The reasons are a fixed set — an agent can branch on the prefix:

keyword_rule:<code>
A LodgeiT word rule owned the line and returned this leaf. Accepted.
twin_leaf (rule=<code>)
The rule leaf is the Direct/Indirect twin of the model leaf; the model leaf is accepted.
rule_model_disagreement (rule=<code>, model=<code>)
A weak rule hit disagrees with the model; the rule leaf is shown and the line is drafted.
non_leaf_prediction (…)
The model landed on a non-postable structural node; the rule leaf is taken and the line is drafted.
Sub-floor model confidence (<x>)
The model's top code is below the 0.50 floor; the line is drafted for review.

What it will not do

Limits

Open access — no key, no signup — bounded by request guards rather than authentication:

GuardValueOn breach
Lines per request500413 — payload too large; split the trial balance.
Body size512 KB413 — payload too large.
Rate60 / minute / IP429 — back off and retry after the window.
Malformed request—422 — the body names the field. Do not retry unchanged.
Unbalanced payloadnet ≠ 0400 — the lines do not net to zero.

The service is capped at three instances, so sustained heavy load will queue. A 413 means the request was too big to accept; a 429 means you were too fast. Both are the engine holding a boundary, not a fault on your side.

Integration kit

An Apache-2.0 kit with the pinned OpenAPI snapshot, the SBRM lexicon, canonical fixtures, and CI that fails when the live contract drifts from the snapshot:

github.com/lodgeit-labs/fano-classifier-integration-kit