Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions examples/personal-finance/agents/personal-finance/SOUL.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,21 @@ When two transactions are the two legs of an internal transfer between accounts
- Activity inside ISAs is not reportable for income tax or CGT. Capture for the user's net-worth picture but flag `tax_relevance=none` on related transactions.
- SIPP contributions are reportable for higher-rate relief; growth inside the wrapper is not.

## Foreign currency (SA106)
- When a transaction or dividend lands in a non-GBP currency: set `transaction.currency='GBP'` (the converted amount), keep the original in `native_amount` + `native_currency`, and record the `fx_rate_to_gbp` + `fx_rate_source` used. HMRC accepts monthly average rates (gov.uk/government/publications/hmrc-exchange-rates) — that's the safe default when you don't have a specific transaction-day rate.
- Foreign-source income (US dividends, EU rentals, etc.) should also have `income_source.country` set to the source country, plus `foreign_tax_paid` + `foreign_tax_currency` + `withholding_jurisdiction` so SA106 FTCR can be computed.
- Treaty rate (e.g. 15% for US/UK dividend treaty): record in `treaty_rate_applied` so the agent can flag over-withholding (e.g. 30% withheld instead of 15%) — that's recoverable but not via the SA return.

## Allowance budgeting
- Maintain one `allowance_window` entity per (active tax_year, allowance kind) for: ISA subscription (£20k), dividend allowance (£500), personal savings allowance (£1,000/£500/£0 by band), CGT annual exempt amount (£3,000), pension annual allowance (£60k + 3-year carry-forward), property income allowance (£1,000), trading allowance (£1,000), and personal allowance (£12,570 — tapered above £100k income).
- When you write a transaction or contribution that affects an allowance, also write an `accumulates_in` link to the right `allowance_window` and update `used` + `remaining`.
- "How much ISA budget do I have left?" should be a single read of the ISA allowance_window for the active year — not a cross-table aggregation each time.

## Filing timeline
- For each tax year, create one or more `filing_obligation` entities for SA100 (paper, online, balancing payment, POA1, POA2). Use them for proactive reminders.
- HMRC payments to/from the user (balancing, POA1, POA2, refunds) become `payment` entities linked via `settles → filing_obligation` and `payment_for → tax_year`.
- When the user uploads or volunteers an SA302, capture a `tax_assessment(source='hmrc_sa302')` so we can reconcile against our own `agent_projection` assessment for the same year.

## Ingestion paths
1. **Forwarded Gmail** — bank confirmations, broker contract notes, dividend notices, P60/P11D, mortgage statements. Watcher `personal-finance.gmail-tx` parses these automatically. Verify gaps and ask the user to forward what's missing.
2. **WhatsApp file uploads** — statements, contract notes, P60s. Follow the playbook in `INGESTION.md`: fetch the `downloadUrl`, extract text with pdftotext/csvtk (both in the agent's nix env), extract structured rows, post-validate totals and date range, then create entities with `parsed_from` provenance links. If totals don't reconcile, surface it to the user before committing.
Expand Down
11 changes: 11 additions & 0 deletions examples/personal-finance/models/accumulates_in.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
version: 1
type: relationship
slug: accumulates_in
name: Accumulates In
description: A transaction or contribution counts toward an allowance window. E.g. an ISA deposit accumulates_in the year's isa_subscription window; a pension contribution accumulates_in the year's pension_annual_allowance window.
metadata_schema:
type: object
properties:
amount_counted:
type: string
description: Decimal — usually equals the underlying transaction amount, but can differ (e.g. employer pension match counts toward AA but not toward your contribution)
53 changes: 53 additions & 0 deletions examples/personal-finance/models/allowance_window.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
version: 1
type: entity
slug: allowance_window
name: Allowance Window
description: A materialized accumulator for one tax allowance over one tax year. Lets the agent answer "how much ISA budget left this year?" or "how much pension annual allowance can I still use?" instantly without recomputing across all underlying transactions/contributions every time.
icon: gauge
color: "#84CC16"
metadata_schema:
type: object
required:
- kind
- cap
- used
properties:
kind:
type: string
enum:
- isa_subscription
- dividend_allowance
- personal_savings_allowance
- cgt_annual_exempt
- pension_annual_allowance
- property_income_allowance
- trading_allowance
- personal_allowance
description: Which HMRC-defined allowance this window tracks
x-table-label: Allowance
x-table-column: true
cap:
type: string
description: Decimal GBP. The statutory limit for this allowance in this year (e.g. "20000" for ISA, "60000" for pension AA).
x-table-label: Cap
x-table-column: true
used:
type: string
description: Decimal GBP consumed so far. Updated on every relevant transaction/contribution write.
x-table-label: Used
x-table-column: true
remaining:
type: string
description: Decimal GBP. cap minus used minus carry_forward used. May go negative if a tapered allowance applies (the agent surfaces this).
x-table-label: Remaining
x-table-column: true
carry_forward_in:
type: string
description: For pension AA — unused allowance carried in from the prior 3 years.
carry_forward_out:
type: string
description: Unused this year, available to carry forward (subject to the allowance's rules).
last_recomputed_at:
type: string
format: date-time
description: When the agent last recomputed used/remaining from underlying entities.
8 changes: 8 additions & 0 deletions examples/personal-finance/models/assessment_for.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
version: 1
type: relationship
slug: assessment_for
name: Assessment For
description: A tax_assessment is for a particular tax_year and subject. Used to anchor agent projections and HMRC SA302 outputs to the same year + filer so they can be compared.
metadata_schema:
type: object
properties: {}
72 changes: 72 additions & 0 deletions examples/personal-finance/models/filing_obligation.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
version: 1
type: entity
slug: filing_obligation
name: Filing Obligation
description: A required tax return or filing the user (or one of their companies) must submit by a deadline. Captures SA100, CT600, SA800, SA900, VAT101, P11D, etc. Lets the agent surface deadlines proactively and reconcile against actual filings.
icon: clock
color: "#F97316"
metadata_schema:
type: object
required:
- return_form
- period_start
- period_end
- deadline_type
- due_date
properties:
return_form:
type: string
enum:
- SA100
- SA800
- SA900
- CT600
- VAT101
- P11D
- PAYE_RTI
- confirmation_statement
x-table-label: Form
x-table-column: true
period_start:
type: string
format: date
period_end:
type: string
format: date
deadline_type:
type: string
enum:
- paper_filing
- online_filing
- balancing_payment
- poa1
- poa2
- corp_tax_payment
- corp_tax_filing
- vat_payment
- registration
x-table-label: Deadline
x-table-column: true
due_date:
type: string
format: date
x-table-label: Due
x-table-column: true
status:
type: string
enum:
- upcoming
- reminded
- overdue
- filed
- paid
- waived
default: upcoming
x-table-label: Status
x-table-column: true
completed_date:
type: string
format: date
hmrc_reference:
type: string
description: HMRC submission receipt or reference number, once filed.
14 changes: 13 additions & 1 deletion examples/personal-finance/models/income_source.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@ metadata_schema:
x-table-column: true
country:
type: string
description: ISO 3166-1 alpha-2; relevant for SA106 foreign income
description: ISO 3166-1 alpha-2; non-GB triggers SA106
foreign_tax_paid:
type: string
description: Decimal — total foreign tax withheld at source for the tax year, in foreign_tax_currency. Drives Foreign Tax Credit Relief (FTCR) on SA106.
foreign_tax_currency:
type: string
description: ISO 4217 of the foreign_tax_paid amount. Usually matches the income currency.
withholding_jurisdiction:
type: string
description: ISO 3166-1 alpha-2 of the country that withheld the tax. May differ from `country` (e.g. US dividends paid via a UK broker — withheld in US, paid to UK).
treaty_rate_applied:
type: string
description: Decimal — treaty withholding rate already applied at source (e.g. "0.15" for the 15% US/UK treaty rate on dividends). Used to flag over-withholding that may be recoverable from the source country.
notes:
type: string
8 changes: 8 additions & 0 deletions examples/personal-finance/models/obligation_for.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
version: 1
type: relationship
slug: obligation_for
name: Obligation For
description: A filing_obligation belongs to a tax_year and a subject ($member or company). The same SA100 obligation is "for" the user's $member and "for" their tax_year.
metadata_schema:
type: object
properties: {}
54 changes: 54 additions & 0 deletions examples/personal-finance/models/payment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
version: 1
type: entity
slug: payment
name: Payment
description: A payment to or from HMRC — balancing payments, payments on account, corporation tax, VAT remittances, refunds. Distinct from generic transactions because it ties to filing_obligation and tax_assessment for reconciliation.
icon: credit-card
color: "#DB2777"
metadata_schema:
type: object
required:
- amount
- currency
- date
- direction
- kind
properties:
amount:
type: string
description: Decimal — always positive
x-table-label: Amount
x-table-column: true
currency:
type: string
default: GBP
date:
type: string
format: date
x-table-label: Date
x-table-column: true
direction:
type: string
enum: [to_hmrc, from_hmrc]
x-table-label: Direction
x-table-column: true
kind:
type: string
enum:
- balancing_payment
- poa1
- poa2
- corp_tax
- vat
- paye_nic
- refund
- penalty
- interest
x-table-label: Kind
x-table-column: true
reference:
type: string
description: HMRC payment reference (UTR + K, or CT-specific accounting reference)
method:
type: string
enum: [bank_transfer, direct_debit, debit_card, cheque, paye_coding]
11 changes: 11 additions & 0 deletions examples/personal-finance/models/settles.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
version: 1
type: relationship
slug: settles
name: Settles
description: A payment settles part or all of a filing_obligation (e.g. balancing_payment settles SA100 balancing). One filing_obligation may be settled by multiple payments.
metadata_schema:
type: object
properties:
portion_settled:
type: string
description: Decimal — how much of the obligation this payment covers (defaults to the full payment amount when not specified)
42 changes: 42 additions & 0 deletions examples/personal-finance/models/tax_assessment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
version: 1
type: entity
slug: tax_assessment
name: Tax Assessment
description: A computed or HMRC-issued tax position for one tax year, one subject. Captures SA302 outputs (HMRC's view) + agent-computed projections (our view) so we can reconcile and surface differences.
icon: file-bar-chart
color: "#7C3AED"
metadata_schema:
type: object
required:
- source
- total_tax_due
- computed_at
properties:
source:
type: string
enum: [agent_projection, hmrc_sa302, hmrc_ct600_acknowledgement, manual]
description: Where this assessment came from. agent_projection = our running estimate; hmrc_* = the authority's number.
x-table-label: Source
x-table-column: true
total_income:
type: string
description: Decimal GBP — sum of all income sources before allowances
total_tax_due:
type: string
description: Decimal GBP — final tax liability for the year
x-table-label: Tax due
x-table-column: true
tax_paid_at_source:
type: string
description: PAYE + dividend tax withheld + foreign tax credit
balancing_owed:
type: string
description: total_tax_due - tax_paid_at_source - poa_paid
allowances_used:
type: object
description: Per-allowance breakdown (personal_allowance, dividend_allowance, psa, cgt_aea, etc.)
computed_at:
type: string
format: date-time
hmrc_reference:
type: string
12 changes: 12 additions & 0 deletions examples/personal-finance/models/transaction.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,15 @@ metadata_schema:
is_personal:
type: boolean
default: true
native_amount:
type: string
description: Decimal amount in the foreign currency, when currency != GBP. Keep alongside `amount` so the agent can show both numbers and recompute if rates need correcting.
native_currency:
type: string
description: ISO 4217 currency code of native_amount (e.g. "USD", "EUR"). When set, the `currency` field on this transaction is GBP and native_currency is the original.
fx_rate_to_gbp:
type: string
description: Decimal — the FX rate snapshot used to convert native_amount to amount (GBP). Source the rate from the transaction date. Required when native_currency is set so HMRC-aligned conversion is auditable.
fx_rate_source:
type: string
description: Where the FX rate came from (e.g. "hmrc_monthly", "broker_statement", "ecb_daily"). Helps reconcile if HMRC's published rate differs.
Loading