---
name: transaction-categorize
description: Apply the expense category taxonomy to each line item. Per-line classification respects merchant default_category as a hint.
allowed-tools: update_transaction query_finance
title: Transaction Categorize
category: finance
status: active
metadata:
  triggers:
  - categorize transaction
  - categorise transaction
  - assign category
  - classify line items
  priority: 6
  model_tier: capable
  language:
    output_policy: source
---
## What this skill is

Step 2 of the `receipt-processing` workflow. After `receipt-ingest` produces a `TransactionDraft` with line items but no categories, this skill runs PER LINE ITEM and assigns each one a `category_slug` from the user-editable taxonomy at `categories.yaml`.

Per-line categorization (not per-transaction) is load-bearing: a Costco receipt has groceries AND fuel AND household goods on one paper. Whole-transaction category would force a coarse single label and lose the analytical richness Phase 3 reports need.

## When to use

Called by the `bookkeeper` agent during the workflow's `categorize` step, after `receipt-ingest` and `merchant-resolve` have populated the draft.

## Flow

1. Read the draft's line items.
2. For each line item:
   - If the merchant has a `default_category` hint AND the item description is generic (no contradiction), use the hint.
   - Otherwise, classify via the LLM against `config/categories.yaml`.
3. Call `update_transaction(draft_id, items=[{item_id, category_slug}, ...])`.
4. Return a one-line summary: `Categorised N items: X groceries, Y fuel, Z restaurants.`

## Rules
- NEVER assign a category outside `config/categories.yaml`. If no category fits, leave it empty (`""`) — the auditor will surface it for user review.
- NEVER set `tax_deductible=true` from this skill. That's a separate decision flag the user owns; suggesting it is the auditor's job, marking it true is the user's.
- NEVER infer category from the MERCHANT alone for items that contradict it. A Costco receipt's "engine oil" line item is `automotive`, not `groceries`, regardless of the merchant default.

## Default category taxonomy (12 buckets)

Lives in `config/categories.yaml`. Conservative starter set:
- groceries
- restaurants
- fuel
- housing
- utilities
- saas
- equipment
- travel
- health
- education
- entertainment
- other

Users edit YAML to extend (`pet-care`, `childcare`, `business-meals` for tax separation, etc.). The skill's classify prompt regenerates from YAML at next run.

## Examples

- input: |
    Categorize draft txn_d4f3a2 items.
  output: |
    [Calls tool: query_finance with {"draft_id": "txn_d4f3a2"}]
    [Reviews 3 line items: "Coffee", "Pastry", "Tip"]
    [Calls tool: update_transaction with item categories assigned: restaurants, restaurants, restaurants]
    Categorised 3 items: 3 restaurants.

- input: |
    Categorize draft txn_b7e9c1 items.
  output: |
    [Calls tool: query_finance with {"draft_id": "txn_b7e9c1"}]
    [Reviews 5 items from Costco: "Eggs 24ct", "Engine oil 5qt", "Bananas", "Paper towels", "Toothpaste"]
    [Calls tool: update_transaction with categories: groceries, automotive, groceries, groceries, health]
    Categorised 5 items: 3 groceries, 1 automotive, 1 health.