Documentation
Tagging
Hforecast uses hledger tags on journal postings to classify income and deductions for tax calculations.
How tags work
Tags serve a dual role:
- Output: When the simulation creates transactions (paychecks, contributions, withdrawals), it tags each posting with the appropriate classification.
- Input: The tax engine reads year-to-date totals by querying journal tags (e.g.,
tag:earnings=incometo compute gross income).
In annual mode, this is a closed loop — the simulation creates all transactions, so tags are always present. In daily mode, the simulation starts from an existing journal with real transactions. If those transactions lack the expected tags, tax calculations will be wrong.
Default tags
| TagMap field | Default value | Used for |
|---|---|---|
earnings |
earnings:income |
Gross income (salary, wages) |
preTaxDeductions |
deductions:pre-tax |
401k contributions, HSA contributions |
postTaxDeductions |
deductions:post-tax |
Post-tax payroll deductions |
medicareExemptDeductions |
medicare-exempt:yes |
Deductions exempt from Medicare tax |
preTax401kContributions |
401k:contribution |
401k-specific contributions |
retirementWithdrawal |
withdrawal:retirement |
Withdrawals from retirement accounts |
opening |
oc:open |
Opening balance transactions |
closing |
oc:close |
Closing balance transactions |
Tagging existing transactions
If you use daily mode with an existing journal, tag your income and deduction postings. For example, a paycheck entry:
2025-01-15 Employer Paycheck
income:salary $-5000 ; earnings:income
assets:retirement:401k $500 ; deductions:pre-tax, 401k:contribution
assets:hsa $200 ; deductions:pre-tax
expenses:taxes:federal $1000
expenses:taxes:state $400
expenses:taxes:social-security $310
expenses:taxes:medicare $72
assets:bank:checking $2518
The tags after the semicolons (;) are what the tax engine reads.
Tagging a loan account
Loans (mortgages, auto loans, student loans) are declared by tagging their account directive, not individual postings. The simulation reads these tags to build an amortization schedule: a level monthly payment that draws the balance down over the remaining term, splitting each payment into interest and principal.
Tag the liability’s account directive with hforecast-account-type:loan plus the loan’s terms:
account liabilities:mortgage:home
; hforecast-account-type:loan
; hforecast-loan-rate:6.5
; hforecast-loan-term:30
; hforecast-loan-start:2020-01-01
Or on one line, comma-separated (hledger reads a tag value up to the next comma):
account liabilities:mortgage:home ; hforecast-account-type:loan, hforecast-loan-rate:6.5, hforecast-loan-term:30, hforecast-loan-start:2020-01-01
The loan tags are:
| Tag | Value | Meaning |
|---|---|---|
hforecast-account-type: |
loan |
Marks the account as an amortizing liability |
hforecast-loan-rate: |
a percentage, e.g. 6.5 for 6.5% |
The annual interest rate |
hforecast-loan-term: |
years, e.g. 30 (fractional allowed) |
The loan’s original term |
hforecast-loan-start: |
a date, YYYY-MM-DD |
When the loan was originated |
hforecast-property: |
a property account name | On a loan account, links it to the property it finances (paid off on that property’s sale) |
Two conventions are worth calling out:
- The rate is a percentage, not a fraction. Write
hforecast-loan-rate:6.5for 6.5%, not0.065. (This differs from the YAMLinterestRate:field, which is a fraction.) - The term is the original term; the start date shortens it. A 30-year mortgage opened in 2020 does not have 30 years left. Give
hforecast-loan-start:and the tool subtracts the time already elapsed, so a plan run in mid-2026 sees ~23.5 years remaining and amortizes the current balance over that. Omithforecast-loan-start:andhforecast-loan-term:is taken as the years still remaining — you then have to shorten it by hand each year.
The balance itself is not a tag: seed it either from the journal (balance: infer-from-journal in YAML, which reads the account’s current balance) or as a literal opening amount.
Linking a mortgage to its property
So a property sale can pay off the right loan, tag the loan account with hforecast-property: pointing at the property it finances (the hub-and-spoke direction: the loan points at the property, and a property can carry more than one linked lien):
account assets:real-estate:home ; hforecast-account-type:property
account liabilities:mortgage:home ; hforecast-account-type:loan, hforecast-property:assets:real-estate:home, hforecast-loan-rate:6.5, hforecast-loan-term:30, hforecast-loan-start:2020-01-01
All of these terms can also be given in YAML instead of tags (interestRate:, term:, propertyAccount:); the tag form is convenient when you keep the facts next to the account in the journal itself. Tags and YAML compose — YAML wins where both are present.
Customizing tags
Override the defaults in the tags section of your hforecast.yaml:
tags:
earnings: type:salary
preTaxDeductions: category:pretax
Automatic tag detection
When inferFromJournal is enabled (the default), the tool scans your journal for tag conventions on income and retirement account postings. If it finds consistent tags, it maps them to the TagMap automatically. If your income postings have no tags at all, a warning is printed to stderr.