Vibecode Fathom HQ
track this build4 phases, 12 steps, beginner friendly0%A KPI dashboard over accounting exports is buildable, but Fathom's value is accounting integrations, consolidation, forecasting, polished board reports, and accountant workflows.
You are building a lean indie version of Fathom HQ. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Fathom HQ · indie build 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-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Source | Read-only import from the accounting system, never a second ledger | the accounting system is the source of truth and a second one diverges | | Definitions | Every metric defined as data with its formula and account mapping | a ratio computed by convention is a ratio nobody can reconcile | | Amounts | Integer minor units with the ledger's own sign conventions preserved | sign conventions differ by system and getting them wrong inverts a report | | Periods | Explicit period definitions including partial and adjusted periods | a month that closed after adjustments is not the month you first reported | | Traceability | Every figure openable to the transactions behind it | the only defence against a disputed number | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the import, computation and reporting. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **PostgreSQL 16 or newer** · free locally - Why: Balances, metric values and their drill paths with exact integer arithmetic. - Get it: Docker or your package manager. - Verify: psql connects and integer sums are exact - [ ] **Read access to a real accounting system** · 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 - [ ] **Your metric definitions, agreed before building** · free, 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 - [ ] **Two years of periods including an adjusted one** · free - 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 ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Being the accounting system. - Tax computation and statutory filing. - Audit or assurance. - Multi-entity consolidation with eliminations. - accounting integrations - consolidation - templates - forecasting - report builder - advisor workflows - reliability If one of those is essential to you, that is the reason to keep paying for Fathom HQ, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Fathom HQ The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me financial reporting from your ledger where every figure can be opened to its transactions to replace Fathom. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Source: Read-only import from the accounting system, never a second ledger · the accounting system is the source of truth and a second one diverges - Definitions: Every metric defined as data with its formula and account mapping · a ratio computed by convention is a ratio nobody can reconcile - Amounts: Integer minor units with the ledger's own sign conventions preserved · sign conventions differ by system and getting them wrong inverts a report - Periods: Explicit period definitions including partial and adjusted periods · a month that closed after adjustments is not the month you first reported - Traceability: Every figure openable to the transactions behind it · the only defence against a disputed number ### Data model (create this before Phase 1) - 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. ### Phase 1 · Importing a ledger without becoming one Build: Import the chart of accounts and balances read only, preserving the source's sign conventions. Reconcile totals against the source. Handle a period reopened and adjusted after you already imported it. Trap: 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. Done when: Imported balances reconcile exactly against the source system's own trial balance. Sign conventions match the source rather than being normalised. A reopened and adjusted period is detected and re-imported, with the previous version retained. An incomplete import is marked incomplete rather than reported as a period. Do not build yet: No metrics, reports or forecasts. ### Phase 2 · Metrics defined rather than assumed Build: Define metrics as data: a formula, the accounts each term maps to, and a written definition note. Compute from the mapping rather than from hard-coded account codes. Display the definition beside the figure. Trap: 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. Done when: Every metric has a stored formula, account mapping and written definition. The definition is visible beside the figure rather than in documentation. Changing an account mapping recomputes affected periods and records the change. Two metrics with the same name and different mappings are impossible, or clearly distinguished. Do not build yet: No reports, comparisons or forecasts yet. ### Phase 3 · Every figure opens to its transactions Build: Make each metric value openable to the account balances and then to the transactions behind them. Assert that the drilled detail sums to the displayed figure. Handle a metric whose denominator is zero or whose period is partial. Trap: 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. Done when: Every metric value opens to balances and then to transactions, and the detail sums exactly to the figure, asserted across all metrics in the suite. A zero denominator produces a stated result rather than an error or a misleading value. A partial period is labelled rather than reported as complete. An adjustment posted after computation invalidates the cached value. Do not build yet: No comparisons or forecasts yet. ### Phase 4 · Comparisons that compare like with like Build: Compare actuals against budget and prior periods, checking that the comparison basis matches: same metric definition, same account mapping, same period length. Refuse or flag a comparison across a definition change. Trap: 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. Done when: A comparison across a mapping or definition change is flagged prominently rather than presented as a variance. Period lengths are checked and a partial period is not compared against a full one without labelling. Budget comparisons state which budget version was used. Variances state the basis they were computed on. Do not build yet: No forecasts or consolidation yet. ### Phase 5 · Forecasts that show their assumptions Build: Produce forecasts from a stated method with explicit assumptions recorded alongside. Show a range rather than a single line. Record what actually happened against past forecasts so the method's accuracy is visible. Trap: A forecast drawn as a single confident line gets treated as a plan, and when it is wrong nobody can say whether the method was bad or the assumptions were. Show a range, record the assumptions with the forecast, and compare past forecasts against outcomes so the method's track record is visible rather than assumed. Done when: Forecasts display a range with the method named. Assumptions are recorded with the forecast and shown alongside it. Past forecasts are compared against actual outcomes and the method's historic accuracy is reported. A forecast built on fewer periods than the method requires is refused rather than produced. Do not build yet: Nothing after this. ### Out of scope (and why) - Being the accounting system. This reads a ledger and never becomes a second source of truth. - Tax computation and statutory filing, which is jurisdiction-specific regulated work. - Audit or assurance. Traceability supports an audit; it does not constitute one. - Consolidation across entities with intercompany eliminations, which is genuinely specialist and easy to get subtly wrong. ### README must contain - That the accounting system is the source of truth and sign conventions are preserved rather than normalised - That every metric stores its formula, account mapping and a written definition shown beside the figure - That every displayed figure is asserted to equal the sum of its drilled detail - That a comparison across a definition change is flagged rather than presented as a variance - That forecasts show a range with recorded assumptions and a visible accuracy record ===== AGENTS.md ===== # Agent instructions · Fathom HQ indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Read-only import from the accounting system, never a second ledger, Every metric defined as data with its formula and account mapping, Integer minor units with the ledger's own sign conventions preserved, Explicit period definitions including partial and adjusted periods, Every figure openable to the transactions behind it. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - 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. - 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. - 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. - 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. ===== BUILD_PLAN.md ===== # Build plan · Fathom HQ 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. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Importing a ledger without becoming one Balances match the accounting system exactly. ### Steps 1. Preserve the source's sign conventions Normalising breaks reconciliation, which is the whole basis of trust. Files: `src/import.ts` 2. Reconcile against the trial balance Exactly. Any difference is a defect, not a rounding matter. Files: `tests/reconcile.test.ts` 3. Detect reopened periods Adjustments after close are normal and change history. Files: `src/periods.ts` ### Done when - [ ] Balances reconcile exactly against the source - [ ] Sign conventions match the source - [ ] Reopened periods are detected and re-imported - [ ] Incomplete imports are marked as such ### 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. ## Phase 2 · Metrics defined rather than assumed Every ratio has a written formula and an account mapping. ### Steps 1. Define metrics as data Formula, mapping and a note. Not code and not convention. Files: `src/metrics.ts` 2. Show the definition beside the figure The disagreement should be discussable rather than mysterious. Files: `src/render.ts` 3. Record mapping changes A changed mapping changes history and should say so. Files: `src/mappings.ts` ### Done when - [ ] Every metric has a formula, mapping and note - [ ] The definition appears beside the figure - [ ] Mapping changes recompute and are recorded - [ ] Same-named metrics with different mappings are distinguished ### 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. ## Phase 3 · Every figure opens to its transactions A number in a report can be drilled to the entries that make it. ### Steps 1. Make drilling work end to end Metric to balances to transactions, in one path. Files: `src/drill.ts` 2. Assert detail sums to the figure For every metric and period, in the suite. Files: `tests/drill-sums.test.ts` 3. Handle zero denominators explicitly A stated result, not an error and not a misleading number. Files: `src/compute.ts` ### Done when - [ ] Every figure opens to its transactions - [ ] Detail sums exactly to the figure across all metrics - [ ] Zero denominators produce a stated result - [ ] Partial periods are labelled ### 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. ## Phase 4 · Comparisons that compare like with like A variance against budget or last year is computed on the same basis. ### Steps 1. Check the comparison basis Definition, mapping and period length. All three. Files: `src/compare.ts`, `src/compare.test.ts` 2. Flag rather than present A mapping artefact looks exactly like a business change. Files: `src/compare.ts` 3. Name the budget version Budgets get revised. Which one you beat matters. Files: `src/budgets.ts` ### Done when - [ ] Definition changes flag the comparison - [ ] Period lengths are checked and labelled - [ ] Budget comparisons name the version - [ ] Variances state their basis ### 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. ## Not in this build - Being the accounting system. - Tax computation and statutory filing. - Audit or assurance. - Multi-entity consolidation with eliminations. ## After v1, if you want it - Cash flow forecasting from receivables and payables ageing, which is the question people actually ask - Scenario comparison, once assumptions are already structured - Board pack generation from the reports, which is the recurring manual work ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Imported financial data for a real business. DATABASE_URL=postgres://localhost:5432/reporting # Required · secret. Read only. This build never writes to the accounting system. LEDGER_API_KEY=a read-only key # Required. Formulas, mappings and definition notes. Agreed before building. METRICS_PATH=./config/metrics.json # Required. For reporting. Source amounts keep their own currency. BASE_CURRENCY=GBP # Required. Present as a constant. A ratio without its definition cannot be reconciled. SHOW_DEFINITION_WITH_FIGURE=always # Required. Below this a forecast is refused rather than produced. MIN_PERIODS_FOR_FORECAST=12
You are building a lean indie version of Fathom HQ. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Fathom HQ · indie build 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-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Source | Read-only import from the accounting system, never a second ledger | the accounting system is the source of truth and a second one diverges | | Definitions | Every metric defined as data with its formula and account mapping | a ratio computed by convention is a ratio nobody can reconcile | | Amounts | Integer minor units with the ledger's own sign conventions preserved | sign conventions differ by system and getting them wrong inverts a report | | Periods | Explicit period definitions including partial and adjusted periods | a month that closed after adjustments is not the month you first reported | | Traceability | Every figure openable to the transactions behind it | the only defence against a disputed number | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the import, computation and reporting. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **PostgreSQL 16 or newer** · free locally - Why: Balances, metric values and their drill paths with exact integer arithmetic. - Get it: Docker or your package manager. - Verify: psql connects and integer sums are exact - [ ] **Read access to a real accounting system** · 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 - [ ] **Your metric definitions, agreed before building** · free, 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 - [ ] **Two years of periods including an adjusted one** · free - 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 ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Being the accounting system. - Tax computation and statutory filing. - Audit or assurance. - Multi-entity consolidation with eliminations. - accounting integrations - consolidation - templates - forecasting - report builder - advisor workflows - reliability If one of those is essential to you, that is the reason to keep paying for Fathom HQ, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Fathom HQ The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me financial reporting from your ledger where every figure can be opened to its transactions to replace Fathom. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Source: Read-only import from the accounting system, never a second ledger · the accounting system is the source of truth and a second one diverges - Definitions: Every metric defined as data with its formula and account mapping · a ratio computed by convention is a ratio nobody can reconcile - Amounts: Integer minor units with the ledger's own sign conventions preserved · sign conventions differ by system and getting them wrong inverts a report - Periods: Explicit period definitions including partial and adjusted periods · a month that closed after adjustments is not the month you first reported - Traceability: Every figure openable to the transactions behind it · the only defence against a disputed number ### Data model (create this before Phase 1) - 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. ### Phase 1 · Importing a ledger without becoming one Build: Import the chart of accounts and balances read only, preserving the source's sign conventions. Reconcile totals against the source. Handle a period reopened and adjusted after you already imported it. Trap: 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. Done when: Imported balances reconcile exactly against the source system's own trial balance. Sign conventions match the source rather than being normalised. A reopened and adjusted period is detected and re-imported, with the previous version retained. An incomplete import is marked incomplete rather than reported as a period. Do not build yet: No metrics, reports or forecasts. ### Phase 2 · Metrics defined rather than assumed Build: Define metrics as data: a formula, the accounts each term maps to, and a written definition note. Compute from the mapping rather than from hard-coded account codes. Display the definition beside the figure. Trap: 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. Done when: Every metric has a stored formula, account mapping and written definition. The definition is visible beside the figure rather than in documentation. Changing an account mapping recomputes affected periods and records the change. Two metrics with the same name and different mappings are impossible, or clearly distinguished. Do not build yet: No reports, comparisons or forecasts yet. ### Phase 3 · Every figure opens to its transactions Build: Make each metric value openable to the account balances and then to the transactions behind them. Assert that the drilled detail sums to the displayed figure. Handle a metric whose denominator is zero or whose period is partial. Trap: 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. Done when: Every metric value opens to balances and then to transactions, and the detail sums exactly to the figure, asserted across all metrics in the suite. A zero denominator produces a stated result rather than an error or a misleading value. A partial period is labelled rather than reported as complete. An adjustment posted after computation invalidates the cached value. Do not build yet: No comparisons or forecasts yet. ### Phase 4 · Comparisons that compare like with like Build: Compare actuals against budget and prior periods, checking that the comparison basis matches: same metric definition, same account mapping, same period length. Refuse or flag a comparison across a definition change. Trap: 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. Done when: A comparison across a mapping or definition change is flagged prominently rather than presented as a variance. Period lengths are checked and a partial period is not compared against a full one without labelling. Budget comparisons state which budget version was used. Variances state the basis they were computed on. Do not build yet: No forecasts or consolidation yet. ### Phase 5 · Forecasts that show their assumptions Build: Produce forecasts from a stated method with explicit assumptions recorded alongside. Show a range rather than a single line. Record what actually happened against past forecasts so the method's accuracy is visible. Trap: A forecast drawn as a single confident line gets treated as a plan, and when it is wrong nobody can say whether the method was bad or the assumptions were. Show a range, record the assumptions with the forecast, and compare past forecasts against outcomes so the method's track record is visible rather than assumed. Done when: Forecasts display a range with the method named. Assumptions are recorded with the forecast and shown alongside it. Past forecasts are compared against actual outcomes and the method's historic accuracy is reported. A forecast built on fewer periods than the method requires is refused rather than produced. Do not build yet: Nothing after this. ### Out of scope (and why) - Being the accounting system. This reads a ledger and never becomes a second source of truth. - Tax computation and statutory filing, which is jurisdiction-specific regulated work. - Audit or assurance. Traceability supports an audit; it does not constitute one. - Consolidation across entities with intercompany eliminations, which is genuinely specialist and easy to get subtly wrong. ### README must contain - That the accounting system is the source of truth and sign conventions are preserved rather than normalised - That every metric stores its formula, account mapping and a written definition shown beside the figure - That every displayed figure is asserted to equal the sum of its drilled detail - That a comparison across a definition change is flagged rather than presented as a variance - That forecasts show a range with recorded assumptions and a visible accuracy record ===== AGENTS.md ===== # Agent instructions · Fathom HQ indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Read-only import from the accounting system, never a second ledger, Every metric defined as data with its formula and account mapping, Integer minor units with the ledger's own sign conventions preserved, Explicit period definitions including partial and adjusted periods, Every figure openable to the transactions behind it. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - 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. - 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. - 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. - 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. ===== BUILD_PLAN.md ===== # Build plan · Fathom HQ 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. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Importing a ledger without becoming one Balances match the accounting system exactly. ### Steps 1. Preserve the source's sign conventions Normalising breaks reconciliation, which is the whole basis of trust. Files: `src/import.ts` 2. Reconcile against the trial balance Exactly. Any difference is a defect, not a rounding matter. Files: `tests/reconcile.test.ts` 3. Detect reopened periods Adjustments after close are normal and change history. Files: `src/periods.ts` ### Done when - [ ] Balances reconcile exactly against the source - [ ] Sign conventions match the source - [ ] Reopened periods are detected and re-imported - [ ] Incomplete imports are marked as such ### 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. ## Phase 2 · Metrics defined rather than assumed Every ratio has a written formula and an account mapping. ### Steps 1. Define metrics as data Formula, mapping and a note. Not code and not convention. Files: `src/metrics.ts` 2. Show the definition beside the figure The disagreement should be discussable rather than mysterious. Files: `src/render.ts` 3. Record mapping changes A changed mapping changes history and should say so. Files: `src/mappings.ts` ### Done when - [ ] Every metric has a formula, mapping and note - [ ] The definition appears beside the figure - [ ] Mapping changes recompute and are recorded - [ ] Same-named metrics with different mappings are distinguished ### 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. ## Phase 3 · Every figure opens to its transactions A number in a report can be drilled to the entries that make it. ### Steps 1. Make drilling work end to end Metric to balances to transactions, in one path. Files: `src/drill.ts` 2. Assert detail sums to the figure For every metric and period, in the suite. Files: `tests/drill-sums.test.ts` 3. Handle zero denominators explicitly A stated result, not an error and not a misleading number. Files: `src/compute.ts` ### Done when - [ ] Every figure opens to its transactions - [ ] Detail sums exactly to the figure across all metrics - [ ] Zero denominators produce a stated result - [ ] Partial periods are labelled ### 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. ## Phase 4 · Comparisons that compare like with like A variance against budget or last year is computed on the same basis. ### Steps 1. Check the comparison basis Definition, mapping and period length. All three. Files: `src/compare.ts`, `src/compare.test.ts` 2. Flag rather than present A mapping artefact looks exactly like a business change. Files: `src/compare.ts` 3. Name the budget version Budgets get revised. Which one you beat matters. Files: `src/budgets.ts` ### Done when - [ ] Definition changes flag the comparison - [ ] Period lengths are checked and labelled - [ ] Budget comparisons name the version - [ ] Variances state their basis ### 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. ## Not in this build - Being the accounting system. - Tax computation and statutory filing. - Audit or assurance. - Multi-entity consolidation with eliminations. ## After v1, if you want it - Cash flow forecasting from receivables and payables ageing, which is the question people actually ask - Scenario comparison, once assumptions are already structured - Board pack generation from the reports, which is the recurring manual work ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Imported financial data for a real business. DATABASE_URL=postgres://localhost:5432/reporting # Required · secret. Read only. This build never writes to the accounting system. LEDGER_API_KEY=a read-only key # Required. Formulas, mappings and definition notes. Agreed before building. METRICS_PATH=./config/metrics.json # Required. For reporting. Source amounts keep their own currency. BASE_CURRENCY=GBP # Required. Present as a constant. A ratio without its definition cannot be reconciled. SHOW_DEFINITION_WITH_FIGURE=always # Required. Below this a forecast is refused rather than produced. MIN_PERIODS_FOR_FORECAST=12
You are building a production product version of Fathom HQ. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # Fathom HQ · product brief ## Problem A KPI dashboard over accounting exports is buildable, but Fathom's value is accounting integrations, consolidation, forecasting, polished board reports, and accountant workflows. ## Product outcome Reporting where every figure opens to the transactions behind it, every ratio carries the definition it was computed from, and a variance caused by a mapping change is never presented as a business result. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - accounting exports/API - data model - chart/report builder - PDF export - hosted app - permissions ## Explicit non-goals for v1 - Being the accounting system. - Tax computation and statutory filing. - Audit or assurance. - Multi-entity consolidation with eliminations. - accounting integrations - consolidation - templates - forecasting - report builder - advisor workflows - reliability ## Success criteria - Balances are confirmed to reconcile exactly against the source - Every metric is confirmed to have a formula, mapping and note - Drilled detail is confirmed to sum to every displayed figure - A definition change is confirmed to flag comparisons - Forecasts are confirmed to show ranges with assumptions ===== BRIEF.md ===== # Build brief · Fathom HQ The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me financial reporting from your ledger where every figure can be opened to its transactions to replace Fathom. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Source: Read-only import from the accounting system, never a second ledger · the accounting system is the source of truth and a second one diverges - Definitions: Every metric defined as data with its formula and account mapping · a ratio computed by convention is a ratio nobody can reconcile - Amounts: Integer minor units with the ledger's own sign conventions preserved · sign conventions differ by system and getting them wrong inverts a report - Periods: Explicit period definitions including partial and adjusted periods · a month that closed after adjustments is not the month you first reported - Traceability: Every figure openable to the transactions behind it · the only defence against a disputed number ### Data model (create this before Phase 1) - 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. ### Phase 1 · Importing a ledger without becoming one Build: Import the chart of accounts and balances read only, preserving the source's sign conventions. Reconcile totals against the source. Handle a period reopened and adjusted after you already imported it. Trap: 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. Done when: Imported balances reconcile exactly against the source system's own trial balance. Sign conventions match the source rather than being normalised. A reopened and adjusted period is detected and re-imported, with the previous version retained. An incomplete import is marked incomplete rather than reported as a period. Do not build yet: No metrics, reports or forecasts. ### Phase 2 · Metrics defined rather than assumed Build: Define metrics as data: a formula, the accounts each term maps to, and a written definition note. Compute from the mapping rather than from hard-coded account codes. Display the definition beside the figure. Trap: 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. Done when: Every metric has a stored formula, account mapping and written definition. The definition is visible beside the figure rather than in documentation. Changing an account mapping recomputes affected periods and records the change. Two metrics with the same name and different mappings are impossible, or clearly distinguished. Do not build yet: No reports, comparisons or forecasts yet. ### Phase 3 · Every figure opens to its transactions Build: Make each metric value openable to the account balances and then to the transactions behind them. Assert that the drilled detail sums to the displayed figure. Handle a metric whose denominator is zero or whose period is partial. Trap: 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. Done when: Every metric value opens to balances and then to transactions, and the detail sums exactly to the figure, asserted across all metrics in the suite. A zero denominator produces a stated result rather than an error or a misleading value. A partial period is labelled rather than reported as complete. An adjustment posted after computation invalidates the cached value. Do not build yet: No comparisons or forecasts yet. ### Phase 4 · Comparisons that compare like with like Build: Compare actuals against budget and prior periods, checking that the comparison basis matches: same metric definition, same account mapping, same period length. Refuse or flag a comparison across a definition change. Trap: 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. Done when: A comparison across a mapping or definition change is flagged prominently rather than presented as a variance. Period lengths are checked and a partial period is not compared against a full one without labelling. Budget comparisons state which budget version was used. Variances state the basis they were computed on. Do not build yet: No forecasts or consolidation yet. ### Phase 5 · Forecasts that show their assumptions Build: Produce forecasts from a stated method with explicit assumptions recorded alongside. Show a range rather than a single line. Record what actually happened against past forecasts so the method's accuracy is visible. Trap: A forecast drawn as a single confident line gets treated as a plan, and when it is wrong nobody can say whether the method was bad or the assumptions were. Show a range, record the assumptions with the forecast, and compare past forecasts against outcomes so the method's track record is visible rather than assumed. Done when: Forecasts display a range with the method named. Assumptions are recorded with the forecast and shown alongside it. Past forecasts are compared against actual outcomes and the method's historic accuracy is reported. A forecast built on fewer periods than the method requires is refused rather than produced. Do not build yet: Nothing after this. ### Out of scope (and why) - Being the accounting system. This reads a ledger and never becomes a second source of truth. - Tax computation and statutory filing, which is jurisdiction-specific regulated work. - Audit or assurance. Traceability supports an audit; it does not constitute one. - Consolidation across entities with intercompany eliminations, which is genuinely specialist and easy to get subtly wrong. ### README must contain - That the accounting system is the source of truth and sign conventions are preserved rather than normalised - That every metric stores its formula, account mapping and a written definition shown beside the figure - That every displayed figure is asserted to equal the sum of its drilled detail - That a comparison across a definition change is flagged rather than presented as a variance - That forecasts show a range with recorded assumptions and a visible accuracy record ===== ARCHITECTURE.md ===== # Architecture · Fathom HQ ## Stack | Part | Choice | Why | | --- | --- | --- | | Source | Read-only import from the accounting system, never a second ledger | the accounting system is the source of truth and a second one diverges | | Definitions | Every metric defined as data with its formula and account mapping | a ratio computed by convention is a ratio nobody can reconcile | | Amounts | Integer minor units with the ledger's own sign conventions preserved | sign conventions differ by system and getting them wrong inverts a report | | Periods | Explicit period definitions including partial and adjusted periods | a month that closed after adjustments is not the month you first reported | | Traceability | Every figure openable to the transactions behind it | the only defence against a disputed number | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Import | Read-only ledger ingestion preserving conventions. | Any accounting system, provided sign conventions are not normalised. | | Metrics | Formulas, mappings and definition notes as data. | Any implementation, provided the definition is displayed with the figure. | | Traceability | Drilling and sum assertions. | Not swappable. A figure that cannot be drilled is a figure that gets disputed. | | Comparison | Basis checking across periods and budgets. | Any implementation, provided definition changes flag rather than compute. | | Forecast | Ranges, assumptions and accuracy history. | Any method, provided the assumptions and track record are visible. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DATABASE_URL` · required, secret · Imported financial data for a real business. - `LEDGER_API_KEY` · required, secret · Read only. This build never writes to the accounting system. - `METRICS_PATH` · required · Formulas, mappings and definition notes. Agreed before building. - `BASE_CURRENCY` · required · For reporting. Source amounts keep their own currency. - `SHOW_DEFINITION_WITH_FIGURE` · required · Present as a constant. A ratio without its definition cannot be reconciled. - `MIN_PERIODS_FOR_FORECAST` · required · Below this a forecast is refused rather than produced. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · Fathom HQ product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Read-only import from the accounting system, never a second ledger, Every metric defined as data with its formula and account mapping, Integer minor units with the ledger's own sign conventions preserved, Explicit period definitions including partial and adjusted periods, Every figure openable to the transactions behind it. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - 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. - 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. - 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. - 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. - A forecast drawn as a single confident line gets treated as a plan, and when it is wrong nobody can say whether the method was bad or the assumptions were. Show a range, record the assumptions with the forecast, and compare past forecasts against outcomes so the method's track record is visible rather than assumed. ===== MILESTONES.md ===== # Delivery milestones · Fathom HQ Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Importing a ledger without becoming one Balances match the accounting system exactly. ### Steps 1. Preserve the source's sign conventions Normalising breaks reconciliation, which is the whole basis of trust. Files: `src/import.ts` 2. Reconcile against the trial balance Exactly. Any difference is a defect, not a rounding matter. Files: `tests/reconcile.test.ts` 3. Detect reopened periods Adjustments after close are normal and change history. Files: `src/periods.ts` ### Done when - [ ] Balances reconcile exactly against the source - [ ] Sign conventions match the source - [ ] Reopened periods are detected and re-imported - [ ] Incomplete imports are marked as such ### 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. ## M2 · Metrics defined rather than assumed Every ratio has a written formula and an account mapping. ### Steps 1. Define metrics as data Formula, mapping and a note. Not code and not convention. Files: `src/metrics.ts` 2. Show the definition beside the figure The disagreement should be discussable rather than mysterious. Files: `src/render.ts` 3. Record mapping changes A changed mapping changes history and should say so. Files: `src/mappings.ts` ### Done when - [ ] Every metric has a formula, mapping and note - [ ] The definition appears beside the figure - [ ] Mapping changes recompute and are recorded - [ ] Same-named metrics with different mappings are distinguished ### 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. ## M3 · Every figure opens to its transactions A number in a report can be drilled to the entries that make it. ### Steps 1. Make drilling work end to end Metric to balances to transactions, in one path. Files: `src/drill.ts` 2. Assert detail sums to the figure For every metric and period, in the suite. Files: `tests/drill-sums.test.ts` 3. Handle zero denominators explicitly A stated result, not an error and not a misleading number. Files: `src/compute.ts` ### Done when - [ ] Every figure opens to its transactions - [ ] Detail sums exactly to the figure across all metrics - [ ] Zero denominators produce a stated result - [ ] Partial periods are labelled ### 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. ## M4 · Comparisons that compare like with like A variance against budget or last year is computed on the same basis. ### Steps 1. Check the comparison basis Definition, mapping and period length. All three. Files: `src/compare.ts`, `src/compare.test.ts` 2. Flag rather than present A mapping artefact looks exactly like a business change. Files: `src/compare.ts` 3. Name the budget version Budgets get revised. Which one you beat matters. Files: `src/budgets.ts` ### Done when - [ ] Definition changes flag the comparison - [ ] Period lengths are checked and labelled - [ ] Budget comparisons name the version - [ ] Variances state their basis ### 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. ## M5 · Forecasts that show their assumptions (production only) A projection states what it assumed and how far it can be trusted. ### Steps 1. Record assumptions with the forecast So a wrong forecast is diagnosable rather than merely wrong. Files: `src/forecast.ts` 2. Show a range, not a line A single line becomes a plan the moment it is on a slide. Files: `src/render.ts` 3. Score past forecasts against outcomes The method's track record is the only honest confidence signal. Files: `src/accuracy.ts` ### Done when - [ ] Forecasts show a range and name their method - [ ] Assumptions are recorded and displayed - [ ] Historic accuracy is reported - [ ] Insufficient history refuses rather than forecasts ### Watch out - A forecast drawn as a single confident line gets treated as a plan, and when it is wrong nobody can say whether the method was bad or the assumptions were. Show a range, record the assumptions with the forecast, and compare past forecasts against outcomes so the method's track record is visible rather than assumed. ===== OPERATIONS.md ===== # Operations · Fathom HQ ## Backup Imported ledger data can be re-imported, but metric definitions, mapping history and past forecasts with their assumptions cannot, since they represent decisions rather than data. Back those up first. ## Restore Restore and confirm metric definitions and their change history are intact, since restoring values without the definitions that produced them leaves figures nobody can reconcile or defend. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Reconciliation against the source trial balance on every import is the health check. A drift means the import is wrong, and every report after that point is wrong in a way that looks entirely plausible. ## Incident checklist A figure disputed in a meeting is the failure this build is organised to prevent, and the drill path plus the stored definition is what turns it from an argument into a question with an answer. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Balances are confirmed to reconcile exactly against the source - [ ] Every metric is confirmed to have a formula, mapping and note - [ ] Drilled detail is confirmed to sum to every displayed figure - [ ] A definition change is confirmed to flag comparisons - [ ] Forecasts are confirmed to show ranges with assumptions ## Launch constraint Do not market omitted Fathom HQ capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Imported financial data for a real business. DATABASE_URL=postgres://localhost:5432/reporting # Required · secret. Read only. This build never writes to the accounting system. LEDGER_API_KEY=a read-only key # Required. Formulas, mappings and definition notes. Agreed before building. METRICS_PATH=./config/metrics.json # Required. For reporting. Source amounts keep their own currency. BASE_CURRENCY=GBP # Required. Present as a constant. A ratio without its definition cannot be reconciled. SHOW_DEFINITION_WITH_FIGURE=always # Required. Below this a forecast is refused rather than produced. MIN_PERIODS_FOR_FORECAST=12
# Fathom HQ · indie build 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-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Source | Read-only import from the accounting system, never a second ledger | the accounting system is the source of truth and a second one diverges | | Definitions | Every metric defined as data with its formula and account mapping | a ratio computed by convention is a ratio nobody can reconcile | | Amounts | Integer minor units with the ledger's own sign conventions preserved | sign conventions differ by system and getting them wrong inverts a report | | Periods | Explicit period definitions including partial and adjusted periods | a month that closed after adjustments is not the month you first reported | | Traceability | Every figure openable to the transactions behind it | the only defence against a disputed number | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the import, computation and reporting. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **PostgreSQL 16 or newer** · free locally - Why: Balances, metric values and their drill paths with exact integer arithmetic. - Get it: Docker or your package manager. - Verify: psql connects and integer sums are exact - [ ] **Read access to a real accounting system** · 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 - [ ] **Your metric definitions, agreed before building** · free, 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 - [ ] **Two years of periods including an adjusted one** · free - 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 ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Being the accounting system. - Tax computation and statutory filing. - Audit or assurance. - Multi-entity consolidation with eliminations. - accounting integrations - consolidation - templates - forecasting - report builder - advisor workflows - reliability If one of those is essential to you, that is the reason to keep paying for Fathom HQ, and the README should say so rather than pretend.
# Build brief · Fathom HQ The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me financial reporting from your ledger where every figure can be opened to its transactions to replace Fathom. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Source: Read-only import from the accounting system, never a second ledger · the accounting system is the source of truth and a second one diverges - Definitions: Every metric defined as data with its formula and account mapping · a ratio computed by convention is a ratio nobody can reconcile - Amounts: Integer minor units with the ledger's own sign conventions preserved · sign conventions differ by system and getting them wrong inverts a report - Periods: Explicit period definitions including partial and adjusted periods · a month that closed after adjustments is not the month you first reported - Traceability: Every figure openable to the transactions behind it · the only defence against a disputed number ### Data model (create this before Phase 1) - 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. ### Phase 1 · Importing a ledger without becoming one Build: Import the chart of accounts and balances read only, preserving the source's sign conventions. Reconcile totals against the source. Handle a period reopened and adjusted after you already imported it. Trap: 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. Done when: Imported balances reconcile exactly against the source system's own trial balance. Sign conventions match the source rather than being normalised. A reopened and adjusted period is detected and re-imported, with the previous version retained. An incomplete import is marked incomplete rather than reported as a period. Do not build yet: No metrics, reports or forecasts. ### Phase 2 · Metrics defined rather than assumed Build: Define metrics as data: a formula, the accounts each term maps to, and a written definition note. Compute from the mapping rather than from hard-coded account codes. Display the definition beside the figure. Trap: 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. Done when: Every metric has a stored formula, account mapping and written definition. The definition is visible beside the figure rather than in documentation. Changing an account mapping recomputes affected periods and records the change. Two metrics with the same name and different mappings are impossible, or clearly distinguished. Do not build yet: No reports, comparisons or forecasts yet. ### Phase 3 · Every figure opens to its transactions Build: Make each metric value openable to the account balances and then to the transactions behind them. Assert that the drilled detail sums to the displayed figure. Handle a metric whose denominator is zero or whose period is partial. Trap: 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. Done when: Every metric value opens to balances and then to transactions, and the detail sums exactly to the figure, asserted across all metrics in the suite. A zero denominator produces a stated result rather than an error or a misleading value. A partial period is labelled rather than reported as complete. An adjustment posted after computation invalidates the cached value. Do not build yet: No comparisons or forecasts yet. ### Phase 4 · Comparisons that compare like with like Build: Compare actuals against budget and prior periods, checking that the comparison basis matches: same metric definition, same account mapping, same period length. Refuse or flag a comparison across a definition change. Trap: 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. Done when: A comparison across a mapping or definition change is flagged prominently rather than presented as a variance. Period lengths are checked and a partial period is not compared against a full one without labelling. Budget comparisons state which budget version was used. Variances state the basis they were computed on. Do not build yet: No forecasts or consolidation yet. ### Phase 5 · Forecasts that show their assumptions Build: Produce forecasts from a stated method with explicit assumptions recorded alongside. Show a range rather than a single line. Record what actually happened against past forecasts so the method's accuracy is visible. Trap: A forecast drawn as a single confident line gets treated as a plan, and when it is wrong nobody can say whether the method was bad or the assumptions were. Show a range, record the assumptions with the forecast, and compare past forecasts against outcomes so the method's track record is visible rather than assumed. Done when: Forecasts display a range with the method named. Assumptions are recorded with the forecast and shown alongside it. Past forecasts are compared against actual outcomes and the method's historic accuracy is reported. A forecast built on fewer periods than the method requires is refused rather than produced. Do not build yet: Nothing after this. ### Out of scope (and why) - Being the accounting system. This reads a ledger and never becomes a second source of truth. - Tax computation and statutory filing, which is jurisdiction-specific regulated work. - Audit or assurance. Traceability supports an audit; it does not constitute one. - Consolidation across entities with intercompany eliminations, which is genuinely specialist and easy to get subtly wrong. ### README must contain - That the accounting system is the source of truth and sign conventions are preserved rather than normalised - That every metric stores its formula, account mapping and a written definition shown beside the figure - That every displayed figure is asserted to equal the sum of its drilled detail - That a comparison across a definition change is flagged rather than presented as a variance - That forecasts show a range with recorded assumptions and a visible accuracy record
# Agent instructions · Fathom HQ indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Read-only import from the accounting system, never a second ledger, Every metric defined as data with its formula and account mapping, Integer minor units with the ledger's own sign conventions preserved, Explicit period definitions including partial and adjusted periods, Every figure openable to the transactions behind it. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - 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. - 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. - 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. - 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.
# Build plan · Fathom HQ 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. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Importing a ledger without becoming one Balances match the accounting system exactly. ### Steps 1. Preserve the source's sign conventions Normalising breaks reconciliation, which is the whole basis of trust. Files: `src/import.ts` 2. Reconcile against the trial balance Exactly. Any difference is a defect, not a rounding matter. Files: `tests/reconcile.test.ts` 3. Detect reopened periods Adjustments after close are normal and change history. Files: `src/periods.ts` ### Done when - [ ] Balances reconcile exactly against the source - [ ] Sign conventions match the source - [ ] Reopened periods are detected and re-imported - [ ] Incomplete imports are marked as such ### 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. ## Phase 2 · Metrics defined rather than assumed Every ratio has a written formula and an account mapping. ### Steps 1. Define metrics as data Formula, mapping and a note. Not code and not convention. Files: `src/metrics.ts` 2. Show the definition beside the figure The disagreement should be discussable rather than mysterious. Files: `src/render.ts` 3. Record mapping changes A changed mapping changes history and should say so. Files: `src/mappings.ts` ### Done when - [ ] Every metric has a formula, mapping and note - [ ] The definition appears beside the figure - [ ] Mapping changes recompute and are recorded - [ ] Same-named metrics with different mappings are distinguished ### 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. ## Phase 3 · Every figure opens to its transactions A number in a report can be drilled to the entries that make it. ### Steps 1. Make drilling work end to end Metric to balances to transactions, in one path. Files: `src/drill.ts` 2. Assert detail sums to the figure For every metric and period, in the suite. Files: `tests/drill-sums.test.ts` 3. Handle zero denominators explicitly A stated result, not an error and not a misleading number. Files: `src/compute.ts` ### Done when - [ ] Every figure opens to its transactions - [ ] Detail sums exactly to the figure across all metrics - [ ] Zero denominators produce a stated result - [ ] Partial periods are labelled ### 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. ## Phase 4 · Comparisons that compare like with like A variance against budget or last year is computed on the same basis. ### Steps 1. Check the comparison basis Definition, mapping and period length. All three. Files: `src/compare.ts`, `src/compare.test.ts` 2. Flag rather than present A mapping artefact looks exactly like a business change. Files: `src/compare.ts` 3. Name the budget version Budgets get revised. Which one you beat matters. Files: `src/budgets.ts` ### Done when - [ ] Definition changes flag the comparison - [ ] Period lengths are checked and labelled - [ ] Budget comparisons name the version - [ ] Variances state their basis ### 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. ## Not in this build - Being the accounting system. - Tax computation and statutory filing. - Audit or assurance. - Multi-entity consolidation with eliminations. ## After v1, if you want it - Cash flow forecasting from receivables and payables ageing, which is the question people actually ask - Scenario comparison, once assumptions are already structured - Board pack generation from the reports, which is the recurring manual work
# Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Imported financial data for a real business. DATABASE_URL=postgres://localhost:5432/reporting # Required · secret. Read only. This build never writes to the accounting system. LEDGER_API_KEY=a read-only key # Required. Formulas, mappings and definition notes. Agreed before building. METRICS_PATH=./config/metrics.json # Required. For reporting. Source amounts keep their own currency. BASE_CURRENCY=GBP # Required. Present as a constant. A ratio without its definition cannot be reconciled. SHOW_DEFINITION_WITH_FIGURE=always # Required. Below this a forecast is refused rather than produced. MIN_PERIODS_FOR_FORECAST=12
# Fathom HQ · product brief ## Problem A KPI dashboard over accounting exports is buildable, but Fathom's value is accounting integrations, consolidation, forecasting, polished board reports, and accountant workflows. ## Product outcome Reporting where every figure opens to the transactions behind it, every ratio carries the definition it was computed from, and a variance caused by a mapping change is never presented as a business result. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - accounting exports/API - data model - chart/report builder - PDF export - hosted app - permissions ## Explicit non-goals for v1 - Being the accounting system. - Tax computation and statutory filing. - Audit or assurance. - Multi-entity consolidation with eliminations. - accounting integrations - consolidation - templates - forecasting - report builder - advisor workflows - reliability ## Success criteria - Balances are confirmed to reconcile exactly against the source - Every metric is confirmed to have a formula, mapping and note - Drilled detail is confirmed to sum to every displayed figure - A definition change is confirmed to flag comparisons - Forecasts are confirmed to show ranges with assumptions
# Build brief · Fathom HQ The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me financial reporting from your ledger where every figure can be opened to its transactions to replace Fathom. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Source: Read-only import from the accounting system, never a second ledger · the accounting system is the source of truth and a second one diverges - Definitions: Every metric defined as data with its formula and account mapping · a ratio computed by convention is a ratio nobody can reconcile - Amounts: Integer minor units with the ledger's own sign conventions preserved · sign conventions differ by system and getting them wrong inverts a report - Periods: Explicit period definitions including partial and adjusted periods · a month that closed after adjustments is not the month you first reported - Traceability: Every figure openable to the transactions behind it · the only defence against a disputed number ### Data model (create this before Phase 1) - 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. ### Phase 1 · Importing a ledger without becoming one Build: Import the chart of accounts and balances read only, preserving the source's sign conventions. Reconcile totals against the source. Handle a period reopened and adjusted after you already imported it. Trap: 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. Done when: Imported balances reconcile exactly against the source system's own trial balance. Sign conventions match the source rather than being normalised. A reopened and adjusted period is detected and re-imported, with the previous version retained. An incomplete import is marked incomplete rather than reported as a period. Do not build yet: No metrics, reports or forecasts. ### Phase 2 · Metrics defined rather than assumed Build: Define metrics as data: a formula, the accounts each term maps to, and a written definition note. Compute from the mapping rather than from hard-coded account codes. Display the definition beside the figure. Trap: 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. Done when: Every metric has a stored formula, account mapping and written definition. The definition is visible beside the figure rather than in documentation. Changing an account mapping recomputes affected periods and records the change. Two metrics with the same name and different mappings are impossible, or clearly distinguished. Do not build yet: No reports, comparisons or forecasts yet. ### Phase 3 · Every figure opens to its transactions Build: Make each metric value openable to the account balances and then to the transactions behind them. Assert that the drilled detail sums to the displayed figure. Handle a metric whose denominator is zero or whose period is partial. Trap: 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. Done when: Every metric value opens to balances and then to transactions, and the detail sums exactly to the figure, asserted across all metrics in the suite. A zero denominator produces a stated result rather than an error or a misleading value. A partial period is labelled rather than reported as complete. An adjustment posted after computation invalidates the cached value. Do not build yet: No comparisons or forecasts yet. ### Phase 4 · Comparisons that compare like with like Build: Compare actuals against budget and prior periods, checking that the comparison basis matches: same metric definition, same account mapping, same period length. Refuse or flag a comparison across a definition change. Trap: 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. Done when: A comparison across a mapping or definition change is flagged prominently rather than presented as a variance. Period lengths are checked and a partial period is not compared against a full one without labelling. Budget comparisons state which budget version was used. Variances state the basis they were computed on. Do not build yet: No forecasts or consolidation yet. ### Phase 5 · Forecasts that show their assumptions Build: Produce forecasts from a stated method with explicit assumptions recorded alongside. Show a range rather than a single line. Record what actually happened against past forecasts so the method's accuracy is visible. Trap: A forecast drawn as a single confident line gets treated as a plan, and when it is wrong nobody can say whether the method was bad or the assumptions were. Show a range, record the assumptions with the forecast, and compare past forecasts against outcomes so the method's track record is visible rather than assumed. Done when: Forecasts display a range with the method named. Assumptions are recorded with the forecast and shown alongside it. Past forecasts are compared against actual outcomes and the method's historic accuracy is reported. A forecast built on fewer periods than the method requires is refused rather than produced. Do not build yet: Nothing after this. ### Out of scope (and why) - Being the accounting system. This reads a ledger and never becomes a second source of truth. - Tax computation and statutory filing, which is jurisdiction-specific regulated work. - Audit or assurance. Traceability supports an audit; it does not constitute one. - Consolidation across entities with intercompany eliminations, which is genuinely specialist and easy to get subtly wrong. ### README must contain - That the accounting system is the source of truth and sign conventions are preserved rather than normalised - That every metric stores its formula, account mapping and a written definition shown beside the figure - That every displayed figure is asserted to equal the sum of its drilled detail - That a comparison across a definition change is flagged rather than presented as a variance - That forecasts show a range with recorded assumptions and a visible accuracy record
# Architecture · Fathom HQ ## Stack | Part | Choice | Why | | --- | --- | --- | | Source | Read-only import from the accounting system, never a second ledger | the accounting system is the source of truth and a second one diverges | | Definitions | Every metric defined as data with its formula and account mapping | a ratio computed by convention is a ratio nobody can reconcile | | Amounts | Integer minor units with the ledger's own sign conventions preserved | sign conventions differ by system and getting them wrong inverts a report | | Periods | Explicit period definitions including partial and adjusted periods | a month that closed after adjustments is not the month you first reported | | Traceability | Every figure openable to the transactions behind it | the only defence against a disputed number | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Import | Read-only ledger ingestion preserving conventions. | Any accounting system, provided sign conventions are not normalised. | | Metrics | Formulas, mappings and definition notes as data. | Any implementation, provided the definition is displayed with the figure. | | Traceability | Drilling and sum assertions. | Not swappable. A figure that cannot be drilled is a figure that gets disputed. | | Comparison | Basis checking across periods and budgets. | Any implementation, provided definition changes flag rather than compute. | | Forecast | Ranges, assumptions and accuracy history. | Any method, provided the assumptions and track record are visible. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DATABASE_URL` · required, secret · Imported financial data for a real business. - `LEDGER_API_KEY` · required, secret · Read only. This build never writes to the accounting system. - `METRICS_PATH` · required · Formulas, mappings and definition notes. Agreed before building. - `BASE_CURRENCY` · required · For reporting. Source amounts keep their own currency. - `SHOW_DEFINITION_WITH_FIGURE` · required · Present as a constant. A ratio without its definition cannot be reconciled. - `MIN_PERIODS_FOR_FORECAST` · required · Below this a forecast is refused rather than produced. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · Fathom HQ product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Read-only import from the accounting system, never a second ledger, Every metric defined as data with its formula and account mapping, Integer minor units with the ledger's own sign conventions preserved, Explicit period definitions including partial and adjusted periods, Every figure openable to the transactions behind it. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - 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. - 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. - 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. - 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. - A forecast drawn as a single confident line gets treated as a plan, and when it is wrong nobody can say whether the method was bad or the assumptions were. Show a range, record the assumptions with the forecast, and compare past forecasts against outcomes so the method's track record is visible rather than assumed.
# Delivery milestones · Fathom HQ Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Importing a ledger without becoming one Balances match the accounting system exactly. ### Steps 1. Preserve the source's sign conventions Normalising breaks reconciliation, which is the whole basis of trust. Files: `src/import.ts` 2. Reconcile against the trial balance Exactly. Any difference is a defect, not a rounding matter. Files: `tests/reconcile.test.ts` 3. Detect reopened periods Adjustments after close are normal and change history. Files: `src/periods.ts` ### Done when - [ ] Balances reconcile exactly against the source - [ ] Sign conventions match the source - [ ] Reopened periods are detected and re-imported - [ ] Incomplete imports are marked as such ### 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. ## M2 · Metrics defined rather than assumed Every ratio has a written formula and an account mapping. ### Steps 1. Define metrics as data Formula, mapping and a note. Not code and not convention. Files: `src/metrics.ts` 2. Show the definition beside the figure The disagreement should be discussable rather than mysterious. Files: `src/render.ts` 3. Record mapping changes A changed mapping changes history and should say so. Files: `src/mappings.ts` ### Done when - [ ] Every metric has a formula, mapping and note - [ ] The definition appears beside the figure - [ ] Mapping changes recompute and are recorded - [ ] Same-named metrics with different mappings are distinguished ### 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. ## M3 · Every figure opens to its transactions A number in a report can be drilled to the entries that make it. ### Steps 1. Make drilling work end to end Metric to balances to transactions, in one path. Files: `src/drill.ts` 2. Assert detail sums to the figure For every metric and period, in the suite. Files: `tests/drill-sums.test.ts` 3. Handle zero denominators explicitly A stated result, not an error and not a misleading number. Files: `src/compute.ts` ### Done when - [ ] Every figure opens to its transactions - [ ] Detail sums exactly to the figure across all metrics - [ ] Zero denominators produce a stated result - [ ] Partial periods are labelled ### 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. ## M4 · Comparisons that compare like with like A variance against budget or last year is computed on the same basis. ### Steps 1. Check the comparison basis Definition, mapping and period length. All three. Files: `src/compare.ts`, `src/compare.test.ts` 2. Flag rather than present A mapping artefact looks exactly like a business change. Files: `src/compare.ts` 3. Name the budget version Budgets get revised. Which one you beat matters. Files: `src/budgets.ts` ### Done when - [ ] Definition changes flag the comparison - [ ] Period lengths are checked and labelled - [ ] Budget comparisons name the version - [ ] Variances state their basis ### 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. ## M5 · Forecasts that show their assumptions (production only) A projection states what it assumed and how far it can be trusted. ### Steps 1. Record assumptions with the forecast So a wrong forecast is diagnosable rather than merely wrong. Files: `src/forecast.ts` 2. Show a range, not a line A single line becomes a plan the moment it is on a slide. Files: `src/render.ts` 3. Score past forecasts against outcomes The method's track record is the only honest confidence signal. Files: `src/accuracy.ts` ### Done when - [ ] Forecasts show a range and name their method - [ ] Assumptions are recorded and displayed - [ ] Historic accuracy is reported - [ ] Insufficient history refuses rather than forecasts ### Watch out - A forecast drawn as a single confident line gets treated as a plan, and when it is wrong nobody can say whether the method was bad or the assumptions were. Show a range, record the assumptions with the forecast, and compare past forecasts against outcomes so the method's track record is visible rather than assumed.
# Operations · Fathom HQ ## Backup Imported ledger data can be re-imported, but metric definitions, mapping history and past forecasts with their assumptions cannot, since they represent decisions rather than data. Back those up first. ## Restore Restore and confirm metric definitions and their change history are intact, since restoring values without the definitions that produced them leaves figures nobody can reconcile or defend. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Reconciliation against the source trial balance on every import is the health check. A drift means the import is wrong, and every report after that point is wrong in a way that looks entirely plausible. ## Incident checklist A figure disputed in a meeting is the failure this build is organised to prevent, and the drill path plus the stored definition is what turns it from an argument into a question with an answer. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Balances are confirmed to reconcile exactly against the source - [ ] Every metric is confirmed to have a formula, mapping and note - [ ] Drilled detail is confirmed to sum to every displayed figure - [ ] A definition change is confirmed to flag comparisons - [ ] Forecasts are confirmed to show ranges with assumptions ## Launch constraint Do not market omitted Fathom HQ capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required · secret. Imported financial data for a real business. DATABASE_URL=postgres://localhost:5432/reporting # Required · secret. Read only. This build never writes to the accounting system. LEDGER_API_KEY=a read-only key # Required. Formulas, mappings and definition notes. Agreed before building. METRICS_PATH=./config/metrics.json # Required. For reporting. Source amounts keep their own currency. BASE_CURRENCY=GBP # Required. Present as a constant. A ratio without its definition cannot be reconciled. SHOW_DEFINITION_WITH_FIGURE=always # Required. Below this a forecast is refused rather than produced. MIN_PERIODS_FOR_FORECAST=12
$ choose a build depth, inspect the files, then open the complete pack in your agent
They pay because board/advisor reporting needs accuracy, comparability, and presentable output.
xaccounting integrations
xconsolidation
xtemplates
xforecasting
xreport builder
xadvisor workflows
xreliability
Fathom HQ pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| fathom pro starter | $41.54/workspace | — | 1 company; unlimited users and consolidated groups; each extra company is about $41.54/month |
| fathom pro silver | $221.77/workspace | — | 10 companies included; each extra company is about $21.83/month |
| fathom pro gold | $316.82/workspace | — | 25 companies included; each extra company is about $11.97/month |
| fathom pro platinum | $566.75/workspace | — | 50 companies included; each extra company is about $9.15/month |
| fathom pro enterprise | custom | — | Custom company allowance and commercial terms |
| portfolio, 100 companies | $43.65/workspace | — | Portfolio reporting for up to 100 companies |
| portfolio, 300 companies | $260.49/workspace | — | Portfolio reporting for up to 300 companies |
| portfolio, unlimited | $352.02/workspace | — | Unlimited companies for portfolio reporting |
free tierno free tier; 14-day trial only
billingmonthly only; no annual contract
hidden costsPrices exclude Australian GST. Pro accounts auto-upgrade when company count crosses a band but do not automatically downgrade when companies are removed; additional-company charges apply above the included count.
verified 2026-08-10 · source ↗
Vibecode Fathom HQ
Kinda. The core of Fathom HQ is buildable in a weekend with the prompt on this page, but there are real gaps: accounting integrations, consolidation. Read the honest list above before committing.
How much does Fathom HQ cost?
Fathom HQ costs about $41.54/month (Fathom Pro Starter, checked 2026-08-10), which is $498.48 per year.
What do I lose by replacing Fathom HQ?
Honestly: accounting integrations; consolidation; templates; forecasting; report builder; advisor workflows; reliability. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Fathom HQ?
Yes: Metabase (Open-source BI/dashboard platform that can power custom KPI dashboards.). Using prior art is also vibecoding; the prompt is for when you want it exactly your way.