Skip to content

Commit 6fd9937

Browse files
authored
feat: initial TypeScript package — SKR03/SKR04 + MCC mapper v0.1.0 (#1)
* chore: add gstack skill routing rules to CLAUDE.md * feat: add TypeScript package with SKR03/SKR04 + MCC mapper (v0.1.0) - skr03.json: 289 accounts (expanded Klasse 7 + granularity in 0/1/8) - skr04.json: 254 accounts derived from SKR03 cross-references - mcc_skr_mapping.json: 230 mappings incl. digital goods MCCs 5815-5818, hotel range 3351-3500, coworking (7299), commercial clothing (5137/5139) - TypeScript package: Kontenrahmen + MCCMapper with MCC range expansion, SKR03/SKR04 singletons, suggestAccount() returning undefined for unmapped - 53 Vitest tests across kontenrahmen, mcc, and data_integrity suites - JSON Schema validation for skr03.json and mcc_skr_mapping.json - GitHub Actions CI: test on Node 18/20/22 + schema validation job - MCC contribution PR template * chore: clean up repo structure and add LICENSE - Move src/data/ to .gitignore (generated by prebuild from root JSON files) - Add prebuild/pretest scripts to auto-copy root JSON → src/data/ - Remove german-accounting-spec.md and TODOS.md (internal planning docs) - Fix CI schema validation to point at root-level JSON files - Rewrite README with cleaner API docs, data schema examples, contribution guide - Add MIT LICENSE * refactor: single source of truth for data files in src/data/ Move JSON files from project root into src/data/ — one location, no copies, no copy scripts. Remove prebuild/pretest hacks from package.json. * fix: inline JSON data into bundle instead of copying as separate files tsup loader:copy caused ERR_IMPORT_ASSERTION_TYPE_MISSING on Node 20 ESM. Removing the custom loader lets tsup inline the JSON directly into the JS bundle — no separate JSON files in dist/, no import attribute issues. * chore: remove sourcemaps from published package * chore: add npm package metadata (repository, homepage, bugs, engines, author) * fix: return queried MCC code in suggestion instead of raw range string suggestAccount('3175') was returning mcc: '3000-3350' (the range key). Now returns the actual queried code. Also fix CI branch trigger to include 'initial' and correct stale steuerschluessel comment in README. * fix: add last_updated to meta and rename id→klasse in klassen array JSON Schema validation was failing: skr03.json meta missing 'last_updated' and klassen entries using 'id' instead of required 'klasse' field.
0 parents  commit 6fd9937

22 files changed

Lines changed: 13855 additions & 0 deletions
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
## MCC Mapping Contribution
2+
3+
**MCC code:** <!-- e.g. 5817 -->
4+
**MCC name:** <!-- e.g. Digital Goods: Applications (SaaS) -->
5+
6+
### Suggested mapping
7+
8+
| Field | Value |
9+
|-------|-------|
10+
| SKR03 primary | <!-- 4-digit account, e.g. 4969 --> |
11+
| SKR03 confidence | <!-- high / medium / low --> |
12+
| SKR04 primary | <!-- 4-digit account, e.g. 6300 --> |
13+
| needs_beleg | <!-- true / false --> |
14+
| ust_abzug | <!-- true / false --> |
15+
16+
### Alternatives (if any)
17+
18+
| Account | Condition |
19+
|---------|-----------|
20+
| <!-- konto --> | <!-- when to use instead of primary --> |
21+
22+
### Rationale
23+
24+
<!-- Why this account? Cite your source (DATEV, SKR documentation, Steuerberater guidance, etc.) -->
25+
26+
### Checklist
27+
28+
- [ ] I have run `npm test` and all tests pass
29+
- [ ] The account number exists in `skr03.json`
30+
- [ ] `skr04_primary` is a valid 4-digit string
31+
- [ ] `confidence` is set to `high` only if the MCC strongly implies this account with no ambiguity
32+
- [ ] If the category name includes "Not Elsewhere Classified" or similar catch-alls, confidence is `medium` or `low`
33+
- [ ] I have added a `notes` field if there are accounting edge cases (e.g. non-deductibility, VAT exemptions)

.github/workflows/ci.yml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: ["main", "initial", "scka-de/**"]
6+
pull_request:
7+
branches: ["main"]
8+
9+
jobs:
10+
test:
11+
name: Test (Node ${{ matrix.node-version }})
12+
runs-on: ubuntu-latest
13+
strategy:
14+
matrix:
15+
node-version: ["18", "20", "22"]
16+
17+
steps:
18+
- uses: actions/checkout@v4
19+
20+
- name: Set up Node.js ${{ matrix.node-version }}
21+
uses: actions/setup-node@v4
22+
with:
23+
node-version: ${{ matrix.node-version }}
24+
cache: "npm"
25+
26+
- name: Install dependencies
27+
run: npm ci
28+
29+
- name: Type check
30+
run: npm run typecheck
31+
32+
- name: Build
33+
run: npm run build
34+
35+
- name: Test
36+
run: npm test
37+
38+
validate-schemas:
39+
name: JSON Schema validation
40+
runs-on: ubuntu-latest
41+
steps:
42+
- uses: actions/checkout@v4
43+
44+
- name: Set up Node.js
45+
uses: actions/setup-node@v4
46+
with:
47+
node-version: "20"
48+
cache: "npm"
49+
50+
- name: Install dependencies
51+
run: npm ci
52+
53+
- name: Install ajv-cli
54+
run: npm install --no-save ajv-cli ajv-formats
55+
56+
- name: Validate skr03.json against schema
57+
run: npx ajv validate -s schemas/skr03.schema.json -d src/data/skr03.json --spec=draft2020 -c ajv-formats
58+
59+
- name: Validate mcc_skr_mapping.json against schema
60+
run: npx ajv validate -s schemas/mcc_skr_mapping.schema.json -d src/data/mcc_skr_mapping.json --spec=draft2020 -c ajv-formats

.gitignore

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Claude
2+
CLAUDE.md
3+
.claude/
4+
.context/
5+
6+
# Node
7+
node_modules/
8+
dist/
9+
10+
# Internal planning docs
11+
german-accounting-spec.md
12+
TODOS.md
13+
14+
# Python
15+
__pycache__/
16+
*.py[cod]
17+
*$py.class
18+
*.egg-info/
19+
build/
20+
*.egg
21+
.eggs/
22+
23+
# Virtual environments
24+
.venv/
25+
venv/
26+
env/
27+
28+
# IDE
29+
.idea/
30+
.vscode/
31+
*.swp
32+
*.swo
33+
*~
34+
35+
# OS
36+
.DS_Store
37+
Thumbs.db
38+
39+
# Testing / Coverage
40+
.pytest_cache/
41+
.mypy_cache/
42+
.ruff_cache/
43+
htmlcov/
44+
.coverage
45+
coverage.xml
46+
47+
# Distribution
48+
*.whl
49+
*.tar.gz

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 german-accounting contributors
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
# german-accounting
2+
3+
Machine-readable SKR03/SKR04 chart of accounts with MCC-to-account mapping for German SME accounting. MIT licensed — code and data.
4+
5+
The only open source dataset combining enriched SKR03/SKR04 metadata with ISO 18245 MCC codes mapped to specific German accounts. Use it from any language: download the JSON files directly or install the npm package for a typed TypeScript API.
6+
7+
```bash
8+
npm install german-accounting
9+
```
10+
11+
---
12+
13+
## Quick start
14+
15+
```typescript
16+
import { SKR03, suggestAccount } from "german-accounting";
17+
18+
// Look up an account
19+
const konto = SKR03.get("4650");
20+
// { konto: "4650", name: "Bewirtungskosten", klasse: 4, typ: "aufwand",
21+
// ust_relevant: true, steuerschluessel: [9], skr04: "6640", ... }
22+
23+
// Categorize a card transaction by MCC
24+
const suggestion = suggestAccount("5812"); // restaurant
25+
// {
26+
// mcc: "5812",
27+
// mcc_name: "Eating Places, Restaurants",
28+
// primary: { konto: "4650", confidence: "high", condition: null },
29+
// alternatives: [{ konto: "1800", condition: "If private meal" }],
30+
// needs_beleg: true,
31+
// ust_abzug: true
32+
// }
33+
34+
// SaaS subscriptions (GitHub, Slack, Notion, Figma...)
35+
suggestAccount("5817").primary.konto; // "4969" — Software-Nutzungsrechte
36+
37+
// Target SKR04 instead of SKR03
38+
suggestAccount("5812", "SKR04").primary.konto; // "6640"
39+
40+
// Returns undefined for unknown MCCs — no throws
41+
suggestAccount("9999"); // undefined
42+
```
43+
44+
---
45+
46+
## API
47+
48+
### `SKR03` / `SKR04`
49+
50+
Pre-loaded chart-of-accounts singletons. Both are instances of `Kontenrahmen`.
51+
52+
```typescript
53+
SKR03.get("4650") // → Konto | undefined
54+
SKR03.search("Reisekosten") // → readonly Konto[] (case-insensitive substring)
55+
SKR03.klasse(4) // → readonly Konto[] (all Klasse 4 expense accounts)
56+
SKR03.exists("4650") // → boolean
57+
SKR03.size // → number
58+
```
59+
60+
### `suggestAccount(mcc, skr?)`
61+
62+
```typescript
63+
suggestAccount(mcc: string, skr?: "SKR03" | "SKR04"): MCCSuggestion | undefined
64+
```
65+
66+
MCC range entries (airlines `3000–3350`, hotels `3351–3500`, car rentals `3501–3999`) are expanded at load time. Any code in those ranges resolves correctly.
67+
68+
When `skr = "SKR04"`, the primary account is the `skr04_primary` value. `name` is `null` and `alternatives` is empty (no SKR04 name data in v0.1).
69+
70+
### Types
71+
72+
```typescript
73+
interface Konto {
74+
konto: string; // "4650"
75+
name: string; // "Bewirtungskosten"
76+
klasse: number; // 4
77+
typ: "aufwand" | "ertrag" | "aktiv" | "passiv";
78+
gruppe: string;
79+
untergruppe?: string;
80+
ust_relevant: boolean;
81+
steuerschluessel: readonly number[];
82+
skr04?: string | null; // cross-ref to SKR04 account (SKR03 only)
83+
skr03?: string | null; // back-ref to SKR03 account (SKR04 only)
84+
notes?: string | null;
85+
}
86+
87+
interface MCCSuggestion {
88+
mcc: string;
89+
mcc_name: string;
90+
category: string;
91+
primary: AccountSuggestion;
92+
alternatives: readonly AccountSuggestion[];
93+
needs_beleg: boolean;
94+
ust_abzug: boolean;
95+
notes: string | null;
96+
}
97+
98+
interface AccountSuggestion {
99+
konto: string;
100+
name: string | null;
101+
confidence: "high" | "medium" | "low";
102+
condition: string | null;
103+
}
104+
```
105+
106+
---
107+
108+
## Data files
109+
110+
The JSON files are the primary product. Download them from [GitHub Releases](https://github.com/german-accounting/german-accounting/releases) to use without Node.
111+
112+
| File | Contents |
113+
|------|----------|
114+
| [`src/data/skr03.json`](src/data/skr03.json) | 289 SKR03 accounts with enriched metadata and SKR04 cross-references |
115+
| [`src/data/skr04.json`](src/data/skr04.json) | 254 SKR04 accounts derived from SKR03 cross-references |
116+
| [`src/data/mcc_skr_mapping.json`](src/data/mcc_skr_mapping.json) | 230 MCC mappings with confidence levels and alternatives |
117+
118+
JSON Schemas are in [`schemas/`](schemas/) and validated by CI on every push.
119+
120+
### Data schema — `skr03.json`
121+
122+
```jsonc
123+
{
124+
"meta": { "version": "2026.1", "last_updated": "2026-04-03", ... },
125+
"klassen": [{ "klasse": 0, "name": "Anlage- und Kapitalkonten" }, ...],
126+
"konten": [
127+
{
128+
"konto": "4650",
129+
"name": "Bewirtungskosten",
130+
"klasse": 4,
131+
"typ": "aufwand",
132+
"gruppe": "Betriebliche Aufwendungen",
133+
"ust_relevant": true,
134+
"steuerschluessel": [9, 8],
135+
"skr04": "6640"
136+
}
137+
]
138+
}
139+
```
140+
141+
### Data schema — `mcc_skr_mapping.json`
142+
143+
```jsonc
144+
{
145+
"meta": { "version": "2026.1", ... },
146+
"mappings": [
147+
{
148+
"mcc": "5812",
149+
"mcc_name": "Eating Places, Restaurants",
150+
"category": "Food & Beverage",
151+
"skr03": { "primary": "4650", "confidence": "high" },
152+
"skr04_primary": "6640",
153+
"alternatives": [
154+
{ "konto": "1800", "condition": "If private meal" }
155+
],
156+
"needs_beleg": true,
157+
"ust_abzug": true
158+
}
159+
]
160+
}
161+
```
162+
163+
---
164+
165+
## Contributing
166+
167+
### Add or fix an MCC mapping
168+
169+
1. Edit `mcc_skr_mapping.json`. Required fields: `mcc`, `mcc_name`, `category`, `skr03.primary`, `skr03.confidence`, `skr04_primary`, `needs_beleg`, `ust_abzug`.
170+
2. Run `npm test` — CI gate verifies that all referenced accounts exist in `skr03.json`.
171+
3. Open a PR using the [MCC mapping template](.github/PULL_REQUEST_TEMPLATE/mcc_mapping.md). Include the account number, confidence level, your rationale, and your source (DATEV docs, Steuerberater guidance, SKR documentation).
172+
173+
**Confidence guidelines:**
174+
- `high` — the MCC strongly implies a specific account with no real ambiguity (e.g., fuel stations → Kfz-Betriebskosten)
175+
- `medium` — likely correct but depends on context (e.g., office supply store → could be Bürobedarf or a larger asset)
176+
- `low` — genuinely ambiguous; the mapping is a reasonable default but alternatives are common
177+
178+
### Add or fix SKR03 accounts
179+
180+
Edit `skr03.json` directly. All fields from the schema are required. Run `npm test` to verify referential integrity before opening a PR.
181+
182+
---
183+
184+
## Versioning
185+
186+
Data is versioned independently from the package using a year-based scheme (`2026.1`, `2026.2`, ...). The package version tracks the API.
187+
188+
---
189+
190+
## License
191+
192+
[MIT](LICENSE) — code and data.

0 commit comments

Comments
 (0)