1501 lines
33 KiB
Markdown
1501 lines
33 KiB
Markdown
# Handover note: household finance ledger platform
|
|
|
|
## 1. Goal of the project / conversation
|
|
|
|
Build a household finance platform that combines transaction import, manual entry, document/email capture, project budgeting, promised-payment tracking, recurring-expense tracking, categorization, budgeting, debt/savings planning, and explainable settlement between household members.
|
|
|
|
The platform should handle a real-world household setup with:
|
|
|
|
```text
|
|
- shared household account(s)
|
|
- personal bank accounts
|
|
- extra fourth bank account
|
|
- personal credit card
|
|
- PayPal accounts
|
|
- Klarna Card / Pay Later style obligations
|
|
- recurring bills and memberships
|
|
- project-based budgets, e.g. holidays, renovation, moving
|
|
```
|
|
|
|
The user wants this to become useful as:
|
|
|
|
```text
|
|
- an API/backend
|
|
- a web frontend
|
|
- a mobile app
|
|
- local-first or privacy-preserving connectors where possible
|
|
```
|
|
|
|
A key privacy goal is that **bank connections should ideally happen locally**, so the central server does not need to store plaintext or unencrypted banking credentials, tokens, or sensitive banking connection material.
|
|
|
|
The conceptual core is not “transaction import.” The core is a **household financial ledger** that distinguishes:
|
|
|
|
```text
|
|
cash movement
|
|
purchase / consumption event
|
|
liability
|
|
promised future payment
|
|
recurring commitment
|
|
refund
|
|
reimbursement
|
|
settlement between household members
|
|
project budget item
|
|
source evidence
|
|
```
|
|
|
|
## 2. Current state
|
|
|
|
No code, files, repository, database schema, or artifacts have been created yet in this conversation.
|
|
|
|
The project is at the **architecture and product-definition stage**.
|
|
|
|
The following concepts have been agreed as important:
|
|
|
|
```text
|
|
- manual entry must be first-class
|
|
- imports/connectors are useful but incomplete
|
|
- duplicate detection is central
|
|
- IMAP ingestion is useful for PayPal, Klarna, invoices, receipts, subscriptions
|
|
- receipt/photo scanning is useful but should be reviewed by the user
|
|
- AI/categorization/OCR should run locally
|
|
- projects should be first-class, not just tags
|
|
- project budgets must track budgeted, committed, paid, reconciled, settled, and still-due amounts separately
|
|
- settlement must be explainable
|
|
- private and shared visibility must be handled carefully
|
|
```
|
|
|
|
Known external access situation from earlier discussion:
|
|
|
|
```text
|
|
German bank accounts:
|
|
FinTS/HBCI and/or PSD2 aggregators are plausible.
|
|
|
|
GLS/comdirect:
|
|
FinTS likely relevant.
|
|
|
|
comdirect:
|
|
also has its own API, but it is not bank-independent.
|
|
|
|
Klarna Bank Account / Guthaben:
|
|
likely accessible through PSD2/AIS route.
|
|
|
|
Klarna Card as credit/BNPL card:
|
|
no known public consumer transaction API.
|
|
Treat as weak/incomplete data source.
|
|
Fallbacks: IMAP, statements/PDFs, GDPR export, manual entry, bank-side repayment reconciliation.
|
|
|
|
PayPal:
|
|
possible routes include PayPal API, CSV export, and IMAP/email parsing.
|
|
Third-party/multi-user API access may require partner status.
|
|
```
|
|
|
|
## 3. Important decisions already made
|
|
|
|
### Product decisions
|
|
|
|
The platform should be modeled as an **evidence-to-ledger system**, not as direct transaction import.
|
|
|
|
Recommended flow:
|
|
|
|
```text
|
|
raw source observation
|
|
-> extraction/parsing
|
|
-> candidate event
|
|
-> duplicate/link/reconciliation engine
|
|
-> accepted household ledger event
|
|
-> categorization
|
|
-> project assignment
|
|
-> split/settlement logic
|
|
-> budget and cash-flow views
|
|
```
|
|
|
|
Every imported or manually entered item should first become a **raw observation** or **candidate**, not an immediately final expense.
|
|
|
|
### Accounting decisions
|
|
|
|
Do not collapse these concepts:
|
|
|
|
```text
|
|
expense
|
|
payment
|
|
cash settlement
|
|
promised future payment
|
|
liability
|
|
recurring commitment
|
|
refund
|
|
reimbursement
|
|
transfer
|
|
project budget item
|
|
```
|
|
|
|
A bank debit from PayPal 30 days after a purchase is usually **not a new expense**. It may settle a previously recorded PayPal/Klarna obligation.
|
|
|
|
A credit card bill payment is usually **not a new expense**. It reduces card liability.
|
|
|
|
A reimbursement between household members is usually **not income**. It settles an interpersonal balance.
|
|
|
|
### Privacy/security decisions
|
|
|
|
Bank connections should ideally happen locally.
|
|
|
|
The server should avoid storing:
|
|
|
|
```text
|
|
- plaintext bank login credentials
|
|
- unencrypted bank tokens
|
|
- TAN/SCA secrets
|
|
- unnecessary raw banking payloads outside encrypted storage
|
|
```
|
|
|
|
Potential model:
|
|
|
|
```text
|
|
local connector agent
|
|
-> connects to FinTS/PSD2/file imports locally
|
|
-> normalizes/encrypts data
|
|
-> sends sanitized observations/events to backend
|
|
```
|
|
|
|
The backend can still store encrypted raw observations if needed, but the design should support local-only sensitive connectors.
|
|
|
|
### Import decisions
|
|
|
|
Manual entry, CSV import, IMAP, PDF import, receipt photo OCR, and API connectors should all feed the same raw observation pipeline.
|
|
|
|
CSV and manual import are not fallback-only features; they are primary ingestion methods.
|
|
|
|
### Project budgeting decisions
|
|
|
|
Projects are first-class entities.
|
|
|
|
A project must support:
|
|
|
|
```text
|
|
budget lines
|
|
planned items
|
|
actual events
|
|
partial payments
|
|
future due payments
|
|
recurring project-related payments
|
|
participants
|
|
split rules
|
|
settlement
|
|
evidence
|
|
open actions
|
|
scenario estimates
|
|
```
|
|
|
|
Projects should work for:
|
|
|
|
```text
|
|
holidays
|
|
renovations
|
|
moving
|
|
large purchases
|
|
weddings/events
|
|
debt repayment
|
|
savings goals
|
|
child/pet/medical/education expenses
|
|
```
|
|
|
|
## 4. Files, code, or artifacts involved
|
|
|
|
None yet.
|
|
|
|
There is no repository, package structure, codebase, database migration, OpenAPI spec, UI design file, or generated artifact from this conversation.
|
|
|
|
The new coding agent should start by creating the initial repository structure.
|
|
|
|
## 5. Relevant architecture / data model / APIs / commands
|
|
|
|
### Recommended high-level architecture
|
|
|
|
Suggested initial architecture:
|
|
|
|
```text
|
|
monorepo/
|
|
apps/
|
|
web/ # web frontend
|
|
mobile/ # app later; can be deferred
|
|
api/ # backend API
|
|
local-agent/ # local bank/import connector agent
|
|
packages/
|
|
domain/ # shared domain types, validation schemas
|
|
importers/ # CSV, IMAP, PDF/OCR parsers
|
|
matching/ # deduplication and reconciliation logic
|
|
categorization/ # local rules/classifier interfaces
|
|
ui/ # shared UI components, optional
|
|
infra/
|
|
docker/
|
|
migrations/
|
|
```
|
|
|
|
No specific stack has been chosen yet. For fast development, a reasonable starting assumption would be:
|
|
|
|
```text
|
|
Language:
|
|
TypeScript for API/web/shared domain.
|
|
|
|
Backend:
|
|
Node.js with Fastify, NestJS, or similar.
|
|
|
|
Frontend:
|
|
React / Next.js.
|
|
|
|
Database:
|
|
PostgreSQL for server mode.
|
|
SQLite for local-agent/local-first mode or test fixtures.
|
|
|
|
Mobile:
|
|
Defer initially; later React Native, Expo, Flutter, or native app.
|
|
|
|
Local agent:
|
|
TypeScript or Python.
|
|
Python may be better for FinTS, OCR, PDF parsing, IMAP prototypes.
|
|
TypeScript may be better for shared validation and app packaging.
|
|
|
|
AI/OCR:
|
|
local only.
|
|
No cloud AI dependency.
|
|
```
|
|
|
|
These are assumptions, not confirmed choices.
|
|
|
|
### Core domain entities
|
|
|
|
Use explicit domain entities. Do not store everything as one flat transaction table.
|
|
|
|
#### `User`
|
|
|
|
```text
|
|
id
|
|
display_name
|
|
email
|
|
household_id
|
|
created_at
|
|
```
|
|
|
|
#### `Household`
|
|
|
|
```text
|
|
id
|
|
name
|
|
default_currency
|
|
created_at
|
|
```
|
|
|
|
#### `Account`
|
|
|
|
Represents checking accounts, shared accounts, PayPal, credit cards, Klarna, cash wallets, or virtual liability accounts.
|
|
|
|
```text
|
|
id
|
|
household_id
|
|
owner_user_id nullable
|
|
name
|
|
account_type # checking, shared_checking, paypal, credit_card, klarna, cash, liability, savings
|
|
institution_name
|
|
currency
|
|
visibility_mode # private, shared_summary, shared_full, settlement_only
|
|
is_active
|
|
created_at
|
|
```
|
|
|
|
#### `RawObservation`
|
|
|
|
Every imported file row, email, PDF, receipt photo, API transaction, notification, or manual draft.
|
|
|
|
```text
|
|
id
|
|
household_id
|
|
source_type # bank_csv, fints, paypal_csv, paypal_email, klarna_email, receipt_photo, pdf_invoice, manual, api
|
|
source_account_id nullable
|
|
owner_user_id nullable
|
|
observed_at nullable
|
|
received_at
|
|
raw_hash
|
|
raw_text nullable
|
|
raw_file_path nullable
|
|
raw_payload_json nullable
|
|
parsed_json nullable
|
|
parser_name nullable
|
|
parser_version nullable
|
|
confidence
|
|
status # new, parsed, candidate_created, ignored, duplicate_source, error
|
|
created_at
|
|
```
|
|
|
|
#### `FinancialEvent`
|
|
|
|
The accepted or candidate economic event.
|
|
|
|
```text
|
|
id
|
|
household_id
|
|
event_type # expense, income, transfer, liability_payment, promised_payment, refund, reimbursement, fee, interest, adjustment
|
|
status # candidate, confirmed, pending, booked, settled, cancelled, ignored
|
|
event_date
|
|
booking_date nullable
|
|
due_date nullable
|
|
amount
|
|
currency
|
|
merchant nullable
|
|
counterparty nullable
|
|
description
|
|
category_id nullable
|
|
project_id nullable
|
|
created_by_user_id nullable
|
|
created_at
|
|
updated_at
|
|
```
|
|
|
|
Important: `FinancialEvent` is not necessarily a bank transaction.
|
|
|
|
#### `EventObservationLink`
|
|
|
|
Links evidence to event.
|
|
|
|
```text
|
|
id
|
|
event_id
|
|
observation_id
|
|
relation # evidence_for, duplicate_source, imported_from, parser_source, statement_source
|
|
confidence
|
|
created_at
|
|
```
|
|
|
|
#### `EventRelation`
|
|
|
|
Links events to each other.
|
|
|
|
```text
|
|
id
|
|
from_event_id
|
|
to_event_id
|
|
relation_type # duplicate_of, same_economic_event_as, settles, funds, reimburses, refunds, reverses, replaces_pending_authorization, part_of_statement
|
|
confidence
|
|
created_by # system, user
|
|
created_at
|
|
```
|
|
|
|
#### `SplitLine`
|
|
|
|
Represents economic responsibility.
|
|
|
|
```text
|
|
id
|
|
event_id
|
|
user_id
|
|
share_amount
|
|
share_percent nullable
|
|
reason # equal_split, custom, private, income_ratio, project_default, manual
|
|
created_at
|
|
```
|
|
|
|
#### `Category`
|
|
|
|
Use hierarchical categories.
|
|
|
|
```text
|
|
id
|
|
household_id
|
|
parent_id nullable
|
|
name
|
|
path # e.g. "holiday > restaurant"
|
|
is_active
|
|
```
|
|
|
|
#### `Tag`
|
|
|
|
Use tags for flexible labels. Do not use tags as project replacement.
|
|
|
|
```text
|
|
id
|
|
household_id
|
|
name
|
|
```
|
|
|
|
#### `Project`
|
|
|
|
First-class project/budget object.
|
|
|
|
```text
|
|
id
|
|
household_id
|
|
name # e.g. "Stockholm 2026"
|
|
description
|
|
start_date nullable
|
|
end_date nullable
|
|
default_split_rule_id nullable
|
|
status # idea, planning, active, completed, cancelled, archived
|
|
currency
|
|
created_at
|
|
updated_at
|
|
```
|
|
|
|
#### `ProjectParticipant`
|
|
|
|
```text
|
|
id
|
|
project_id
|
|
user_id
|
|
role
|
|
default_share_percent nullable
|
|
```
|
|
|
|
#### `ProjectBudgetLine`
|
|
|
|
Budget category within project.
|
|
|
|
```text
|
|
id
|
|
project_id
|
|
category_id nullable
|
|
name # Transport, Accommodation, Food, Souvenirs, Buffer
|
|
budgeted_amount
|
|
committed_amount_cached nullable
|
|
paid_amount_cached nullable
|
|
sort_order
|
|
notes
|
|
```
|
|
|
|
#### `ProjectPlannedItem`
|
|
|
|
Planned or committed project item.
|
|
|
|
```text
|
|
id
|
|
project_id
|
|
budget_line_id nullable
|
|
description
|
|
estimated_amount
|
|
committed_amount nullable
|
|
expected_date nullable
|
|
due_date nullable
|
|
expected_payer_account_id nullable
|
|
split_rule_id nullable
|
|
planning_status # idea, estimated, committed, cancelled
|
|
payment_status # unpaid, partially_paid, paid, refunded, partially_refunded
|
|
reconciliation_status # no_evidence, email_seen, receipt_seen, bank_matched, statement_matched, manually_confirmed
|
|
settlement_status # not_shared, unsettled, partially_settled, settled, ignored
|
|
notes
|
|
created_at
|
|
updated_at
|
|
```
|
|
|
|
#### `PaymentLine`
|
|
|
|
Partial payments for project items or events.
|
|
|
|
```text
|
|
id
|
|
planned_item_id nullable
|
|
event_id nullable
|
|
amount
|
|
currency
|
|
date
|
|
paid_by_user_id nullable
|
|
payment_account_id nullable
|
|
source_observation_id nullable
|
|
status # expected, scheduled, paid, reconciled, failed, cancelled
|
|
created_at
|
|
```
|
|
|
|
#### `RecurringCommitment`
|
|
|
|
For insurance, memberships, subscriptions, rent, direct debits, standing orders.
|
|
|
|
```text
|
|
id
|
|
household_id
|
|
provider
|
|
description
|
|
amount_expected
|
|
amount_min nullable
|
|
amount_max nullable
|
|
currency
|
|
interval # monthly, yearly, quarterly, weekly, custom
|
|
next_due_date
|
|
payer_account_id nullable
|
|
split_rule_id nullable
|
|
category_id nullable
|
|
project_id nullable
|
|
notice_period nullable
|
|
renewal_date nullable
|
|
status # active, paused, cancelled, unknown
|
|
matching_rule_json nullable
|
|
created_at
|
|
updated_at
|
|
```
|
|
|
|
#### `PromisedPayment`
|
|
|
|
May be separate from `FinancialEvent`, or implemented as event type `promised_payment`. Prefer separate table only if extra lifecycle detail is needed.
|
|
|
|
```text
|
|
id
|
|
household_id
|
|
related_event_id nullable
|
|
provider # Klarna, PayPal, credit_card, insurance, manual
|
|
amount
|
|
currency
|
|
created_date
|
|
due_date
|
|
expected_funding_account_id nullable
|
|
responsible_user_id nullable
|
|
status # expected, scheduled, debited, reconciled, failed, cancelled
|
|
source_observation_id nullable
|
|
created_at
|
|
updated_at
|
|
```
|
|
|
|
#### `ImportConnection`
|
|
|
|
For connectors and local agent configuration metadata. Do not store plaintext secrets.
|
|
|
|
```text
|
|
id
|
|
household_id
|
|
owner_user_id
|
|
connection_type # fints, psd2_aggregator, imap, paypal_api, csv_folder, local_folder
|
|
display_name
|
|
status # active, needs_auth, error, disabled
|
|
last_sync_at nullable
|
|
last_error nullable
|
|
secret_storage_ref nullable
|
|
runs_locally boolean
|
|
created_at
|
|
updated_at
|
|
```
|
|
|
|
### Key services/classes/components discussed
|
|
|
|
#### Backend/domain services
|
|
|
|
```text
|
|
ObservationIngestionService
|
|
accepts raw inputs and stores RawObservation
|
|
|
|
ParserRegistry
|
|
selects parser by source_type, sender, file type, MIME type, CSV header, etc.
|
|
|
|
CandidateEventService
|
|
turns parsed observations into candidate FinancialEvents
|
|
|
|
DeduplicationService
|
|
exact hash/ID-based duplicate detection
|
|
|
|
SemanticMatchingService
|
|
fuzzy matching across observations/events
|
|
|
|
ReconciliationService
|
|
links PayPal/Klarna/bank/card lifecycle events
|
|
|
|
LedgerService
|
|
confirms events, updates split lines, creates relations
|
|
|
|
SettlementService
|
|
calculates who owes whom globally or per project
|
|
|
|
BudgetService
|
|
calculates category and project budget states
|
|
|
|
ProjectBudgetService
|
|
calculates budgeted/committed/paid/due/settled/reconciled values
|
|
|
|
RecurringCommitmentService
|
|
tracks recurring expenses and expected future payments
|
|
|
|
PromisedPaymentService
|
|
tracks PayPal/Klarna/credit card/direct debit future cash movements
|
|
|
|
CategorizationService
|
|
rules first, local ML later, local LLM suggestions only if available
|
|
|
|
PrivacyVisibilityService
|
|
filters events by account visibility and user permissions
|
|
|
|
AuditLogService
|
|
records merges, edits, category changes, settlement changes
|
|
```
|
|
|
|
#### Importer/connectors
|
|
|
|
```text
|
|
ManualEntryImporter
|
|
CsvImporter
|
|
BankCsvImporter
|
|
PayPalCsvImporter
|
|
ImapImporter
|
|
KlarnaEmailParser
|
|
PayPalEmailParser
|
|
PdfInvoiceImporter
|
|
ReceiptPhotoImporter
|
|
FintsConnector
|
|
Psd2AggregatorConnector
|
|
LocalAgentSyncClient
|
|
```
|
|
|
|
#### Matching functions
|
|
|
|
```text
|
|
computeRawObservationHash(input): string
|
|
|
|
computeNormalizedTransactionFingerprint(parsed): string
|
|
|
|
findExactDuplicates(observation): DuplicateCandidate[]
|
|
|
|
findSemanticMatches(candidateEvent): MatchCandidate[]
|
|
|
|
scoreEventMatch(a, b): number
|
|
|
|
linkEvents(from, to, relationType, confidence): EventRelation
|
|
|
|
markDuplicate(source, duplicateOf): void
|
|
|
|
reconcilePromisedPaymentWithBankDebit(promisedPayment, bankEvent): ReconciliationResult
|
|
|
|
reconcilePayPalPurchaseWithFundingDebit(paypalEvent, bankEvent): ReconciliationResult
|
|
|
|
reconcileCreditCardPurchaseWithStatementPayment(cardEvent, bankEvent): ReconciliationResult
|
|
```
|
|
|
|
#### Project functions
|
|
|
|
```text
|
|
createProject(input): Project
|
|
|
|
createProjectFromTemplate(templateName, participants): Project
|
|
|
|
addProjectBudgetLine(projectId, input): ProjectBudgetLine
|
|
|
|
addProjectPlannedItem(projectId, input): ProjectPlannedItem
|
|
|
|
linkEventToProject(eventId, projectId, budgetLineId?): void
|
|
|
|
addPaymentLine(input): PaymentLine
|
|
|
|
calculateProjectBudget(projectId): ProjectBudgetSummary
|
|
|
|
calculateProjectSettlement(projectId): SettlementSummary
|
|
|
|
calculateProjectCashForecast(projectId): CashForecast
|
|
|
|
calculateProjectOpenActions(projectId): ActionItem[]
|
|
```
|
|
|
|
#### UI components
|
|
|
|
```text
|
|
DashboardPage
|
|
ImportInboxPage
|
|
RawObservationReviewQueue
|
|
CandidateEventReviewCard
|
|
DuplicateMatchReviewCard
|
|
HouseholdLedgerPage
|
|
Transaction/EventDetailPage
|
|
ProjectListPage
|
|
ProjectDetailPage
|
|
ProjectBudgetTable
|
|
ProjectTimeline
|
|
ProjectSettlementPanel
|
|
PromisedPaymentsCalendar
|
|
RecurringCommitmentsPage
|
|
SettlementOverview
|
|
ManualEntryForm
|
|
ReceiptUploadForm
|
|
ImapConnectionSettings
|
|
LocalAgentStatusPanel
|
|
AccountVisibilitySettings
|
|
```
|
|
|
|
### Duplicate/linking relationships
|
|
|
|
Use relationship types instead of one boolean duplicate flag:
|
|
|
|
```text
|
|
duplicate_of
|
|
same_economic_event_as
|
|
settles
|
|
funds
|
|
reimburses
|
|
refunds
|
|
reverses
|
|
part_of_statement
|
|
replaces_pending_authorization
|
|
```
|
|
|
|
### Matching strategy
|
|
|
|
Use layered matching:
|
|
|
|
```text
|
|
Layer A: exact technical deduplication
|
|
- source transaction ID
|
|
- message ID
|
|
- file hash
|
|
- attachment hash
|
|
- raw payload hash
|
|
- CSV import fingerprint
|
|
|
|
Layer B: semantic matching
|
|
- amount
|
|
- currency
|
|
- date window
|
|
- merchant/counterparty similarity
|
|
- order ID
|
|
- invoice number
|
|
- PayPal transaction ID
|
|
- Klarna reference
|
|
- IBAN
|
|
- card last four digits
|
|
- receipt total
|
|
- due date
|
|
- email sender domain
|
|
|
|
Layer C: confidence thresholds
|
|
>= 0.98: auto-link if safe
|
|
0.75 - 0.98: review queue
|
|
< 0.75: keep separate
|
|
```
|
|
|
|
Conservative rule: a bad merge is worse than a missed merge.
|
|
|
|
### Source authority hierarchy
|
|
|
|
Use source confidence.
|
|
|
|
```text
|
|
High authority:
|
|
booked bank/card/PayPal transaction
|
|
official statement
|
|
imported CSV from provider
|
|
|
|
Medium authority:
|
|
invoice PDF
|
|
Klarna/PayPal email
|
|
merchant receipt
|
|
recurring contract register
|
|
|
|
Low authority:
|
|
push notification
|
|
OCR line item
|
|
manual draft
|
|
browser capture
|
|
```
|
|
|
|
### Project budget calculations
|
|
|
|
Each project should calculate:
|
|
|
|
```text
|
|
budgeted_total
|
|
committed_total
|
|
paid_total
|
|
reconciled_total
|
|
settled_total
|
|
due_later_total
|
|
remaining_uncommitted_budget
|
|
current_settlement_balance
|
|
forecast_settlement_balance
|
|
open_actions
|
|
```
|
|
|
|
Example target output:
|
|
|
|
```text
|
|
Project: Stockholm 2026
|
|
|
|
Budgeted total: 2,350 €
|
|
Committed so far: 1,455 €
|
|
Already paid: 775 €
|
|
Still due: 680 €
|
|
Remaining uncommitted: 895 €
|
|
|
|
Current settlement:
|
|
B owes A 187.50 €
|
|
|
|
Forecast settlement:
|
|
B will owe A 527.50 € if A pays all remaining committed items.
|
|
|
|
Open items:
|
|
hotel balance due 2026-08-12
|
|
accommodation receipt missing
|
|
3 restaurant receipts uncategorized
|
|
local transport not yet budgeted
|
|
```
|
|
|
|
### Local AI/OCR assumptions
|
|
|
|
Everything AI-related should run locally.
|
|
|
|
Suggested local stack:
|
|
|
|
```text
|
|
OCR:
|
|
Tesseract and/or PaddleOCR
|
|
|
|
PDF extraction:
|
|
text extraction first
|
|
OCR only for scanned PDFs
|
|
|
|
Invoice extraction:
|
|
template/rule-based parser first
|
|
invoice2data-style approach
|
|
|
|
Categorization:
|
|
deterministic rules first
|
|
local classifier second
|
|
local embeddings for similarity third
|
|
local LLM only as assistant/suggester
|
|
|
|
Local LLM:
|
|
optional
|
|
use only for structured suggestions
|
|
must not directly mutate confirmed ledger state
|
|
```
|
|
|
|
Categorization order:
|
|
|
|
```text
|
|
1. deterministic user rules
|
|
2. previous merchant/account/category memory
|
|
3. local supervised classifier
|
|
4. local embeddings/similarity
|
|
5. local LLM suggestion
|
|
6. user review
|
|
```
|
|
|
|
## 6. Bugs, open questions, and TODOs
|
|
|
|
### Bugs
|
|
|
|
No code exists yet, so there are no known software bugs.
|
|
|
|
### Unresolved compile/runtime issues
|
|
|
|
None yet.
|
|
|
|
No build system, package manager, database, migrations, Docker setup, test runner, or runtime has been created.
|
|
|
|
### Open product questions
|
|
|
|
These should be resolved soon, but do not block initial domain modeling:
|
|
|
|
```text
|
|
- Exact tech stack: TypeScript/Node vs Python backend vs mixed.
|
|
- Whether the local connector agent is mandatory in v1 or introduced after local imports.
|
|
- Whether mobile app starts as PWA or native app.
|
|
- Whether backend is single-household self-hosted first or multi-tenant from day one.
|
|
- Whether data is encrypted field-by-field, database-level, or local-only for sensitive payloads.
|
|
- Whether raw observations are stored centrally, locally, or optionally both.
|
|
- Whether FinTS connector is Python local-agent only or backend-capable.
|
|
- Whether IMAP credentials are stored in local agent or backend encrypted vault.
|
|
- Which user authentication system to use.
|
|
- Whether project budgeting should support multiple currencies in v1.
|
|
```
|
|
|
|
### Important technical TODOs
|
|
|
|
```text
|
|
- Choose initial stack.
|
|
- Create repository scaffold.
|
|
- Define domain schema and migrations.
|
|
- Implement RawObservation ingestion first.
|
|
- Implement manual entry first.
|
|
- Implement candidate event review.
|
|
- Implement exact deduplication.
|
|
- Implement event relations.
|
|
- Implement split lines and settlement calculation.
|
|
- Implement project model and project budget summary.
|
|
- Add CSV import.
|
|
- Add IMAP import.
|
|
- Add PDF/receipt import after core ledger works.
|
|
- Add local categorization rules.
|
|
- Add local-agent architecture before real bank connectors.
|
|
```
|
|
|
|
## 7. Constraints, preferences, naming conventions, style rules
|
|
|
|
### Constraints
|
|
|
|
```text
|
|
- AI must be local-only.
|
|
- Categorization must be local-only.
|
|
- OCR should be local-only.
|
|
- Bank connection should ideally happen locally.
|
|
- Server should not store plaintext banking credentials.
|
|
- Klarna Card cannot be assumed to have API access.
|
|
- CSV/manual/PDF/email imports must be treated as first-class sources.
|
|
- Duplicate detection and reconciliation are central.
|
|
- Privacy modes are required for multi-user support.
|
|
```
|
|
|
|
### Product preferences
|
|
|
|
```text
|
|
- Explainability over opaque automation.
|
|
- Conservative deduplication.
|
|
- User review for uncertain matches.
|
|
- Raw evidence retained.
|
|
- Manual corrections should improve future suggestions.
|
|
- Project budgets should be explicit and useful for planning.
|
|
- Settlement calculations should show reasoning.
|
|
```
|
|
|
|
### Naming conventions
|
|
|
|
Suggested names:
|
|
|
|
```text
|
|
RawObservation:
|
|
untrusted/raw source item.
|
|
|
|
FinancialEvent:
|
|
economic event or candidate event in ledger.
|
|
|
|
EventRelation:
|
|
semantic relationship between events.
|
|
|
|
EventObservationLink:
|
|
evidence relationship between observation and event.
|
|
|
|
PromisedPayment:
|
|
known or inferred future cash movement.
|
|
|
|
RecurringCommitment:
|
|
subscription/contract/standing expected payment.
|
|
|
|
Project:
|
|
first-class budgeting/planning object.
|
|
|
|
ProjectBudgetLine:
|
|
high-level budget bucket.
|
|
|
|
ProjectPlannedItem:
|
|
planned/committed item inside project.
|
|
|
|
PaymentLine:
|
|
partial payment attached to item/event.
|
|
|
|
SplitLine:
|
|
economic responsibility allocation.
|
|
|
|
SettlementSummary:
|
|
who owes whom and why.
|
|
```
|
|
|
|
Do not name everything `Transaction`. Reserve `Transaction` only for provider/bank-specific imported rows if needed, or avoid the term entirely in domain code.
|
|
|
|
Better terms:
|
|
|
|
```text
|
|
BankTransactionObservation
|
|
PayPalTransactionObservation
|
|
FinancialEvent
|
|
LedgerEntry
|
|
PaymentLine
|
|
```
|
|
|
|
### UI style preference
|
|
|
|
No detailed UI style was decided. Functional preference:
|
|
|
|
```text
|
|
- review queues
|
|
- explainable cards
|
|
- budget tables
|
|
- project timelines
|
|
- settlement panels
|
|
- confidence indicators
|
|
- evidence links
|
|
```
|
|
|
|
## 8. Exact next steps
|
|
|
|
### Step 1: Create the initial repo
|
|
|
|
Create a minimal monorepo.
|
|
|
|
Recommended initial structure:
|
|
|
|
```text
|
|
household-finance/
|
|
apps/
|
|
api/
|
|
web/
|
|
packages/
|
|
domain/
|
|
matching/
|
|
db/
|
|
migrations/
|
|
docs/
|
|
handover.md
|
|
```
|
|
|
|
Defer mobile and local-agent folders until the first backend/web skeleton exists, unless the chosen stack strongly favors creating them immediately.
|
|
|
|
### Step 2: Add the handover note
|
|
|
|
Save this handover as:
|
|
|
|
```text
|
|
docs/handover.md
|
|
```
|
|
|
|
### Step 3: Define the domain package
|
|
|
|
Create shared schemas/types for:
|
|
|
|
```text
|
|
Household
|
|
User
|
|
Account
|
|
RawObservation
|
|
FinancialEvent
|
|
EventObservationLink
|
|
EventRelation
|
|
SplitLine
|
|
Category
|
|
Tag
|
|
Project
|
|
ProjectParticipant
|
|
ProjectBudgetLine
|
|
ProjectPlannedItem
|
|
PaymentLine
|
|
RecurringCommitment
|
|
PromisedPayment
|
|
ImportConnection
|
|
```
|
|
|
|
Use validation schemas if using TypeScript, e.g. Zod. Use Pydantic if using Python.
|
|
|
|
### Step 4: Define database schema
|
|
|
|
Create initial migrations for:
|
|
|
|
```text
|
|
households
|
|
users
|
|
accounts
|
|
raw_observations
|
|
financial_events
|
|
event_observation_links
|
|
event_relations
|
|
split_lines
|
|
categories
|
|
tags
|
|
event_tags
|
|
projects
|
|
project_participants
|
|
project_budget_lines
|
|
project_planned_items
|
|
payment_lines
|
|
recurring_commitments
|
|
promised_payments
|
|
import_connections
|
|
audit_log
|
|
```
|
|
|
|
### Step 5: Implement minimal API
|
|
|
|
Minimum endpoints:
|
|
|
|
```text
|
|
POST /households
|
|
GET /households/:id
|
|
|
|
POST /accounts
|
|
GET /accounts
|
|
|
|
POST /observations/manual
|
|
GET /observations/inbox
|
|
|
|
POST /events
|
|
GET /events
|
|
GET /events/:id
|
|
PATCH /events/:id
|
|
|
|
POST /events/:id/confirm
|
|
POST /events/:id/relations
|
|
POST /events/:id/splits
|
|
|
|
GET /settlement/summary
|
|
|
|
POST /projects
|
|
GET /projects
|
|
GET /projects/:id
|
|
POST /projects/:id/budget-lines
|
|
POST /projects/:id/planned-items
|
|
GET /projects/:id/summary
|
|
GET /projects/:id/settlement
|
|
```
|
|
|
|
### Step 6: Implement manual entry flow
|
|
|
|
Manual entry should create:
|
|
|
|
```text
|
|
RawObservation(source_type = "manual")
|
|
-> Candidate FinancialEvent
|
|
-> optional SplitLine
|
|
-> optional Project assignment
|
|
```
|
|
|
|
Manual entry fields:
|
|
|
|
```text
|
|
amount
|
|
currency
|
|
date
|
|
merchant/counterparty
|
|
description
|
|
paid_by account/user
|
|
event_type
|
|
category
|
|
project
|
|
tags
|
|
split rule
|
|
due date if promised payment
|
|
attachment optional
|
|
```
|
|
|
|
### Step 7: Implement exact deduplication
|
|
|
|
Start with:
|
|
|
|
```text
|
|
raw_hash
|
|
source_type + provider_transaction_id
|
|
message_id for email
|
|
file hash
|
|
normalized amount/date/merchant fingerprint
|
|
```
|
|
|
|
Create reviewable duplicate candidates. Do not auto-delete.
|
|
|
|
### Step 8: Implement settlement calculation
|
|
|
|
Start simple:
|
|
|
|
```text
|
|
confirmed shared expenses only
|
|
single currency
|
|
equal split or explicit SplitLine amounts
|
|
exclude pending/promised by default
|
|
optionally include promised payments later
|
|
```
|
|
|
|
Settlement output must include explanation:
|
|
|
|
```text
|
|
payer totals
|
|
share totals
|
|
net owed
|
|
events included
|
|
events excluded
|
|
pending/promised handling
|
|
```
|
|
|
|
### Step 9: Implement project budgeting v1
|
|
|
|
Project v1 should support:
|
|
|
|
```text
|
|
project creation
|
|
participants
|
|
budget lines
|
|
planned items
|
|
manual payment lines
|
|
linking existing events to project
|
|
summary calculation
|
|
project settlement calculation
|
|
```
|
|
|
|
Do not wait for bank imports before building projects.
|
|
|
|
### Step 10: Add importers
|
|
|
|
After manual/project/settlement core works:
|
|
|
|
```text
|
|
CSV importer v1
|
|
IMAP importer v1
|
|
PayPal email parser
|
|
Klarna email parser
|
|
PDF invoice importer
|
|
receipt photo OCR importer
|
|
```
|
|
|
|
### Step 11: Add local agent design
|
|
|
|
Define but do not overbuild:
|
|
|
|
```text
|
|
local-agent registers with backend
|
|
local-agent stores connector secrets locally
|
|
local-agent runs imports/connectors locally
|
|
local-agent submits RawObservations or normalized candidate events
|
|
backend never receives connector passwords
|
|
```
|
|
|
|
Possible local-agent API:
|
|
|
|
```text
|
|
POST /local-agent/register
|
|
POST /local-agent/sync/observations
|
|
GET /local-agent/config
|
|
POST /local-agent/heartbeat
|
|
```
|
|
|
|
### Step 12: Add local categorization
|
|
|
|
First version:
|
|
|
|
```text
|
|
merchant/counterparty rules
|
|
IBAN rules
|
|
amount + recurrence rules
|
|
project context rules
|
|
manual correction memory
|
|
```
|
|
|
|
Only later add:
|
|
|
|
```text
|
|
local classifier
|
|
local embeddings
|
|
local LLM structured suggestions
|
|
```
|
|
|
|
## 9. Anything that should not be repeated or reopened unless necessary
|
|
|
|
Do not reopen the question of whether Klarna Card has a clean public transaction API unless new evidence is needed. Current working assumption:
|
|
|
|
```text
|
|
Klarna Card API access is unavailable or unreliable.
|
|
Use IMAP, statements/PDFs, manual entry, GDPR export, and bank repayment reconciliation.
|
|
```
|
|
|
|
Do not design the platform as a plain transaction viewer.
|
|
|
|
Do not treat PayPal/Klarna bank debits as ordinary expenses by default. They may settle prior obligations.
|
|
|
|
Do not treat credit card bill payments as ordinary expenses by default.
|
|
|
|
Do not let local AI or categorization directly mutate confirmed ledger entries without review or rules.
|
|
|
|
Do not assume imported rows are final truth. Store them as observations/evidence.
|
|
|
|
Do not overload categories with project names. Use:
|
|
|
|
```text
|
|
category: holiday > restaurant
|
|
project: Stockholm 2026
|
|
tags: optional flexible labels
|
|
```
|
|
|
|
Do not assume all users should see all transactions. Privacy modes are part of the design.
|
|
|
|
Do not delay manual entry until connectors are ready. Manual entry is core.
|
|
|
|
Do not delay project budgeting until imports are ready. Project budgeting is core.
|
|
|
|
## 10. Assumptions about runtime/build/deployment environment
|
|
|
|
These are starting assumptions for development, not confirmed requirements.
|
|
|
|
```text
|
|
Development mode:
|
|
local machine development first.
|
|
|
|
Deployment mode:
|
|
self-hosted or private household deployment first.
|
|
multi-tenant SaaS later only if regulatory/compliance issues are handled.
|
|
|
|
Backend:
|
|
API server with relational database.
|
|
|
|
Database:
|
|
PostgreSQL preferred for backend.
|
|
SQLite acceptable for early prototype/local-agent/testing.
|
|
|
|
Frontend:
|
|
web app first.
|
|
|
|
Mobile:
|
|
defer; possibly PWA first.
|
|
|
|
Local connector agent:
|
|
separate process/app running on user-controlled machine.
|
|
stores secrets locally.
|
|
talks to backend through authenticated API.
|
|
|
|
Bank connector:
|
|
not implemented in first coding pass.
|
|
architecture should allow FinTS/PSD2 connectors later.
|
|
|
|
AI/OCR:
|
|
local process only.
|
|
no cloud OCR/LLM/categorization dependency.
|
|
```
|
|
|
|
## 11. Minimum viable development target
|
|
|
|
The first useful prototype should not connect to banks yet.
|
|
|
|
MVP target:
|
|
|
|
```text
|
|
1. Create household and users.
|
|
2. Create accounts.
|
|
3. Manually enter expense/payment/promised payment.
|
|
4. Assign category, project, tags.
|
|
5. Attach raw evidence placeholder.
|
|
6. Split expense between users.
|
|
7. Detect exact duplicates among manual/CSV-like observations.
|
|
8. Show ledger.
|
|
9. Show settlement summary.
|
|
10. Create project with budget lines and planned items.
|
|
11. Show project budget summary:
|
|
budgeted / committed / paid / due later / settled.
|
|
```
|
|
|
|
This validates the domain model before spending time on connectors.
|
|
|
|
## 12. Suggested first implementation milestone
|
|
|
|
Milestone name:
|
|
|
|
```text
|
|
M0: Local household ledger skeleton
|
|
```
|
|
|
|
Acceptance criteria:
|
|
|
|
```text
|
|
- Repository builds.
|
|
- Database migrations run.
|
|
- API starts.
|
|
- Web app starts.
|
|
- User can create household, users, accounts.
|
|
- User can create manual RawObservation.
|
|
- System creates candidate FinancialEvent.
|
|
- User can confirm event.
|
|
- User can split event between users.
|
|
- Settlement summary shows who owes whom.
|
|
- User can create project.
|
|
- User can add project budget lines and planned items.
|
|
- User can link event to project.
|
|
- Project summary calculates budgeted, committed, paid, due later.
|
|
- Basic duplicate hash detection exists.
|
|
- Tests cover settlement and project summary calculations.
|
|
```
|
|
|
|
## 13. Suggested test cases
|
|
|
|
### Settlement test
|
|
|
|
```text
|
|
A pays 120 € groceries, common 50/50.
|
|
Expected:
|
|
A share = 60 €
|
|
B share = 60 €
|
|
B owes A = 60 €
|
|
```
|
|
|
|
### Shared account test
|
|
|
|
```text
|
|
Shared account pays 100 € common expense.
|
|
If shared account is neutral household wallet:
|
|
no direct A/B reimbursement generated.
|
|
```
|
|
|
|
### Reimbursement test
|
|
|
|
```text
|
|
B transfers A 60 € after A paid 120 € common.
|
|
Expected:
|
|
reimbursement settles previous imbalance.
|
|
transfer is not income.
|
|
```
|
|
|
|
### PayPal promised payment test
|
|
|
|
```text
|
|
PayPal email creates 84 € promised payment due in 30 days.
|
|
Later bank debit from PayPal for 84 €.
|
|
Expected:
|
|
bank debit settles promised payment.
|
|
no duplicate expense created.
|
|
```
|
|
|
|
### Credit card test
|
|
|
|
```text
|
|
Card purchase 50 € restaurant.
|
|
Later bank payment 50 € to card issuer.
|
|
Expected:
|
|
purchase is expense.
|
|
card payment is liability payment, not second expense.
|
|
```
|
|
|
|
### Project partial payment test
|
|
|
|
```text
|
|
Hotel planned item:
|
|
budgeted 800 €
|
|
committed 760 €
|
|
deposit paid 200 €
|
|
due later 560 €
|
|
Expected:
|
|
committed = 760 €
|
|
paid = 200 €
|
|
due_later = 560 €
|
|
```
|
|
|
|
### Project settlement test
|
|
|
|
```text
|
|
Project has 420 € transport paid by A and 200 € hotel deposit paid by B.
|
|
Split 50/50.
|
|
Expected:
|
|
A paid 420 €
|
|
B paid 200 €
|
|
total = 620 €
|
|
each share = 310 €
|
|
B owes A = 110 €
|
|
```
|
|
|
|
### Duplicate observation test
|
|
|
|
```text
|
|
Same manual input imported twice with same raw hash.
|
|
Expected:
|
|
second observation flagged duplicate_source.
|
|
no second confirmed event unless user overrides.
|
|
```
|
|
|
|
## 14. Final note for the next coding agent
|
|
|
|
Start with the domain model and ledger semantics. Connectors, OCR, IMAP, and local AI are important, but they should plug into the same `RawObservation -> CandidateEvent -> Ledger` pipeline later.
|
|
|
|
The first code should prove that the platform can correctly answer:
|
|
|
|
```text
|
|
What happened?
|
|
Who paid?
|
|
Who benefited?
|
|
Is it private or shared?
|
|
Is it already paid?
|
|
Is it still due?
|
|
Is it reconciled with evidence?
|
|
Is it settled between household members?
|
|
Which project/budget does it belong to?
|
|
What should happen next?
|
|
```
|
|
|
|
No blocking clarification is required before scaffolding a prototype. The main implementation choice still open is the tech stack; absent other instructions, a TypeScript monorepo with API, web app, shared domain package, PostgreSQL migrations, and a future local-agent package is the most straightforward starting point.
|
|
|