Create readme.md

Create an initial guiding document for both human and AI.
This commit is contained in:
2026-08-25 13:11:24 +02:00
parent 77354d4ba8
commit 0dfc720f47
+290
View File
@@ -0,0 +1,290 @@
# RustyRPN
RustyRPN is a project to create and maintain a business management application for a small Swedish Aktiebolag.
## The business
The company in question is *Rent & Petroleum Nordic AB* with a yearly turnover of about 20 million SEK. RPNAB has two main revenue generators:
1. Selling gasoline and diesel at the local airport both to retail, as well as to the car rental firms located at the airport.
2. Acting as a franchisee to Enterprise Rent-A-Car.
RPNAB is historically a family company and is at the moment driven by two cousins; Johan (CEO) and Jakob (chairman of the board) Rönnbäck.
For a few years RPNAB had a daughter company called Recamp Nordic AB that everything related to car rentals was delegated to, but it was recently absorbed back into the main company.
## The maintainer
- Official position within the company is board-of-director as well as owner of 60% of the private equity
- Have some minor experience with development (mainly node.js)
- Is using this project as an opportunity to learn Rust, as well as AI-assisted development
- Prefer a TDD approach and functional code style (used ramda library while writing node.js code)
- Uses OmniFocus and a GTD inspired workflow for keeping track of tasks
## The application
Code will eventually be kept on a private gitea instance (for issue handling), but everything is to be considered open source. No expectations of assistance with writing code, but if the project and the company is successful a dream of the maintainer is for other companies and developers to make use of it. However, due to the bespoke nature of the functionality provided this seems unlikely.
## Technology
The whole stack is Rust: one language, one toolchain, one test runner,
from database to browser. Motivation for choosing Rust is maintainability,
not speed or efficiency.
- Rust: stable channel, edition 2024. Minimum supported version: 1.8x (TBD).
Crate versions are decided at `cargo add` time and pinned in Cargo.lock;
this document records choices, not versions.
- Async runtime: tokio.
- CLI: clap (derive API).
- Web server & JSON API: axum.
- SPA: Leptos, served by the same binary as the API.
- Database: MariaDB (protocol-compatible MySQL). Client: sqlx, with
mysql_async as fallback. The DB server itself is out of scope; given
connection details in config, the application creates its own tables
from embedded migrations (schema_migrations table).
- Config: TOML file (see config.template.toml), loaded via
--config flag or $RUSTYRPN_CONFIG. Real config files are gitignored.
- Serialization: serde / serde_json.
- Logging: tracing (level via RUST_LOG).
- CSV/TSV files: csv crate; non-standard raw-export dates are parsed by
our own module, test-covered.
- Dates: chrono.
- Errors: thiserror for domain errors, anyhow in binaries.
Repository layout (Cargo workspace, root: `Application/`):
- src/core – domain logic: ingest, invoices, Fortnox, car registry
- src/cli – command line interface
- src/server – axum server: JSON API + SPA assets
- src/web – Leptos SPA
Build & test:
- cargo build / cargo test at the workspace root
- Tests needing a database use config.test.toml and a scratch schema;
tests must not assume pre-existing data
- The binaries are built on FreeBSD (pkg rust); they run inside a jail
Hosting:
- Caddy reverse-proxies the web interface (TLS) from a separate jail
(running caddy is out of scope)
- The CLI is used from the LAN only (e.g. via SSH); nothing is exposed
for it.
Authentication & Authorization:
- Portal authentication (v1): login form with email + password (argon2id)
- Server-side session in MariaDB behind a HttpOnly/Secure/SameSite=Lax cookie, rate-limited, no user enumeration
- Roles:
- director (Jakob & Johan, full access)
- employee (access to appropriate internal functions)
- customer (own company's invoices only — every query scoped to the session's company)
- OIDC and passkeys are future options, not part of v1.
## Data formats
Three document types flow into the application. Samples for all of them live
under `Application/data/test_input/` in one directory per source. Samples
contain real customer and card data: they are gitignored, must never be
committed, and their values must never be embedded in committed test files.
Tests may *reference* sample files by path (the files stay local), or use
synthetic/sanitized values in the repo.
The raw formats are canonical: the application parses what the source systems
deliver. Filenames carry metadata (batch / month / invoice number) and are
the natural keys for idempotent re-ingestion — ingesting the same file twice
must not duplicate data.
Date and number formats differ per source; never assume:
| Source | Date format | Example | Number format |
|---|---|---|---|
| epsilon | US style, no zero padding: `M/d/yyyy h:mm:ss AM/PM` | `3/16/2026 6:08:43 AM` | `561.24` |
| tsdrms | EU style: `DD/MM/YYYY` | `07/01/2026` | `-540.54` |
| subfranchise PDF | Month name + year in text | `January 2026` | European: `1.387.732,10`, `(35.070,15)` for negatives |
### 1. Epsilon fuel station exports
Sales transactions from the station's cash register system Epsilon
- Filename: `raw-export-from-epsilon-<batch>.txt`, e.g.
`raw-export-from-epsilon-405.txt`. One file = one batch; the filename
number equals the `Batch number` field of every row in the file.
- Batches are numbered sequentially (two per month in samples) and span
several days each (e.g. batch 409: 2/1–2/9). Cumulative exports can
contain many batches (sample: batches 1–406, ~138k rows, 2019→2025).
- File format: ASCII, tab-separated, every field double-quoted, CRLF line
endings, header row, trailing newline. 16 fields:
| Field | Example | Notes |
|---|---|---|
| Date | `3/16/2026 6:08:43 AM` | US format, see table above |
| Batch number | `405` | matches filename |
| Amount | `561.24` | SEK; = Volume × Price |
| Volume | `31.18` | liters |
| Price | `18.00` | SEK/liter |
| Quality | `1001` | code: 1001 = unleaded, 4 = Diesel, 0 = zero-value record |
| QualityName | `95 Oktan` | empty for Quality 0 |
| Card number | `549543******5778` | consumer cards are masked; contract cards appear **unmasked** and are exactly the rows with a non-empty Customer number — personal data, store as delivered |
| Card type | `549543******5778` | equals Card number in all samples |
| Customer number | `1861` | contract customer id; empty for retail |
| Station | `97254` | single station in samples |
| Terminal / Pump | `1` / `2` | small integers |
| Receipt | `004109` | zero-padded 6 digits |
| Card report group number | `4` | |
| Control number | `126301` | alphanumeric; empty on some rows |
Known edge cases (from samples — verify against new real files before
changing the parser):
- Quality 0 rows are zero-value records (amount, volume, price all `0.00`,
empty name)
- canceled fuelings and testing events
- No negative amounts in any sample.
### 2. TSDRMS exports (Enterprise rental system)
- Filename: `YYYY-MM.xlsx` (monthly) or `YYYY-MM-DD - YYYY-MM-DD.xlsx`
(period), e.g. `2026-01.xlsx`, `2026-01-01 - 2026-07-31.xlsx`.
One sheet, named `Sheet`.
- Content: a **general ledger journal export** (double-entry), *not* a list
of rentals. Each rental appears as paired debit/credit rows. 11 fields:
| Field | Example | Notes |
|---|---|---|
| GL Account # | `2050` | `2050` DEPOSITS RECEIVED, `8000` MASTERCARD, `1030` WIRE TRANSFER PAYMENT, `1511` ACCOUNTS RECEIVABLE, `2611` TAX TYPE 1, … full code list unknown — derive from data |
| Description | `DEPOSITS RECEIVED` | GL account name |
| Location | `LLAT73` | Enterprise location code |
| CODE | `M`, `R1`, `P`, `D`, `CDWTPI` | product identifier used to map rows to bookkeeping accounts |
| R/A # | `LLAT62-3986i5` | rental agreement id; format varies (also `345670C`) |
| Transaction Date | `07/01/2026` | DD/MM/YYYY |
| DBR | `LLAC61-1260` | meaning unknown |
| Cutoff Date | `07/01/2026` | DD/MM/YYYY |
| Debit / Credit | `-1.25` / `1.25` | numeric; stored as IEEE floats in the XML (e.g. `-540.53999999999996`) — round to 2 decimals on ingest |
| Product Type | `VEHICLE` | `VEHICLE` / `NO PRODUCT` |
- **Partitioning quirk (important):** monthly files are partitioned by
*Cutoff Date*, not Transaction Date. All 2797 rows of the January 2026
file have January cutoffs, but 23 of them have December 2025 transaction
dates. Never assume transaction month = file month.
- Scale: ~2.8k–3.6k rows per month; ~19.9k rows for a 7-month period file.
### 3. Subfranchise statements (PDF)
Monthly settlement statements from Enterprise (Shared Mobility Sverige
Filial, org.nr 516411-7920) for the sub-franchised business. One PDF per
settlement month, received ~1–2 months after the period ends.
- Filename: `<received YYYY-MM-DD> -- <Invoice #> - Subfranchise Statement
- <settlement YYYY-MM>.pdf`, e.g.
`2026-03-02 -- 821100844 - Subfranchise Statement - 2026-01.pdf`.
Invoice numbers are sequential (821100844, 821100853, …).
- 1-page A4, **text-based (not scanned)** — text extraction works. Choose a
Rust PDF text-extraction crate when this feature starts; layout stability
is verified for six samples (2026-01 → 2026-06) only, so parse
defensively and re-verify against each new statement.
- Header block: sender name/address/org.nr, settlement period (month name +
year), location code (`LLAT`), Invoice #, Date, Partner
(`Recamp Nordic AB`), Reference (e.g. `Subfranchise Statement - Luleå
January 2026`).
- Body: line-item table with columns
`DESCRIPTION | EUR | @daily rate | AMOUNT SEK`. Numbers use the European
format from the table above; zero cells read `€ - 10,66 -`.
- Known line items (order can vary; some months contain zero rows; names can
embed the month, e.g. `InMoment SQI - January 2026` — match by pattern,
not exact string): All Revenues, Excluded revenues, Gross revenues less
exclusions, Royalty Fee (7%), Central Invoicing Fee (2%), Marketing Fee
(1%), reservation-fee line, GF Learning Center, InMoment SQI, Cross Border
Debit, Bad Debt Reserve (1%), subtotals, Total fees due, VAT (25%),
adjustments, Total amount due to Franchisee.
### 4. Voucher mapping reference (Fortnox)
`Application/data/other_resources/` (gitignored) holds a copy of the Apple
Numbers spreadsheet the maintainer currently uses to turn tsdrms data into
bookkeeping vouchers. It is the reference for the voucher feature: read it
to understand the mapping; never modify it.
It maps tsdrms `Description` values to Swedish voucher descriptions in these
groups: sales (`Fordran - Biluthyrning - Faktura` / `- Shared Mobility`,
`Bränsleförsäljning`, VAT-liable / VAT-exempt sales), products and fees
(`Försäkringsprodukter`, parking and traffic-violation fees, airport fee,
road assistance), damage billing (`Skadedebitering`), write-offs
(`Nedskrivning`), card-network and Enterprise reconciliations
(`Avstämning - American Express` / `- Windcave` / `- Enterprise`), and
output VAT (`Utgående moms, 25%`). Output includes `Export ID` and
`Bankkonto` per line.
The complete mapping lives in the spreadsheet's formulas and is *not* in the
repo. When the voucher feature starts, its first task is to port the mapping
into versioned data in the repo (e.g. a TOML table) before writing code
against it.
## Development
- Two target interfaces
- Command line: for scripting & automation, and efficiency for advanced users (i.e. myself)
- Web based (SPA) for regular employees and customers
- Code
- Main priority is maintainability and user facing interface stability
- Style should emphasize ease of understanding as well as long term stability
- Keep source code clean by mainly keeping descriptive documentation in the git commit messages rather than inline
- Use generative AI for writing docs, tests, and code
- Use a TDD inspired approach by always writing tests before implementation code
- Liberal use of checklists to keep both human and AI on track
### Workflow
Use a test-driven-development inspired approach, specifically the following workflow:
1. Create a named git branch for a new feature, improvement, or bugfix
2. Create specification for changes to be made
2.1 Discuss the goal; developer and AI-model discuss a new feature, fix, or improvement to be done
2.2 AI-model asks clarifying questions until a clear understanding is shared between human and AI on what the goal is
2.3 Agree on a checklist of items to test against to help keep the AI on track
3. Write a failing test
4. Commit test to git
5. Write code
5.1 Write the minimal code to pass one of the tests
5.2 Run test and verify test passes
5.3 Create a git commit per passing test
5.4 Iterate for each test until they all pass
6. Verify everything on the checklist has been completed
7. Update roadmap
- this file is the source of truth
- any task that changes a decision (stack, format, workflow) updates it in the same commit
8. Provide a short list of suggestions for improvements and include a suggested global next step
### General guidelines for AI
- Do not
- commit secrets or private data
- commit config files (except for template)
- commit anything under data/
- add dependencies without asking
- refactor existing code as part of an unrelated task
- put secrets or real customer data in tests
- Git commit messages
- Follow best practices regarding format and context, but also keep in mind the commit messages are the main code documentation
- Always ask for feedback before actually committing
- Be concise
## Roadmap and status of functionality
### Active
nothing yet…
### In development
* cli
* ingest tab-separated .txt files containing transactions from automated gas station into a MariaDB
* create invoices from data in database
### Future / planned
* cli
* ingest xlsx files containing car rental transactions exported from tsdrms
* prepare bookkeeping voucher to be entered into Fortnox accounting/bookkeeping software suite
* ingest pdf files containing monthly settlement data from Enterprise
* spa
* website for car rental firms to check details regarding invoices
* to-do-application for employees
* car registry for RPN
* cars directly owned
* leased cars
* keeps track of costs associated with each car
* keeps track of yearly car inspections (besiktning)
* keeps track of general car status
* winter or summer tyres
* active, broken, being repaired (i.e. general status)
## Glossary
- aktiebolag: limited company
- besiktning: yearly car inspection mandated by Swedish law
- Fortnox: suite of accounting and bookkeeping software
- tsdrms: rental management software used by Enterprise and its subfranchisees
- Epsilon: cash register system used to administer fuel sales