Set up cargo workspace backbone
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>
This commit is contained in:
commit
e5400c8992
9 changed files with 169 additions and 0 deletions
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Build artifacts
|
||||||
|
/target
|
||||||
|
**/*.rs.bk
|
||||||
|
*.pdb
|
||||||
|
|
||||||
|
# Cargo.lock is committed: this workspace produces a binary (immo-web).
|
||||||
|
|
||||||
|
# Coverage / profiling
|
||||||
|
*.profraw
|
||||||
|
*.profdata
|
||||||
|
/tarpaulin-report.*
|
||||||
|
/cobertura.xml
|
||||||
|
|
||||||
|
# Editor and OS
|
||||||
|
.DS_Store
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
# Local environment
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
94
CLAUDE.md
Normal file
94
CLAUDE.md
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
# 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
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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`, no `axum`, no `reqwest`, no filesystem, no network access.
|
||||||
|
- Must stay compilable to `wasm32-unknown-unknown`. After adding any dependency, verify with
|
||||||
|
`cargo check -p immo-core --target wasm32-unknown-unknown`.
|
||||||
|
- Time is a parameter. Never call `Utc::now()` or read the system clock inside `immo-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 on `FromRequest` / `FromRequestParts` in 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::Decimal` or integer cents. Never `f64`. Never `as` casts 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 bare `rate`.
|
||||||
|
- 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.rs` with 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
|
||||||
|
|
||||||
|
- `thiserror` for typed errors in `immo-core`; `anyhow` only at the `immo-web` boundary.
|
||||||
|
- No `unwrap()`, `expect()`, or `panic!()` outside tests and `main`.
|
||||||
|
- Every `core` function 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 Trait` in
|
||||||
|
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.
|
||||||
11
Cargo.lock
generated
Normal file
11
Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "immo-core"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "immo-web"
|
||||||
|
version = "0.1.0"
|
||||||
7
Cargo.toml
Normal file
7
Cargo.toml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
[workspace]
|
||||||
|
members = ["crates/immo-core", "crates/immo-web"]
|
||||||
|
resolver = "3"
|
||||||
|
|
||||||
|
[workspace.package]
|
||||||
|
edition = "2024"
|
||||||
|
rust-version = "1.97"
|
||||||
7
crates/immo-core/Cargo.toml
Normal file
7
crates/immo-core/Cargo.toml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
[package]
|
||||||
|
name = "immo-core"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
14
crates/immo-core/src/lib.rs
Normal file
14
crates/immo-core/src/lib.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
pub fn add(left: u64, right: u64) -> u64 {
|
||||||
|
left + right
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
let result = add(2, 2);
|
||||||
|
assert_eq!(result, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
7
crates/immo-web/Cargo.toml
Normal file
7
crates/immo-web/Cargo.toml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
[package]
|
||||||
|
name = "immo-web"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
3
crates/immo-web/src/main.rs
Normal file
3
crates/immo-web/src/main.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() {
|
||||||
|
println!("Hello, world!");
|
||||||
|
}
|
||||||
4
rust-toolchain.toml
Normal file
4
rust-toolchain.toml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "1.97"
|
||||||
|
components = ["rustfmt", "clippy"]
|
||||||
|
targets = ["wasm32-unknown-unknown"]
|
||||||
Loading…
Add table
Reference in a new issue