Skip to content

Methodology

How the optimizer makes decisions you can audit.

Before you trust an AI with a client’s chart of accounts, you should know exactly what it checks, what it doesn’t, and where judgment gives way to rules. This page documents the ranges, criteria, and minimums the optimizer enforces — drawn live from the same source files that drive scoring in production.

Architecture

Two systems working together.

The optimizer pairs deterministic checks with AI judgment. Both run on every chart, and a deterministic enforcer scores the final result against the same rubric the optimizer was asked to satisfy.

Deterministic

Rules with a single right answer

GAAP (Generally Accepted Accounting Principles) range checks, account renumbering, hierarchy validation, fixed-asset pairing, duplicate detection. Pure functions in the codebase. Same input always produces the same output.

  • Numbering coverage and uniqueness
  • Range compliance per account type
  • Parent / child type and number ordering
  • QBO import-blocking characters and length limits

AI judgment

Calls that need context

Account classification when names are ambiguous, naming clean-up, organizational grouping, and industry fit. The AI works inside the deterministic rules — it can never propose a change that violates them, because the enforcer rejects it and re-prompts.

  • Classifying ambiguous account names
  • Polishing names against firm conventions
  • Industry fit and depth-of-detail judgment
  • Suggesting subaccount structure

Numbering

The 16 GAAP ranges, in full.

Every numbered account must fall inside the canonical range for its type. The optimizer renumbers accounts that drift, preserves anchors that are already correct, and reserves the +5 offset on Fixed Assets for accumulated depreciation.

RangeAccount typeDescriptionExamples
1000–1099BankCash and bank accountsChecking Account, Savings Account, Petty Cash
1200–1299Accounts ReceivableMoney owed by customersAccounts Receivable, Allowance for Doubtful Accounts
1300–1399Other Current AssetShort-term assets (excluding AR and Inventory)Prepaid Expenses, Short-term Investments, Deposits
1400–1499Inventory AssetGoods for sale or productionInventory Asset, Raw Materials, Finished Goods
1500–1799Fixed AssetLong-term tangible assets (Accumulated Depreciation uses +5 pairing: 1505, 1515, etc.)Equipment, Vehicles, Building
1800–1999Other AssetLong-term non-current assetsLong-term Investments, Intangible Assets, Goodwill
2000–2099Accounts PayableMoney owed to vendorsAccounts Payable, Trade Payables
2100–2199Credit CardCredit card liabilitiesBusiness Credit Card, Corporate Card
2200–2499Other Current LiabilityShort-term obligationsAccrued Expenses, Payroll Liabilities, Sales Tax Payable
2500–2999Long Term LiabilityLong-term debts and obligationsLong Term Debt, Equipment Loans, Mortgage Payable
3000–3999EquityOwner equity and retained earningsOwner Equity, Retained Earnings, Owner Draws
4000–4699IncomePrimary operating revenueSales Income, Service Revenue, Product Sales
4700–4999Other IncomeNon-operating income and gainsInterest Earned, Dividend Income, Gain on Sale of Assets
5000–5999Cost of Goods SoldDirect costs of producing goods/servicesCost of Goods Sold, Direct Materials, Direct Labor
6000–6999ExpenseOperating expenses (parents use 100-point: 6000, 6100; children use 10-point: 6010, 6020)Office Expenses, Marketing, Rent
7500–7999Other ExpenseNon-operating expenses and lossesInterest Expense, Loss on Sale of Assets, Bank Fees

Source: services/gaap-ranges.ts — the same module the analyzer, optimizer, and scorer all import.

Scoring

The 32-criterion rubric, grouped by step.

The optimizer runs through six analysis steps and scores against 32 machine-checkable criteria with a combined weight of 195 points. Each criterion has a pure-function validator; the deterministic enforcer normalizes the result to 0–100 and feeds failing criteria back into the optimizer’s next pass.

Step 1 · Foundational classification

5 criteria · weight 50

  • Valid QBO Account Type

    classification.typeValid

    weight 17

    Every account must carry a non-empty accountType that matches one of the 15 QBO-mandated types (singular aliases accepted).

    Optimizer guidance

    Every account must have an accountType that is one of the 15 official QBO types: Bank, Accounts Receivable, Other Current Assets, Fixed Assets, Other Assets, Accounts Payable, Credit Card, Other Current Liabilities, Long Term Liabilities, Equity, Income, Other Income, Cost of Goods Sold, Expenses, Other Expense. No empty values, no custom types.

  • Balance Sheet vs Income Statement

    classification.bsVsIs

    weight 8

    An account's type must align with the expected financial statement bucket. e.g., Bank/AR/Fixed Assets → balance sheet; Income/Expenses/COGS → income statement.

    Optimizer guidance

    Never mis-bucket statement placement. Bank, AR, Fixed Assets, Other Assets, Other Current Assets, AP, Credit Card, Liabilities, and Equity belong on the Balance Sheet. Income, Other Income, COGS, Expenses, and Other Expense belong on the Income Statement. A type that cannot be resolved to a canonical QBO type fails this criterion.

  • Detail Type Consistent with Account Type

    classification.detailTypeConsistent

    weight 7

    If detailType is populated, it must belong to the allowed set for the account's accountType (per QBO detail-type taxonomy).

    Optimizer guidance

    When you assign a detailType, it MUST be one of QBO's allowed detail types for that accountType. For example, a Bank account's detailType must be one of Checking, Savings, MoneyMarket, etc. See types/qbo-account-types.ts QBO_DETAIL_TYPES for the complete taxonomy. Missing detailType is not penalized by this criterion — only mismatches.

  • Known Name Patterns Match Expected Type

    classification.knownPatternTypes

    weight 8

    Accounts whose name matches a well-known pattern (e.g., "Cash", "Allowance for Doubtful Accounts", "Accumulated Depreciation - X") must carry the corresponding canonical accountType. Names that do not match any pattern are not penalised.

    Optimizer guidance

    Common account names dictate their type. The reference table below lists every name pattern the scorer checks — pulled directly from lib/account-name-patterns.ts so it is always in sync. Patterns are advisory: only matched-but-mistyped accounts cost points; novel names that match no row are not penalised.

  • Semantic Coherence (Contra / Prepaid / Returns)

    classification.semanticCoherence

    weight 10

    Accounts whose name implies a specific balance-sheet role (contra-asset, contra-revenue, prepaid) must carry the canonical accountType + detailType pair. Catches the dimensions the AI Analysis "Account Categories" step catches but the older rubric missed — preventing the rubric from reporting a high score while obvious semantic issues remain.

    Optimizer guidance

    When an account name clearly indicates a contra-account, a prepaid, or returns/allowances, its accountType AND detailType must match the canonical pairing. Examples: "Prepaid Insurance" → Other Current Assets / Prepaid Expenses (NOT Expense). "Allowance for Doubtful Accounts" → Other Current Assets / Allowance for Bad Debts. "Accumulated Depreciation - X" → Fixed Assets / Accumulated Depreciation. "Sales Returns & Allowances" → Income / Discounts/Refunds Given (NOT Other Income, NOT Expense). Names that do not match any semantic pattern are not penalized.

Step 2 · GAAP-aligned numbering

4 criteria · weight 30

  • Numbering Coverage

    numbering.coverage

    weight 12

    Every active account must have a non-empty account number.

    Optimizer guidance

    Ensure every active account has a GAAP-compliant account number assigned. Coverage is scored as (accounts_with_numbers / total_active_accounts) × 12. Missing even one number costs points.

  • GAAP Range Compliance

    numbering.gaapRange

    weight 9

    Every numbered account must fall within the canonical GAAP range for its account type.

    Optimizer guidance

    Every numbered account must fall in the GAAP range for its type. The exact ranges are listed in the reference table below and come directly from services/gaap-ranges.ts — they are authoritative. An account tagged with the WRONG TYPE will fail this check even if the number is "reasonable", so classify types correctly first, then assign numbers inside the type's range.

  • Numbering Uniqueness

    numbering.uniqueness

    weight 5

    No two accounts may share the same account number.

    Optimizer guidance

    Every account number must be unique across the entire chart. Duplicates cost 1 point each. The deterministic numberer prevents this — never override its output.

  • Systematic Numbering

    numbering.systematic

    weight 4

    Numbering should follow consistent increments within each type. Expense parents at 100-point boundaries (6000, 6100, 6200); children at 10-point increments within (6010, 6020, 6030). Other types use 10-point increments throughout.

    Optimizer guidance

    Use consistent numbering patterns. For Expense accounts: parent categories at 100-point boundaries (6000, 6100, 6200), children at 10-point increments (6010, 6020, 6030). For all other types, use 10-point increments from the range start (e.g., A/P: 2000, 2010, 2020). Avoid gaps larger than 100 within the same parent group. The deterministic numberer handles this — do not manually assign numbers.

Step 3 · Fixed assets & depreciation pairing

4 criteria · weight 25

  • Fixed Asset / Depreciation Pairing

    fixedAsset.pairing

    weight 10

    Every depreciable Fixed Asset (named "X - Original Cost") must have a matching "X - Accumulated Depreciation" child, and vice versa.

    Optimizer guidance

    For every Fixed Asset base "X - Original Cost" there must be a matching "X - Accumulated Depreciation" child (parentId pointing to the base). Orphaned depreciation accounts or bases without their depreciation counterpart fail this check. Use the canonical suffix form — never the prefix form ("Accumulated Depreciation - X").

  • Accumulated Depreciation +5 Offset

    fixedAsset.numberOffset

    weight 5

    Accumulated Depreciation accounts should be numbered at parent + 5 (e.g., parent 1510 → accum dep 1515).

    Optimizer guidance

    Each "X - Accumulated Depreciation" account should be numbered at its parent + 5. For example, if "Equipment - Original Cost" is 1510, "Equipment - Accumulated Depreciation" should be 1515.

  • Accumulated Depreciation Contra Sign

    fixedAsset.contraSign

    weight 5

    Accumulated Depreciation accounts must carry a non-positive balance (≤ 0) since they are contra-asset accounts.

    Optimizer guidance

    Every "- Accumulated Depreciation" account must have a balance ≤ 0. A positive balance indicates either a sign error in posted entries or an account misclassified as Accum. Depr. when it is actually a regular asset. Skip this check for accounts with no balance recorded (treat as not-yet-posted).

  • Fixed Asset Naming Convention

    fixedAsset.namingConvention

    weight 5

    Fixed Asset depreciation accounts must use the suffix form "X - Accumulated Depreciation", not the prefix form "Accumulated Depreciation - X". The suffix form is what assignGaapNumbers and the linker rely on.

    Optimizer guidance

    Always name depreciation accounts as "<Asset Name> - Accumulated Depreciation" (suffix form). Never use "Accumulated Depreciation - <Asset Name>" (prefix form). The deterministic numberer at lib/deterministic-gaap-numbering.ts:216 and the asset linker at lib/fixed-asset-linker.ts both detect depreciation accounts by the suffix — the prefix form will be missed entirely.

Step 4 · Hierarchical grouping

6 criteria · weight 30

  • Hierarchy Depth ≤ 3

    hierarchy.depthLimit

    weight 5

    No account should be nested more than 3 levels deep (parentId chain ≤ 3).

    Optimizer guidance

    QBO supports up to 5 levels of nesting but deep trees hurt readability. Keep all accounts within 3 levels of a root. Accounts deeper than 3 levels should be flattened or re-parented.

  • Child accountType Matches Parent

    hierarchy.parentTypeMatch

    weight 5

    A child account's accountType must equal its parent's accountType (QBO requirement).

    Optimizer guidance

    QBO enforces that a child account must share its parent's accountType. A Bank parent can only have Bank children; a Fixed Assets parent can only have Fixed Assets children. Mismatches will be rejected by QBO sync.

  • No Orphan parentId

    hierarchy.noOrphans

    weight 5

    Every account with a parentId must reference an existing account in the chart.

    Optimizer guidance

    Every populated parentId must point to an account that exists in the chart. Orphan references (parentId pointing to a missing/deleted account) will cause QBO sync failures and display bugs.

  • Balanced Fan-Out (≤12 children per parent)

    hierarchy.balancedFanOut

    weight 5

    No parent account should have more than 12 direct children. Wider fan-out becomes hard to scan and usually indicates the parent should be split into sub-categories.

    Optimizer guidance

    When a parent grows beyond ~12 direct children, split it into 2–3 logical sub-groups. Example: instead of "Operating Expenses" with 25 children, create "Operating Expenses → Office (10), Travel (8), Professional Services (7)". The 12-child threshold matches typical scan-list cognitive load.

  • Parent Number < Child Number

    hierarchy.parentChildNumberOrder

    weight 5

    For every numbered child with a numbered parent, the child's number must be greater than the parent's. This is a post-numberer guarantee — assignGaapNumbers() always satisfies this — so any violation indicates numbers were assigned outside the canonical pipeline.

    Optimizer guidance

    Never produce a chart where a child account has a lower number than its parent (e.g., parent 6100 with child 6050). The deterministic numberer at lib/deterministic-gaap-numbering.ts guarantees parent < child by DFS traversal. If a violation appears, the chart was numbered outside the canonical pipeline and must be re-numbered through assignGaapNumbers().

  • Chart Has Some Hierarchical Organization

    hierarchy.someOrganization

    weight 5

    A non-empty chart must show some organizational grouping — either ≥ 20% of non-header accounts linked to a parent, OR ≥ 1 header-type parent per top-level account type. Without this anti-flatness gate, a completely flat chart would score N/A on every other Step 4 criterion and get rewarded with a silently-inflated overall score.

    Optimizer guidance

    A good chart groups related accounts under type headers or parent accounts. Aim for at least 20% of non-header accounts to reference a parentId, or create header-type parents (isParent=true) for each top-level account type (Bank, A/R, Fixed Assets, Expenses, etc.). A completely flat chart with no hierarchy is a red flag — even a minimal "Operating Expenses" parent grouping its children adds clarity at zero cost.

Step 5 · Business optimization

1 criteria · weight 10

  • Required Accounts for Industry

    business.requiredAccountsForIndustry

    weight 10

    For curated industries (Construction, Restaurant, Nonprofit, Ecommerce, Professional Services, IT Services, Healthcare, Legal), the chart must contain accounts matching each industry-essential pattern (e.g., Construction → WIP + Job Costs + Subcontractors). Industries without curated data award full credit.

    Optimizer guidance

    If the business has an industry that maps to a curated list (see lib/industry-required-accounts.ts), ensure the chart contains at least one account matching each required pattern. Example: Construction → "Work in Progress" + "Job Costs" + "Subcontractor Expenses" + "Retainage" + "Materials". Missing any listed requirement flags that account type as absent.

Step 6 · Final validation

12 criteria · weight 50

  • No Empty Parent Accounts

    validation.noEmptyParents

    weight 4

    Every account marked isParent=true must have at least one child account.

    Optimizer guidance

    A parent account with no children is visual noise — flatten it or remove it. If isParent is set but no account has this parentId, it fails this check.

  • No Placeholder Names

    validation.noPlaceholders

    weight 3

    No account name should contain placeholder tokens like "todo", "tbd", "xxx", "temp", "test", or "unnamed".

    Optimizer guidance

    Account names must describe real categories. Never ship names containing "todo", "tbd", "xxx", "temp", "test", "unnamed", or similar placeholder tokens. These indicate incomplete categorization.

  • Non-Empty Statement Buckets

    validation.activeCount

    weight 3

    At least one active account must exist per major statement bucket (balance sheet and income statement).

    Optimizer guidance

    A complete chart has at least some active accounts on both the balance sheet and the income statement. An empty P&L or empty BS indicates the chart is incomplete and cannot produce financial statements.

  • No Duplicate Account Names

    validation.noDuplicateNames

    weight 4

    No two accounts share an identical name. Duplicate-name collisions usually indicate the LLM independently renamed multiple distinct accounts to the same string during a later optimizer step.

    Optimizer guidance

    Each account in the final output must have a unique name. Avoid renaming distinct accounts to identical strings during consolidation steps; if consolidation is intended, merge the accounts into a single row.

  • Cross-Dimensional Consistency

    validation.crossValidationClean

    weight 8

    Number, accountType, parentId, and detailType must be self-consistent. Catches: number outside type range, orphan parentId, parent/child type mismatch, child number ≤ parent number.

    Optimizer guidance

    Every account must satisfy 4 cross-dimensional invariants: (1) number falls in the GAAP range for accountType; (2) parentId resolves to an existing account; (3) child accountType matches parent accountType; (4) child number > parent number. These invariants are automatically reconciled by services/gaap-ranges.ts crossValidateAndReconcile() in the production pipeline — the AI should not produce charts that fail them in the first place.

  • No Blank Account Names

    validation.noBlankNames

    weight 3

    Every active account must have a non-empty name.

    Optimizer guidance

    Every account must have a descriptive name. Accounts with empty, null, or whitespace-only names are data integrity bugs that will break import and reporting.

  • No Circular Parent-Child References

    validation.noCircularRefs

    weight 5

    Following parentId from any account must terminate; a cycle is a runtime bug.

    Optimizer guidance

    Parent-child relationships must form a forest (trees), not a graph with cycles. An account cannot be its own ancestor. Cycles break hierarchy traversal and reporting.

  • No Zero or Negative Account Numbers

    validation.noNonPositiveNumbers

    weight 3

    Any account number that exists must be a positive integer greater than zero.

    Optimizer guidance

    Account numbers must be positive. Zero or negative numbers indicate corrupt data or a typo that will fail QBO validation on import.

  • No Duplicate Account Numbers

    validation.noDuplicateNumbers

    weight 5

    Every numbered active account must have a unique account number within the chart.

    Optimizer guidance

    Account numbers must be unique. Two accounts sharing the same number will collide in QBO and break reporting.

  • Account Names Within QBO Length Limit

    validation.nameLengthWithinLimit

    weight 3

    Account names must be 100 characters or fewer to satisfy QBO import limits.

    Optimizer guidance

    QBO rejects account names longer than 100 characters at import. Keep names concise and specific.

  • No QBO-Breaking Characters in Names

    validation.noSpecialCharacters

    weight 3

    Account names must not contain characters that break QBO import: @ % * " ' < >.

    Optimizer guidance

    Account names should avoid characters that QBO rejects or that break CSV/JSON interchange. Disallowed: @, %, *, straight quotes (") and ('), angle brackets (<, >). Ampersand (&) and hash (#) are allowed by QBO and common in real names (e.g., "Payroll & Benefits"), so we do not flag them.

  • Critical GAAP Accounts Present

    validation.criticalGAAPAccountsPresent

    weight 6

    The chart must include at least one account serving each critical GAAP function: Cash/Bank, Accounts Receivable, Accounts Payable, Equity, Retained Earnings.

    Optimizer guidance

    Every production-ready chart of accounts must include accounts covering these critical GAAP functions: (1) Cash or Bank; (2) Accounts Receivable; (3) Accounts Payable; (4) Equity; (5) Retained Earnings. Matching is by canonical account type first, then by common name patterns — so a "Checking Account" typed as Bank covers Cash, and an "Owner Capital" account covers Equity.

Source: services/scoring-rubric.ts. Two judgment criteria (industry fit, currently reserved) are not shown — they are AI-graded and excluded from the deterministic score.

Industry minimums

Eight industries, with explicit required-account lists.

One rubric criterion checks for industry-specific accounts that are indispensable, not just typical. Below are the eight industries we’ve curated. Charts whose business context falls outside this list are not penalised — the criterion is skipped and full credit awarded, because we won’t pretend to know what we haven’t curated.

Construction

6 required

  • Work in Progress (WIP)
  • Job Costs (COGS)
  • Subcontractor Expenses
  • Retainage
  • Costs in Excess of Billings (POC)
  • Construction Materials

Restaurant

4 required

  • Food Cost / Inventory
  • Beverage Cost
  • Tips Payable / Tips Received
  • Restaurant Labor

Nonprofit

5 required

  • Grant Revenue
  • Contributions / Donations
  • Program Expenses
  • Management & General Expenses
  • Fundraising Expenses

E-commerce

5 required

  • Inventory
  • Merchant Processing Fees
  • Shipping Income
  • Shipping/Fulfillment Cost
  • Sales Tax Payable

Professional services

3 required

  • Service / Consulting Revenue
  • Unbilled Revenue / Services WIP
  • Client Retainers / Deferred Revenue

IT services / MSP

4 required

  • MSP / Managed Services Revenue
  • Project Services Revenue
  • Software Subscriptions Expense
  • Technician Labor

Healthcare

4 required

  • Patient Service Revenue
  • Insurance Receivable
  • Medical Supplies
  • Malpractice Insurance

Legal

4 required

  • IOLTA / Client Trust Account
  • Client Retainers (Trust Liability)
  • Legal Fees Income
  • Case Costs / Client Advances

Source: lib/industry-required-accounts.ts.

Scope

What the optimizer doesn't decide for you.

The optimizer restructures the chart of accounts. It does not touch transactions, balances, or anything that requires a professional’s sign-off. These calls stay with you.

Transaction history

We never read, modify, or reclassify ledger transactions. Only the chart structure is in scope.

Account balances

Balances are observed read-only — for instance, to flag a contra-asset with a positive balance — never altered.

Period close decisions

Closing entries, retained-earnings rollover, and period locks remain entirely in your hands and inside QuickBooks Online.

Tax treatment selections

Tax codes, tax tracking, and reporting elections are out of scope. Coordinate with your tax preparer.

Audit and review opinions

Independence, materiality, and audit conclusions are professional judgments that no automated tool should make.

Industry fit (judgment portion)

We surface the deterministic minimums and let an accountant decide whether granularity matches the engagement.

Audit trail

Every change is logged with the rule that drove it.

Every reclassification, rename, renumber, and merge the optimizer proposes is tagged with the rubric criterion that drove it. Charts can be exported with a Source column showing, per account, whether the value is Original, AI, AI Modified, or User-edited — so you can review the work the way you would review a junior’s.

Source column values

  • Original — pulled from QuickBooks Online, untouched.
  • AI — created by the optimizer to fill a deterministic gap.
  • AI Modified — an original account whose name, number, type, or parent was changed by the optimizer; original values stay queryable.
  • User — an edit made by the practitioner during review, after the optimizer ran.

Try it on a real chart

Run the rubric against your own books.

Connect QuickBooks Online read-only, run a full analysis, and review every flagged criterion. Free demo, no credit card.

Read-only OAuth · Works with QBO Simple Start through Advanced