Skip to content
Back to skills

Data Schemas

ASecurity

Data structure definitions for all SmartSpender CSV files. Reference this when reading or writing to any data file.

  • 5 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added May 26, 2026
datagoapi

Works with

  • api

Security analysis

A100/100

Scanned May 27, 2026

npx -y skills add peerjakobsen/smartspender --skill data-schemas --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Data Schemas?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Data Schemas
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/peerjakobsen-data-schemas/badge)](https://www.skillsdirectory.com/skills/peerjakobsen-data-schemas)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: data-schemas
description: Data structure definitions for all SmartSpender CSV files. Reference this when reading or writing to any data file.
---

# Data Schemas

## Purpose

Defines the local CSV file structure that stores all SmartSpender data. All read/write operations use the Filesystem tool to access CSV files in the working directory. Files are created automatically on first use if they don't exist — write the header row first, then append data rows.

---

## 1. Transaction Schema

### Purpose

Defines the common data format for all transactions in SmartSpender. Every bank adapter normalizes its export into this schema before storing locally.

### Schema

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| tx_id | string | yes | Unique transaction ID (auto-generated UUID) |
| tx_hash | string | yes | Deduplication hash |
| date | date | yes | Transaction date (YYYY-MM-DD) |
| amount | number | yes | Amount with sign (negative = expense, positive = income) |
| currency | string | yes | ISO currency code (default: DKK) |
| description | string | yes | Cleaned, human-readable description |
| raw_text | string | yes | Original text from bank export, unmodified |
| bank | string | yes | Bank identifier (e.g., `enable-banking`) |
| account | string | yes | Account identifier from the bank |
| synced_at | datetime | yes | When the transaction was imported (YYYY-MM-DD HH:MM:SS) |

### Hash Computation

The `tx_hash` prevents duplicate imports across multiple sync runs.

#### Primary formula (when bank provides running balance)

```
"{account}|{date}|{amount}|{saldo}"
```

Where:
- `account` is the account identifier from the bank export (e.g., the Enable Banking account UID for API sync)
- `date` is in YYYY-MM-DD format
- `amount` is formatted to exactly 2 decimal places (e.g., `-55.00`)
- `saldo` is the running balance after the transaction, formatted to exactly 2 decimal places (e.g., `927.83`)

The running balance is a natural disambiguator — even two identical purchases at the same merchant on the same day produce different balances.

#### Fallback formula (when bank does not provide running balance)

```
"{account}|{date}|{amount}|{raw_text_normalized}"
```

Where:
- `raw_text_normalized` is the raw_text trimmed of whitespace and lowercased

This fallback is used when:
- Enable Banking API response lacks `balance_after_transaction` for a specific transaction

Before appending any row to transactions.csv, check whether a row with the same `tx_hash` already exists. If it does, skip the row.

### Transformation Rules

#### Date Normalization
- Enable Banking API provides YYYY-MM-DD (no conversion needed)
- Always convert to: **YYYY-MM-DD**
- Reject any date in the future — this likely indicates a parsing error

#### Amount Normalization
- Danish number format uses period as thousands separator and comma as decimal: `1.234,56`
- Convert to standard decimal: `1234.56`
- Enable Banking API provides amounts as positive strings with a `credit_debit_indicator` — negate DBIT amounts
- Expenses are **negative**, income is **positive**
- Always store with exactly 2 decimal places
- Currency defaults to `DKK` unless the transaction specifies otherwise

#### Description Cleaning
- Trim leading and trailing whitespace
- Collapse multiple consecutive spaces into one
- Preserve bank-specific prefixes that indicate payment method (e.g., `Debitcard DK` indicates card payment)
- For Enable Banking: use `creditor.name` (expenses) or `debtor.name` (income) as the description
- Store the original unmodified text in `raw_text`
- Store the cleaned version in `description`

#### Bank and Account Fields
- `bank` uses the lowercase bank identifier: `enable-banking`
- `account` uses the Enable Banking account UID

### Validation Rules

Before storing a transaction, verify all of the following:
- `date` is a valid date and not in the future
- `amount` is a non-zero number
- `raw_text` is not empty
- `tx_hash` does not already exist in transactions.csv (deduplication)

If validation fails, log the issue but do not halt the entire sync — skip the invalid row and continue.

### Transaction Examples

#### Enable Banking Card Payment (API Sync, with balance)
```
tx_id: "d4e5f6a7-b8c9-0123-defa-234567890123"
tx_hash: "eb-uid-abc123|2026-01-15|-847.50|12543.25"
date: 2026-01-15
amount: -847.50
currency: DKK
description: FOETEX
raw_text: "Dankort-koeb FOETEX 4123"
bank: enable-banking
account: "eb-uid-abc123"
synced_at: 2026-02-02 14:00:00
```

#### Enable Banking Salary (API Sync, with balance)
```
tx_id: "e5f6a7b8-c9d0-1234-efab-345678901234"
tx_hash: "eb-uid-abc123|2026-01-31|32500.00|45043.25"
date: 2026-01-31
amount: 32500.00
currency: DKK
description: Virksomhed A/S
raw_text: "Loen januar 2026"
bank: enable-banking
account: "eb-uid-abc123"
synced_at: 2026-02-02 14:00:00
```

#### Enable Banking Subscription (API Sync, no balance — fallback hash)
```
tx_id: "f6a7b8c9-d0e1-2345-fabc-456789012345"
tx_hash: "eb-uid-abc123|2026-01-15|-149.00|netflix.com"
date: 2026-01-15
amount: -149.00
currency: DKK
description: Netflix
raw_text: "NETFLIX.COM"
bank: enable-banking
account: "eb-uid-abc123"
synced_at: 2026-02-02 14:00:00
```

---

## 2. Receipt Schema

### Purpose

Defines the CSV file structure for storing receipt metadata and line-item detail. Two files work together: `receipts.csv` holds one row per receipt with match status; `receipt-items-YYYY-MM.csv` files hold one row per line item with product-level categories, partitioned by month based on the receipt date.

### receipts.csv (Receipt Metadata)

Stores one row per uploaded receipt with extraction results and transaction match status.

**Header row**:
```
receipt_id,transaction_id,date,merchant,total_amount,currency,source,file_reference,match_status,match_confidence,item_count,created_at
```

| Column | Type | Description |
|--------|------|-------------|
| receipt_id | string | Unique receipt ID (`rcpt-` prefix + 8 hex chars, e.g. `rcpt-a1b2c3d4`) |
| transaction_id | string | Matched tx_id from transactions.csv (empty if unmatched) |
| date | date | Receipt date (YYYY-MM-DD) |
| merchant | string | Normalized merchant name (e.g. Foetex, TDC) |
| total_amount | number | Receipt total as positive number (e.g. 347.50) |
| currency | string | ISO currency code (DKK) |
| source | string | How the receipt was obtained: `upload`, `storebox`, `coop`, `eboks`, `email` |
| file_reference | string | Local archive path (e.g. `receipts/rcpt-a1b2c3d4.jpg`) |
| match_status | string | `matched`, `unmatched`, `ambiguous` |
| match_confidence | number | Confidence of the transaction match (0.0-1.0) |
| item_count | number | Number of line items extracted |
| created_at | datetime | When the receipt was processed (YYYY-MM-DD HH:MM:SS) |

**File operations**:
- Append row: after receipt extraction and matching completes
- Read all: for deduplication check before appending
- Read filtered by transaction_id: to find receipts linked to a specific transaction
- Update row: when user corrects a match (update transaction_id, match_status, match_confidence)

### receipt-items-YYYY-MM.csv (Line Items, Monthly Files)

Stores individual line items from each receipt with product-level categorization. Items are partitioned into monthly files based on the receipt's `date` in `receipts.csv`. For example, a receipt dated 2026-01-28 has its items stored in `receipt-items-2026-01.csv`.

**Header row**:
```
item_id,receipt_id,item_name,quantity,unit_price,total_price,category,subcategory,discount,created_at
```

| Column | Type | Description |
|--------|------|-------------|
| item_id | string | Unique item ID (`ritm-` prefix + 8 hex chars, e.g. `ritm-e5f6g7h8`) |
| receipt_id | string | Reference to receipts.csv |
| item_name | string | Product name as printed on receipt (e.g. `Minimælk 1L`) |
| quantity | number | Quantity purchased (default 1) |
| unit_price | number | Price per unit as positive number |
| total_price | number | Line total as positive number (quantity x unit_price - discount) |
| category | string | Danish product category (e.g. Dagligvarer, Bolig, Abonnementer) |
| subcategory | string | Product-level subcategory (e.g. Mejeriprodukter, Koed, Alkohol) |
| discount | number | Discount amount as positive number (0 if no discount) |
| created_at | datetime | When the item was extracted (YYYY-MM-DD HH:MM:SS) |

**File operations**:
- Append rows: determine the receipt date from `receipts.csv`, then write items to `receipt-items-{YYYY-MM}.csv` (create the file with header row if it doesn't exist)
- Read filtered by receipt_id: get the receipt date from `receipts.csv`, open the corresponding monthly file, filter by receipt_id
- Read filtered by date range: get receipt dates from `receipts.csv`, open only the relevant monthly files, filter by receipt_id

### ID Generation

#### Receipt IDs
- Format: `rcpt-` + 8 random hex characters
- Example: `rcpt-a1b2c3d4`, `rcpt-f9e8d7c6`
- Must be unique across all rows in receipts.csv

#### Item IDs
- Format: `ritm-` + 8 random hex characters
- Example: `ritm-e5f6g7h8`, `ritm-1a2b3c4d`
- Must be unique across all monthly receipt-items files

### Receipt Deduplication

Before appending a new receipt, check for duplicates using three fields:
- `date` — same date
- `merchant` — same normalized merchant name
- `total_amount` — same total (exact match)

If all three match an existing receipt, warn the user: "Denne kvittering ligner en der allerede er registreret ({receipt_id} fra {date}). Vil du tilfoeje den alligevel?"

### File Archiving

Receipt images and PDFs are saved to a local `receipts/` directory in the working directory. The directory is created automatically on first use.

#### Naming Convention
- Format: `receipts/{receipt_id}.{ext}`
- Extension preserved from the original file (jpg, png, pdf, etc.)
- Example: `receipts/rcpt-a1b2c3d4.jpg`, `receipts/rcpt-b2c3d4e5.pdf`

#### Archive Rules
- Save the file immediately after generating the receipt_id, before writing CSV rows
- If the file cannot be saved, log a warning but continue processing (the extracted data is still valuable)
- The `file_reference` column in receipts.csv stores the relative path (e.g. `receipts/rcpt-a1b2c3d4.jpg`)

### Receipt Examples

#### Foetex Grocery Receipt

**receipts.csv row**:
```
rcpt-a1b2c3d4,tx-uuid-001,2026-01-28,Foetex,347.50,DKK,upload,receipts/rcpt-a1b2c3d4.jpg,matched,0.95,8,2026-02-01 10:30:00
```

**receipt-items-2026-01.csv rows** (receipt date is 2026-01-28):
```
ritm-e5f6g7h8,rcpt-a1b2c3d4,Minimælk 1L,2,12.95,25.90,Dagligvarer,Mejeriprodukter,0,2026-02-01 10:30:00
ritm-f1g2h3i4,rcpt-a1b2c3d4,Hakket oksekød 500g,1,45.00,45.00,Dagligvarer,Kød,0,2026-02-01 10:30:00
ritm-a2b3c4d5,rcpt-a1b2c3d4,Øko bananer,1,22.95,22.95,Dagligvarer,Frugt og grønt,0,2026-02-01 10:30:00
ritm-b3c4d5e6,rcpt-a1b2c3d4,Coca-Cola 1.5L,2,22.00,44.00,Dagligvarer,Drikkevarer,0,2026-02-01 10:30:00
ritm-c4d5e6f7,rcpt-a1b2c3d4,Rødvin Chianti,1,79.95,79.95,Dagligvarer,Alkohol,0,2026-02-01 10:30:00
ritm-d5e6f7g8,rcpt-a1b2c3d4,Tandbørstehoveder,1,89.95,89.95,Sundhed,Personlig pleje,0,2026-02-01 10:30:00
ritm-e6f7g8h9,rcpt-a1b2c3d4,Rugbrød,1,24.95,24.95,Dagligvarer,Brød og bagværk,0,2026-02-01 10:30:00
ritm-f7g8h9i0,rcpt-a1b2c3d4,Pose 2 kr,1,2.00,2.00,Andet,Andet,0,2026-02-01 10:30:00
```

Note: Items sum to 334.70, but receipt total is 347.50. The difference (12.80) is likely pant (bottle deposit) or rounding — flag for user review if variance > 5%.

#### TDC Telecom Invoice

**receipts.csv row**:
```
rcpt-b2c3d4e5,tx-uuid-042,2026-01-15,TDC,299.00,DKK,upload,receipts/rcpt-b2c3d4e5.pdf,matched,1.00,3,2026-02-01 11:00:00
```

**receipt-items-2026-01.csv rows** (receipt date is 2026-01-15):
```
ritm-g8h9i0j1,rcpt-b2c3d4e5,Mobilabonnement Frihed+,1,199.00,199.00,Abonnementer,Mobilabonnement,0,2026-02-01 11:00:00
ritm-h9i0j1k2,rcpt-b2c3d4e5,Ekstra data 10GB,1,49.00,49.00,Abonnementer,Mobilabonnement,0,2026-02-01 11:00:00
ritm-i0j1k2l3,rcpt-b2c3d4e5,Forsikring Mobil+,1,51.00,51.00,Abonnementer,Forsikring,0,2026-02-01 11:00:00
```

---

## 3. CSV File Reference

All data is stored as CSV files in the working directory. Use comma as the delimiter. Enclose fields in double quotes if they contain commas.

### transactions.csv

Stores raw imported transactions from all bank syncs.

**Header row**:
```
tx_id,tx_hash,date,amount,currency,description,raw_text,bank,account,synced_at
```

| Column | Type | Description |
|--------|------|-------------|
| tx_id | string | Unique transaction ID (UUID) |
| tx_hash | string | Deduplication hash (`"{account}\|{date}\|{amount}\|{saldo}"`) |
| date | date | Transaction date (YYYY-MM-DD) |
| amount | number | Amount with sign (negative = expense) |
| currency | string | ISO currency code (DKK) |
| description | string | Cleaned description |
| raw_text | string | Original bank text |
| bank | string | Bank identifier |
| account | string | Account identifier |
| synced_at | datetime | Import timestamp |

**File operations**:
- Append rows: after sync completes
- Read all rows: before sync (for deduplication by tx_hash)
- Read filtered: by date range for analysis

### categorized.csv

Stores transactions with assigned categories and merchant info.

**Header row**:
```
tx_id,date,amount,description,category,subcategory,merchant,is_recurring,confidence,manual_override
```

| Column | Type | Description |
|--------|------|-------------|
| tx_id | string | Reference to transactions.csv |
| date | date | Transaction date (YYYY-MM-DD) |
| amount | number | Amount |
| description | string | Description |
| category | string | Assigned Danish category (e.g., Dagligvarer) |
| subcategory | string | Optional subcategory (e.g., Supermarked) |
| merchant | string | Normalized merchant name (e.g., Netto) |
| is_recurring | boolean | TRUE if detected as recurring |
| confidence | number | Categorization confidence (0.0-1.0) |
| manual_override | boolean | TRUE if user corrected this category |

**File operations**:
- Append rows: after analyze command categorizes transactions
- Read all: for spending analysis and reporting
- Update row: when user manually corrects a category (set `manual_override` to TRUE)

### subscriptions.csv

Detected recurring charges and their status.

**Header row**:
```
subscription_id,merchant,category,amount,frequency,annual_cost,first_seen,last_seen,status,cancelled_date,notes
```

| Column | Type | Description |
|--------|------|-------------|
| subscription_id | string | Unique ID |
| merchant | string | Service name (e.g., Netflix) |
| category | string | Subscription category (e.g., Streaming) |
| amount | number | Recurring charge amount |
| frequency | string | monthly, yearly, weekly |
| annual_cost | number | Calculated annual cost |
| first_seen | date | First transaction date |
| last_seen | date | Most recent transaction |
| status | string | active, cancelled, paused |
| cancelled_date | date | When cancelled (if applicable) |
| notes | string | Any notes |

**File operations**:
- Append rows: after analyze detects new subscriptions
- Read all: for subscriptions list and overview
- Update row: when subscription is cancelled (set `status`, `cancelled_date`)

### monthly-summary.csv

Aggregated spending by month and category.

**Header row**:
```
month,category,total,transaction_count,avg_transaction,vs_prev_month,vs_prev_month_pct
```

| Column | Type | Description |
|--------|------|-------------|
| month | string | YYYY-MM format |
| category | string | Danish category name |
| total | number | Total spent in this category |
| transaction_count | number | Number of transactions |
| avg_transaction | number | Average transaction size |
| vs_prev_month | number | Change from previous month (absolute) |
| vs_prev_month_pct | number | Percentage change from previous month |

**File operations**:
- Write/overwrite rows: after analyze command completes
- Read filtered by month: for overview and report commands

### action-log.csv

Audit trail of all agent actions for transparency.

**Header row**:
```
timestamp,action_type,target,status,details,savings_amount,notes
```

| Column | Type | Description |
|--------|------|-------------|
| timestamp | datetime | When action occurred (YYYY-MM-DD HH:MM:SS) |
| action_type | string | sync, analyze, cancel, suggest, report |
| target | string | What the action targeted (e.g., "enable-banking", "netflix") |
| status | string | suggested, in_progress, completed, failed |
| details | string | Additional details |
| savings_amount | number | Estimated or actual savings (kr/year) |
| notes | string | Any notes |

**File operations**:
- Append row: after every command execution
- Read filtered: for report command (actions in a given month)

### accounts.csv

Configured bank accounts.

**Header row**:
```
account_id,bank,account_name,account_type,last_synced,is_active,eb_account_uid,eb_session_id,balance,balance_type,balance_date
```

| Column | Type | Description |
|--------|------|-------------|
| account_id | string | Unique account identifier |
| bank | string | Bank identifier (e.g., enable-banking) |
| account_name | string | User-friendly name (e.g., Lonkonto) |
| account_type | string | checking, savings, credit |
| last_synced | datetime | Last successful sync timestamp |
| is_active | boolean | TRUE if currently tracked |
| eb_account_uid | string | Enable Banking account UID |
| eb_session_id | string | Enable Banking session ID |
| balance | number | Current account balance |
| balance_type | string | Balance type code (e.g., CLBD, ITAV) |
| balance_date | date | When balance was last updated (YYYY-MM-DD) |

**Balance type codes**:
- `CLBD`: Closing booked balance (end of day settled) — preferred
- `ITAV`: Intraday available (current available)
- `XPCD`: Expected balance

**File operations**:
- Append row: when user adds a new account
- Update row: after each sync (update `last_synced`, `balance`, `balance_type`, `balance_date`)
- Read all: to list configured accounts

### settings.csv

Plugin configuration stored as key-value pairs.

**Header row**:
```
setting_key,setting_value,updated_at
```

| Column | Type | Description |
|--------|------|-------------|
| setting_key | string | Setting name (snake_case) |
| setting_value | string | Setting value |
| updated_at | datetime | Last updated timestamp |

**Default settings**:

| Key | Default Value | Description |
|-----|---------------|-------------|
| default_currency | DKK | Currency for all transactions |
| categorization_confidence_threshold | 0.7 | Minimum confidence to auto-assign category |
| subscription_detection_months | 3 | Minimum occurrences to detect subscription |
| preferred_language | da | Language for user-facing output |

**File operations**:
- Read all: at command startup to load configuration
- Update row: when user changes a setting

### merchant-overrides.csv

Learned merchant categorization rules from user corrections. When a user manually recategorizes a transaction, the correction is stored here as a reusable rule for future transactions from the same merchant.

**Header row**:
```
raw_pattern,merchant,category,subcategory,created_at
```

| Column | Type | Description |
|--------|------|-------------|
| raw_pattern | string | Normalized pattern from raw_text (uppercase, trimmed) e.g. `NORMAL FREDERIK` |
| merchant | string | User-corrected merchant name e.g. `Normal` |
| category | string | User-corrected category e.g. `Shopping` |
| subcategory | string | User-corrected subcategory (can be empty) |
| created_at | datetime | When the override was created (YYYY-MM-DD HH:MM:SS) |

**File operations**:
- Read all: before categorization to apply learned rules first
- Append row: when a manual correction in categorized.csv introduces a new merchant override
- Check for duplicates: don't add a row if raw_pattern already exists

---

## 3. Payslip Schema

### Purpose

Defines the CSV file structure for storing payslip data extracted from Danish lønsedler. Enables accurate pension tracking and income analysis based on actual gross salary rather than net deposits.

### payslips.csv

Stores one row per uploaded payslip with extracted salary breakdown and pension data.

**Header row**:
```
payslip_id,pay_period,employer,employer_id,gross_salary,a_skat,am_bidrag,pension_employer,pension_employee,pension_total,pension_pct,atp,feriepenge,net_salary,benefits_json,transaction_id,file_reference,created_at
```

| Column | Type | Description |
|--------|------|-------------|
| payslip_id | string | Unique payslip ID (`ps-` prefix + 8 hex chars, e.g. `ps-a1b2c3d4`) |
| pay_period | string | Pay period in YYYY-MM format (e.g. `2026-01`) |
| employer | string | Company name as shown on payslip |
| employer_id | string | Normalized employer ID (lowercase, no spaces) |
| gross_salary | number | Bruttoløn — total earnings before deductions |
| a_skat | number | Income tax withheld |
| am_bidrag | number | Labor market contribution (8% of gross) |
| pension_employer | number | Employer's pension contribution |
| pension_employee | number | Employee's pension contribution |
| pension_total | number | Combined pension (employer + employee) |
| pension_pct | number | Pension as percentage of gross salary |
| atp | number | ATP contribution |
| feriepenge | number | Holiday pay accrued this period |
| net_salary | number | Nettoløn — amount deposited to bank |
| benefits_json | string | JSON string of additional benefits (sundhedsforsikring, fritvalgskonto, etc.) |
| transaction_id | string | Matched tx_id from transactions.csv (empty if unmatched) |
| file_reference | string | Local archive path (e.g. `payslips/ps-a1b2c3d4.pdf`) |
| created_at | datetime | When the payslip was processed (YYYY-MM-DD HH:MM:SS) |

**File operations**:
- Append row: after payslip extraction and validation completes
- Read all: for deduplication check before appending
- Read filtered by pay_period: to analyze pension trends over time
- Read filtered by transaction_id: to find payslip linked to a salary transaction
- Update row: when user corrects extracted data

### ID Generation

#### Payslip IDs
- Format: `ps-` + 8 random hex characters
- Example: `ps-a1b2c3d4`, `ps-f9e8d7c6`
- Must be unique across all rows in payslips.csv

### Payslip Deduplication

Before appending a new payslip, check for duplicates using two fields:
- `employer_id` — same normalized employer
- `pay_period` — same month

If both match an existing payslip, warn the user: "Denne lønseddel ligner en der allerede er registreret ({payslip_id} for {pay_period}). Vil du tilføje den alligevel?"

### File Archiving

Payslip images and PDFs are saved to a local `payslips/` directory in the working directory. The directory is created automatically on first use.

#### Naming Convention
- Format: `payslips/{payslip_id}.{ext}`
- Extension preserved from the original file (jpg, png, pdf)
- Example: `payslips/ps-a1b2c3d4.pdf`

#### Archive Rules
- Save the file immediately after generating the payslip_id, before writing CSV row
- The `file_reference` column stores the relative path (e.g. `payslips/ps-a1b2c3d4.pdf`)
- **Privacy**: Never store CPR numbers — mask any detected CPR in logs

### Payslip Examples

#### Standard Monthly Payslip

**payslips.csv row**:
```
ps-a1b2c3d4,2026-01,Teknologi A/S,teknologi,42000.00,11200.00,3360.00,4200.00,2100.00,6300.00,15.00,94.65,5250.00,25245.35,"{""sundhedsforsikring"":150}",tx-uuid-045,payslips/ps-a1b2c3d4.pdf,2026-02-01 14:30:00
```

#### Payslip with Bonus

**payslips.csv row**:
```
ps-b2c3d4e5,2026-03,Startup ApS,startup,55000.00,16500.00,4400.00,5500.00,2750.00,8250.00,15.00,94.65,6875.00,31255.35,"{""bonus"":10000}",tx-uuid-089,payslips/ps-b2c3d4e5.pdf,2026-04-02 10:15:00
```

---

## 5. File Relationships

```
transactions.csv --[tx_id]--> categorized.csv
transactions.csv --[tx_id]--> receipts.csv (via transaction_id)
transactions.csv --[tx_id]--> payslips.csv (via transaction_id)
receipts.csv --[receipt_id + date]--> receipt-items-YYYY-MM.csv
categorized.csv --[merchant + is_recurring]--> subscriptions.csv
categorized.csv --[month + category]--> monthly-summary.csv
merchant-overrides.csv <-- learned from manual corrections in categorized.csv
payslips.csv --> advice command (pension_pct for Step 3 assessment)
All commands --> action-log.csv
accounts.csv <--> Sync commands (last_synced updated)
```

---

## Related Skills

- See `skills/document-parsing/SKILL.md` for extraction rules and product subcategories
- See `skills/payslip-parsing/SKILL.md` for payslip extraction rules and validation
- See `skills/transaction-matching/SKILL.md` for how receipts and payslips are matched to transactions
- See `skills/categorization/SKILL.md` for merchant-level categories

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.