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.
| Range | Account type | Description | Examples |
|---|---|---|---|
| 1000–1099 | Bank | Cash and bank accounts | Checking Account, Savings Account, Petty Cash |
| 1200–1299 | Accounts Receivable | Money owed by customers | Accounts Receivable, Allowance for Doubtful Accounts |
| 1300–1399 | Other Current Asset | Short-term assets (excluding AR and Inventory) | Prepaid Expenses, Short-term Investments, Deposits |
| 1400–1499 | Inventory Asset | Goods for sale or production | Inventory Asset, Raw Materials, Finished Goods |
| 1500–1799 | Fixed Asset | Long-term tangible assets (Accumulated Depreciation uses +5 pairing: 1505, 1515, etc.) | Equipment, Vehicles, Building |
| 1800–1999 | Other Asset | Long-term non-current assets | Long-term Investments, Intangible Assets, Goodwill |
| 2000–2099 | Accounts Payable | Money owed to vendors | Accounts Payable, Trade Payables |
| 2100–2199 | Credit Card | Credit card liabilities | Business Credit Card, Corporate Card |
| 2200–2499 | Other Current Liability | Short-term obligations | Accrued Expenses, Payroll Liabilities, Sales Tax Payable |
| 2500–2999 | Long Term Liability | Long-term debts and obligations | Long Term Debt, Equipment Loans, Mortgage Payable |
| 3000–3999 | Equity | Owner equity and retained earnings | Owner Equity, Retained Earnings, Owner Draws |
| 4000–4699 | Income | Primary operating revenue | Sales Income, Service Revenue, Product Sales |
| 4700–4999 | Other Income | Non-operating income and gains | Interest Earned, Dividend Income, Gain on Sale of Assets |
| 5000–5999 | Cost of Goods Sold | Direct costs of producing goods/services | Cost of Goods Sold, Direct Materials, Direct Labor |
| 6000–6999 | Expense | Operating expenses (parents use 100-point: 6000, 6100; children use 10-point: 6010, 6020) | Office Expenses, Marketing, Rent |
| 7500–7999 | Other Expense | Non-operating expenses and losses | Interest 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.