The review workflow

How a company goes from a folder of workbooks to a closed, locked month: what it sends, what you decide once, and what happens every month after.

What a review is

A review is the close: one run of the pipeline for one company and one month. Haruno reads the company's workbooks, builds one canonical ledger from them, runs integrity checks, and writes the statement package, the reconciliation binder and the dashboard. Then it locks the months it reported on, so a reported figure cannot change later without a record.

Company and client
Haruno calls each company a client; that is the word in folder names, commands and the dashboard.

The review runs in the macOS app today. You can export the dashboard and any report as a PDF.

The stages of a close

StageReadsWrites
ReadEach workbook or CSV in workbooks/<client>/The tables read from each file, in ledger/staging/. Hidden sheets are set aside, not read.
Build the ledgerThose tables, and the company's approved specThe canonical ledger, ledger/ledger.sqlite
ChecksThe ledgerFindings and questions, in the close report
StatementsThe ledger and the spec's statement layoutThe statement package
ReconciliationThe ledger's reconciliation schedulesThe reconciliation binder
LockThe months this close reported, and the unresolved findingsA close record per month: fully reconciled, or reported with exception
DashboardThe ledger, once after every company in the run has closed, for every company with a specThe portfolio dashboard

The checks run before any report is written, and they are the point. A finding does not stop the close: it is the thing you would otherwise have found in week three.

The same run writes the investor report and the adjustments log, and keeps a copy of every source workbook. See what a close writes.

What a company sends

A company sends what its bookkeeper already produces. A financial package and a reconciliation binder are enough to close a month, and plain QuickBooks statement exports can stand in for the package. Every other input is optional, and each one adds a panel or a check.

InputWhat it feeds
Monthly financial package: the pasted export, a hand-filled mapping column and the presentation tabs in one workbookThe ledger and the statement package. The mapping and subtotals are read from the workbook's own formulas.
QuickBooks statement exports: balance sheet, profit and loss, statement of cash flowsThe same, with the mapping inferred from each export's sections. Send all three: the cash-flow statement is kept as the classification it asserts, so the checks can disagree with it.
Trial balance (optional)A check that no account carrying a balance goes unpresented. Sent alone it is refused, because it presents nothing.
Reconciliation binderThe reconciliation binder and its checks. Each schedule is re-added from its own items and tied to the general ledger.
Budget (optional)The budget-against-actual panel. A variance is not a finding.
Operating inputs tab (optional): headcount and new customers by monthThe headcount, OpEx per employee and revenue per employee tiles, and a new-customer count; no statement carries these counts
Billing export, such as Stripe invoices (CSV) (optional)Revenue by customer, and MRR, ARR and churn
CRM export, such as HubSpot deals (CSV) (optional)Bookings and a new-customer count, which are not revenue
Revenue-by-customer spreadsheet (optional)The same MRR, ARR and churn, read as revenue already recognised rather than invoices raised
Profit and Loss by Class export, one file per month (optional)The operating expense by department panel, and a check that each month's class split adds up to the month's activity (class_total_disagrees). A month with no file shows a dash, not zero.
A/R Aging Detail and Transaction List by Vendor (optional QuickBooks reports)The invoices that settle whether a spike month is a prepayment, and, once you name the receivable account, a tie of open invoices to the receivable balance

What the exports must be

  • On an accrual basis. A cash-basis export is refused.
  • Monthly. A profit and loss with quarter columns is refused by name; ask for the report by month.
  • In the company's reporting currency. There is one currency per company and nothing is converted, so an export declaring another currency is refused.
  • Claimed by the spec. A file no spec section names is reported by name and skipped, and the rest of the company still closes.

Hidden sheets are quarantined, not read

A hidden sheet is recorded by name and never imported; in practice hidden sheets often hold data left over from another company. Haruno shows you what one contains only when you ask, and brings it into a company's ledger only after you confirm it belongs to that company. See hidden sheets.

Onboarding a company

A company with no spec cannot be closed. Onboarding produces that spec, once.

  1. Open the review workspace in the macOS app with File ▸ Open Folder… (⌘O): the folder that contains .haruno, not the one above it.
  2. Put the company's workbooks in workbooks/<client>/, for example workbooks/acme/: the monthly financial package, the reconciliation binder, and the budget if there is one.
  3. Ask Haruno to set the company up.
    text
    Acme just signed. Their files are in workbooks — set them up.
  4. Haruno drafts a spec from the workbooks, then stops and shows it to you, starting with the REVIEW list: the questions the workbooks cannot answer. Nothing is applied yet.
  5. Read the REVIEW list and answer it in plain words. Haruno records each answer and shows you the diff.
  6. Approve it, and the close runs.
What a conversation sends
Onboarding through the assistant sends what you type and what the tools print back, such as account names, captions and figures, through Inferara's hosted service to the model provider that answers. The close itself runs on your machine. To keep client data out of the conversation, run the commands yourself: see Running it yourself, below.

For a financial package, the draft is not a guess: a statement states its own arithmetic, so each mapping is read off the SUMIF that puts an account on a line, and each subtotal off the SUM above it. Plain QuickBooks statement exports carry no mapping. For those, the draft infers one from each export's sections, and renaming QuickBooks's captions to the ones you present to investors is the main REVIEW item.

What a spec is

A spec is one YAML file per company, at .haruno/skills/QuickBooksCFO/specs/<client>.yaml. It is the whole record of how that company's books map into statements: which accounts roll into which statement line, how each subtotal composes, which lines are presented against their natural sign, plus the company's name, report scale, reporting currency, materiality and, once you state it, its accounting policy. It is plain text so a person can read it, and a close is reproducible from a spec and a workbook.

The REVIEW list

What a workbook cannot say is judgment. Those questions arrive as a list headed # REVIEW: at the top of the draft. Read it: it is the mapping conversation, and it happens once per company. Typical items:

  • what counts as material, in dollars, not the thousands the statements are presented in;
  • which missing reconciliations are expected, from a list of the accounts the binder has no schedule for;
  • the capitalization policy: threshold, depreciation method, useful lives;
  • which statement lines are expected to be empty, and how the account numbering classifies;
  • readings to confirm: the company's name from the cover sheet, the report scale, and an unlabelled row at the bottom of the balance sheet read as the CHECK row, the tie-out line that should be zero.

Agreeing with a reading writes nothing; a correction is written. Check the scale: a stale ('000) banner accepted as right leaves every figure in every deliverable off by a factor of a thousand. Answers are plain words:

text
Materiality is a thousand dollars, and cash reconciles in QuickBooks.

Haruno records both answers and shows the diff. 'Cash reconciles in QuickBooks' is an exemption: an accepted exception to the check for missing reconciliations, not a mapping. Once the company has a closed month, Haruno prints which accounts it covers; before the first close there is nothing to match against. One that covers no account is reported at the next close as unused_exemption.

Answer the whole list before the first close
Closing with half the list answered buries the real findings among ones about accounts the binder was never going to schedule.

What is never decided for you

Some numbers are a written judgment about a business, and only the company can state them. Where the draft writes a value the books do not state (a materiality, the straight-line method, USD when an export declares no currency), treat it as a default to confirm or correct. Haruno never records a capitalization threshold or a useful life you have not stated. It checks the company's schedules against what you state, and the assistant never answers these on your behalf.

PolicyWhat Haruno does
MaterialityThe draft carries a stated default, flagged in the REVIEW list as a number to correct, not one that was read.
Capitalization thresholdNot drafted. Nothing is reported against a threshold until you write one.
Useful livesNot drafted. The register's own lives are the schedule's choices; quoting them back as the policy would test the schedule against itself.
Depreciation methodStraight-line is the only method computed, so the draft writes it and flags it. Another method is refused rather than checked with the wrong arithmetic.
CurrencyRead from what the exports declare, USD when they declare nothing, and never converted.

With no policy stated, schedule_drift still runs the half that needs no judgment: the schedule the register implies, against the general ledger. Three other calls stay yours too: whether a spike month is a prepayment, whether a balance is reconciled elsewhere, and the reason for reopening a locked month.

The mapping conversation

New accounts appear and mappings drift. The close reports an account no statement line claims as unmapped_active_account (high) if it carries a balance, or unmapped_empty_account (low) if it has been zero so far. From there the flow is always the same: Haruno proposes, you confirm.

  1. Haruno reads the finding against the tie-out. If the CHECK row is off by exactly that account's balance, the unmapped account is the cause, and one mapping clears both the unmapped-account finding and the tie-out finding.
  2. It proposes a line from the account's name, number range and neighbours, with its reasoning.
  3. You confirm; it records the mapping and closes the company again. If the company has closed before, that close is refused until you give a reason: see When the close refuses, below.

Changing your mind

text
1450 Deposits Receivable belongs in Other assets.

Haruno proposes that as reviewable YAML, not as an edit to your spreadsheet:

yaml
mappings:
  "1450": "Other assets"

An approved mapping survives re-ingesting a workbook whose own mapping column is still blank or still wrong. Haruno reports the disagreement rather than restoring the file's answer, and never deletes a mapping you approved.

An exemption is not a mapping

An exemption quiets one question: where this balance is reconciled, why this line is empty, or whether this spike is a prepayment. A reconciliation exemption does not put the account on any statement, so a balance it carries is still missing everywhere and unmapped_active_account still reports it. And a schedule that does not tie to the general ledger is not fixed by any mapping; that exception is real.

Answers are recorded, not hand-edited
Haruno records REVIEW answers and mapping changes with its answer command (ingest.py answer, shown under Running it yourself), and never edits the spec by hand. A hand edit destroys the REVIEW list and its comments, can leave a spec that no longer loads, and makes the approved judgment untraceable. If an answer needs a value that no answer flag carries, Haruno says so and asks you to make that one edit yourself.

Closing a month

After onboarding, every month is the same pipeline with no spec conversation.

  1. Add the month's workbooks to workbooks/<client>/.
  2. Ask Haruno to close the month.
    text
    Close the month for Acme — the new workbooks are in.
  3. Haruno runs the close in one command: it reads the workbooks, applies the approved spec, runs the checks, writes the statement package and binder, locks the months and refreshes the dashboard.
  4. It reads the findings to you before anything circulates. The statement package and binder are written either way; the findings tell you what to settle before they go out.

The close report

The close prints one report per company:

LineWhat it tells you
First lineThe company's name, whether it closed, workbooks read, findings and questions counted
read but not appliedEach file no spec section claims, by name
RESTATED, and other notesRestated figures from a re-sent workbook, and mappings the file and the spec disagree about
[high], [medium], [low]One finding per line: severity, check, subject and detail
?One question per line, such as prepaid_candidate: asked, never counted as findings
re-openedThe months reopened on your reason
lockedThe months locked and the close state, with the unresolved checks named
restatedHow many figures moved; each is a row in the adjustments log
wroteEach file written: statements, binder, investor report, adjustments log, dashboard

A lock line reads like this (synthetic demo data):

text
locked 2023-02-28 … 2026-06-30 (41 periods) — reported with exception: 3 unresolved finding(s) (presentation_tieout, recon_exceptions, unmapped_active_account); re-opening one needs a reason (`ingest.py reopen`)

A close with findings is a successful close. Clean means no high or medium finding. See severities.

When the close refuses

Once a month is reported, it is locked. An import that would change, drop or add a figure in a locked month stops that company's close: nothing is written to its ledger and no report is refreshed for it. Other companies closed in the same run carry on. That includes an approved mapping or a change to how a statement line adds up, both of which move the statements although no figure in the workbooks changed, and a corrected export for a locked month. The refusal names the months and the figures that would move, with both values: the first twelve in full, then a count of the rest. It also prints the command that would reopen those months. An import stating the same figures is silent, so re-running an unchanged close is unaffected.

text
2023-02-28 … 2026-06-30 are CLOSED, and this import would change what have
been reported for them. Nothing was written.

  2023-02-28  presentation  BS line 'CHECK': -6,600.00 -> 0.00
  2023-02-28  presentation  BS line 'Other assets': 19,500.00 -> 26,100.00
  2023-02-28  presentation  BS line 'TOTAL ASSETS': 5,394,868.29 -> 5,401,468.29

An excerpt from a demo close on synthetic data, after the 1450 mapping above was approved for a company with 41 locked months.

Giving a reason

Haruno relays the refusal and stops. It does not reopen a month on its own or write the reason for you: the reason is kept for good beside the figure it let change, so it has to be your sentence. To proceed, say why:

text
1450 was unmapped; approved as Other assets.

That reopens exactly the months the refusal named, keeps the reason against each, and closes them again. A placeholder, such as the bracketed example the refusal prints, is refused. Give no reason and nothing changes. If figures moved, every later statement package carries a Restated periods table, and each moved figure is a row in the adjustments log. See the lock.

A month that does not tie is still locked

A month whose statements do not tie is not refused. It is locked like any reported month, and the lock records it as reported with exception, with the unresolved checks named, instead of fully reconciled. The lock is not a verdict on the books. See close states.

Other refusals are questions

The close also refuses input it cannot read without guessing, such as a binder whose contents page carries dates from two different months, and says why. Haruno asks you and records the answer in the spec. It never edits the company's workbook to make the error go away.

Several companies

One workspace holds every company you review: one folder under workbooks/ and one spec each, and the portfolio dashboard across them all. To onboard several at once:

text
These are my client spreadsheets. Import everything in workbooks/ — one client per folder. For each: read their workbooks, propose the account mapping and show me the REVIEW notes before applying anything, then close the month and refresh the dashboard.
  • Every spec is drafted first and shown together. A REVIEW item the drafts share is asked once.
  • A shared answer is written to each spec; an exception for one company stays that company's.
  • Every company closes in one run, independently, so a malformed workbook fails alone.
  • The dashboard is refreshed once at the end. Closing one company refreshes the whole portfolio.
  • On the dashboard, one spec per company: if two specs name the same company, the dashboard is built from one of them, and the close says which file it kept, why, and which it left out.

Nothing is converted between companies, and portfolio totals do not add companies that report in different currencies. See the portfolio view.

Running it yourself

The pipeline is plain Python in your workspace, and it is yours. From the workspace folder:

bash
# Read a workbook without importing it
python3 .haruno/skills/QuickBooksCFO/scripts/ingest.py inspect "workbooks/acme/financial-package.xlsx"

# Draft the client's spec from their own workbooks, then read the REVIEW block
python3 .haruno/skills/QuickBooksCFO/scripts/ingest.py propose-spec "workbooks/acme/financial-package.xlsx" --spec-name acme --out .haruno/skills/QuickBooksCFO/specs/acme.yaml

# Answer a REVIEW item with the answer command, not by editing the file
python3 .haruno/skills/QuickBooksCFO/scripts/ingest.py answer --spec .haruno/skills/QuickBooksCFO/specs/acme.yaml --materiality 1000

# Close: staging, ledger, checks, statements, binder, dashboard
python3 .haruno/skills/QuickBooksCFO/scripts/close.py --workspace . --client acme

It needs a standard Python 3 and nothing else: no installs, no accounts, no network. Everything it reads and writes stays in the workspace folder. Quote every file path, since real export names carry spaces and parentheses. To proceed past a lock refusal, add --reopen-reason with your sentence to the close command; to close every company, use --all in place of --client.

Why you might: the close itself runs on your machine and transmits no client data, with or without the assistant. A conversation sends what you send: what you type and what the tools print back (account names, captions, figures, findings) goes through Inferara's hosted service to the model provider that answers. Settle this against your own clients' confidentiality terms before pointing it at real books. To keep the benefit without that half, run these commands yourself and use the assistant only for parts with no client data in them. See where your data goes.

Next steps

Go deeper on what a close finds, shows and records.