Two-crate workspace: immo-core for pure financial logic (wasm-compatible, no async/IO deps) and immo-web for HTTP and rendering. Both crates are still cargo-new skeletons; no domain code yet. Pins the toolchain to 1.97 with rustfmt, clippy and the wasm32-unknown-unknown target so the core crate's wasm constraint is checkable locally. Cargo.lock is committed since the workspace ships a binary. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4.4 KiB
CLAUDE.md
Project
Web app that answers one question with data: for a given property in France, is buying financially better than renting and investing the same cash? It outputs an amortisation schedule, total ownership costs, and breakeven curves under several market hypotheses.
This is a financial tool. A wrong number is worse than a slow response, an ugly page, or a missing feature. Correctness and traceable sources come first.
I am learning Rust through this project. See "Working with me" below.
Commands
cargo run -p immo-web # start the server on :3000
cargo watch -x 'run -p immo-web' # live reload during development
cargo test --workspace # all tests
cargo test -p immo-core # domain tests only, fast
cargo fmt --all
cargo clippy --workspace --all-targets -- -D warnings
Run cargo clippy and cargo test -p immo-core before telling me a change is finished.
Architecture boundary
Two crates. The boundary is a rule, not a description.
crates/immo-core holds all financial logic as pure functions over plain structs.
- No
tokio, noaxum, noreqwest, no filesystem, no network access. - Must stay compilable to
wasm32-unknown-unknown. After adding any dependency, verify withcargo check -p immo-core --target wasm32-unknown-unknown. - Time is a parameter. Never call
Utc::now()or read the system clock insideimmo-core.
crates/immo-web holds HTTP, HTML rendering, and input parsing. It contains no financial
arithmetic.
If a computation is tempting to write inline in a handler, it belongs in immo-core.
Stack, pinned
- axum 0.8. Path parameters use
{param}, not:param— that is 0.7 syntax and will not compile. #[async_trait]is not needed onFromRequest/FromRequestPartsin 0.8. Do not add it.- askama for templates, tower-http for static files and compression, serde, rust_decimal.
- Server-rendered HTML. No npm, no bundler, no JS framework. Charts are a CDN
<script>tag fed by a JSON endpoint.
Before writing axum code from memory, check the version in Cargo.toml and verify the API against
docs.rs for that exact version.
Domain rules
- Money is
rust_decimal::Decimalor integer cents. Neverf64. Neverascasts between numeric types in financial code. - Rates are stored as annual nominal and converted explicitly. Name variables so the period is
unambiguous (
taeg_annual,monthly_rate), never a barerate. - Keep French domain vocabulary in type and field names:
FraisDeNotaire,TaxeFonciere,IndiceReferenceLoyers,LoyerDeReferenceMajore,ZoneTendue,Taeg. Do not translate them. - Every hardcoded assumption lives in
crates/immo-core/src/assumptions.rswith a///comment giving its source and the date it was checked. Nothing hardcoded anywhere else. - If you do not know a real figure — a barème, a tax rate, a fee schedule, an IRL value — say so and leave a clearly named placeholder. Do not invent plausible-looking French tax numbers.
- TAEG and taux nominal are different things. Distinguish them explicitly wherever both appear.
Error handling and tests
thiserrorfor typed errors inimmo-core;anyhowonly at theimmo-webboundary.- No
unwrap(),expect(), orpanic!()outside tests andmain. - Every
corefunction producing a number the user sees has a known-answer test, with the worked example stated in the test name or a comment above it. - Test the amortisation schedule against hand-checked values, never against the implementation's own output.
Working with me
I know traits, Result, and the borrow checker. I do not know the ecosystem well, and learning it
is half the point of this project.
- When you use a non-obvious idiom — extractors,
Layer, lifetimes on a struct,impl Traitin return position — explain why in one or two sentences, in chat rather than as a code comment. - If my request implies an unidiomatic design, say so before implementing it.
- Prefer the simple version over the clever one, and tell me when you deliberately chose the simple one.
Do not
- Add a dependency without asking first, including small ones.
- Introduce npm, a bundler, TypeScript, or a frontend framework.
- Write financial logic in
crates/immo-web. - Leave commented-out code, or comments that restate what the code does.
- Reformat or restructure files you were not asked to touch.