Build Fathom HQ

KINDAreplaces $41.54/mosaves $498.48/yrback to the verdict

0%0 of 17 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A reporting layer over accounting data whose real problem is that two tools computing the same ratio from the same ledger will disagree unless every definition is written down, and a figure nobody can trace back to transactions is a figure nobody should act on.

estimated effort multi-daythe files for this build are in the project pack

SourceRead-only import from the accounting system, never a second ledgerDefinitionsEvery metric defined as data with its formula and account mappingAmountsInteger minor units with the ledger's own sign conventions preservedPeriodsExplicit period definitions including partial and adjusted periodsTraceabilityEvery figure openable to the transactions behind it

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the import, computation and reporting.

    Get it nodejs.org, or nvm install 22. open ↗

    Verify node --version prints v22 or higher

  2. installfree locally

    Why Balances, metric values and their drill paths with exact integer arithmetic.

    Get it Docker or your package manager. open ↗

    Verify psql connects and integer sums are exact

  3. free with an existing subscription

    Why Sign conventions, chart of accounts structure and period adjustment behaviour differ by system, and none of it can be guessed from a specification.

    Get it Read-only API access to a real ledger with at least two years of history, ideally including a period that was reopened and adjusted.

    Verify An imported trial balance matches the system's own exactly

  4. decidefree, and it takes a conversation with your accountant

    Why Which accounts belong in cost of sales is a judgement, and two people computing the same ratio will disagree unless it is written down first.

    Get it Agree each metric's formula and account mapping with whoever owns the numbers, and write the definition note that will appear beside the figure.

    Verify Every planned metric has an agreed formula, mapping and note

  5. have readyfree

    Why Comparison basis checking and adjustment handling only matter across real history, and a reopened period is the case that breaks naive imports.

    Get it Use the real history from your accounting system rather than generated data.

    Verify Two years exist and at least one period was adjusted after close

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- imports(id, source_system, period_from, period_to, imported_at, ledger_version, complete)
- accounts(id, code, name, kind, parent_id, normal_balance) · kind is asset, liability, equity, income or expense
- balances(account_id, period, amount_minor, currency, adjusted) · adjusted marks a period reopened and changed
- metrics(id, name, formula_json, account_mapping_json, definition_note, created_at)
- metric_values(metric_id, period, value, numerator_minor, denominator_minor, computed_at) · both sides kept
- forecasts(id, metric_id, method, horizon, assumptions_json, produced_at)
- variances(metric_id, period, actual, comparison_kind, comparison_value, explanation)
- A metric value stores its numerator and denominator, so a disputed ratio can be argued about in terms of the amounts rather than the result.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
DATABASE_URLsecretrequiredpostgres://localhost:5432/reportingImported financial data for a real business.
LEDGER_API_KEYsecretrequireda read-only keyRead only. This build never writes to the accounting system.
METRICS_PATHrequired./config/metrics.jsonFormulas, mappings and definition notes. Agreed before building.
BASE_CURRENCYrequiredGBPFor reporting. Source amounts keep their own currency.
SHOW_DEFINITION_WITH_FIGURErequiredalwaysPresent as a constant. A ratio without its definition cannot be reconciled.
MIN_PERIODS_FOR_FORECASTrequired12Below this a forecast is refused rather than produced.

The build, in order

  1. Importing a ledger without becoming one

    Balances match the accounting system exactly.

    1. Normalising breaks reconciliation, which is the whole basis of trust.

      Files src/import.ts

    2. Exactly. Any difference is a defect, not a rounding matter.

      Files tests/reconcile.test.ts

    3. Adjustments after close are normal and change history.

      Files src/periods.ts

    done when · tick each as it passes
    watch out
    • Normalising sign conventions on import so everything is positive makes reports read nicely and makes reconciliation against the accounting system impossible, because the numbers no longer match what the accountant sees. Preserve the source conventions and handle presentation at the display layer.
  2. Metrics defined rather than assumed

    Every ratio has a written formula and an account mapping.

    1. Formula, mapping and a note. Not code and not convention.

      Files src/metrics.ts

    2. The disagreement should be discussable rather than mysterious.

      Files src/render.ts

    3. A changed mapping changes history and should say so.

      Files src/mappings.ts

    done when · tick each as it passes
    watch out
    • Two tools computing gross margin from the same ledger will disagree, because the question of which accounts belong in cost of sales is a judgement rather than a fact, and neither tool says which it used. Store the mapping, show the definition beside the number, and make the disagreement discussable instead of mysterious.
  3. Every figure opens to its transactions

    A number in a report can be drilled to the entries that make it.

    1. Metric to balances to transactions, in one path.

      Files src/drill.ts

    2. For every metric and period, in the suite.

      Files tests/drill-sums.test.ts

    3. A stated result, not an error and not a misleading number.

      Files src/compute.ts

    done when · tick each as it passes
    watch out
    • A report computed from a cached aggregate drifts from the ledger the moment an adjustment is posted, and the discrepancy surfaces in a meeting rather than in a test. Assert in the suite that every displayed figure equals the sum of its drilled detail, for every metric and period.
  4. Comparisons that compare like with like

    A variance against budget or last year is computed on the same basis.

    1. Definition, mapping and period length. All three.

      Files src/compare.tssrc/compare.test.ts

    2. A mapping artefact looks exactly like a business change.

      Files src/compare.ts

    3. Budgets get revised. Which one you beat matters.

      Files src/budgets.ts

    done when · tick each as it passes
    watch out
    • Comparing this year against last year across a change in account mapping produces a variance that is entirely an artefact of the mapping, and it looks exactly like a real change in the business. Detect that the definition differs between the periods and flag the comparison rather than presenting the number.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more finance & accounting.