{"id":51735166,"url":"https://github.com/riccardomerenda/domus-scope","last_synced_at":"2026-07-18T06:08:20.929Z","repository":{"id":371769818,"uuid":"1290293989","full_name":"riccardomerenda/domus-scope","owner":"riccardomerenda","description":"Local-first rent-vs-buy decision lab — two-lens simulation, explainable numbers, bilingual (EN/IT)","archived":false,"fork":false,"pushed_at":"2026-07-16T19:56:50.000Z","size":1069,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-16T21:23:45.918Z","etag":null,"topics":["decision-support","finance","local-first","pwa","react","real-estate","typescript","vite"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/riccardomerenda.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-07-05T19:49:31.000Z","updated_at":"2026-07-16T19:56:54.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/riccardomerenda/domus-scope","commit_stats":null,"previous_names":["riccardomerenda/domus-scope"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/riccardomerenda/domus-scope","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/riccardomerenda%2Fdomus-scope","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/riccardomerenda%2Fdomus-scope/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/riccardomerenda%2Fdomus-scope/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/riccardomerenda%2Fdomus-scope/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/riccardomerenda","download_url":"https://codeload.github.com/riccardomerenda/domus-scope/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/riccardomerenda%2Fdomus-scope/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35606966,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-18T02:00:07.223Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["decision-support","finance","local-first","pwa","react","real-estate","typescript","vite"],"created_at":"2026-07-18T06:08:16.177Z","updated_at":"2026-07-18T06:08:20.921Z","avatar_url":"https://github.com/riccardomerenda.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# DomusScope\n\n[![CI](https://github.com/riccardomerenda/domus-scope/actions/workflows/ci.yml/badge.svg)](https://github.com/riccardomerenda/domus-scope/actions/workflows/ci.yml)\n[![Deploy](https://github.com/riccardomerenda/domus-scope/actions/workflows/deploy.yml/badge.svg)](https://github.com/riccardomerenda/domus-scope/actions/workflows/deploy.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n\n**A local-first decision lab for the rent vs. buy vs. cash-purchase question.**\n\n**[Try it live →](https://riccardomerenda.github.io/domus-scope/)** — no account, no\nbackend; everything stays in your browser.\n\nDomusScope is a personal decision-support tool for real-estate choices. It does not answer\n\"is buying better than renting?\" with a slogan — it simulates both paths over time,\nseparates _unrecoverable costs_ from _wealth accumulation_, makes every assumption\nexplicit and editable, and shows how fragile each conclusion is when assumptions change.\n\n\u003e Derived from the [source domain document](docs/00-source-document.md). This repository\n\u003e contains the critique, refined domain specification, architecture, UI design, and\n\u003e implementation roadmap — followed by the implementation itself.\n\n## Screenshots\n\n|                                                                                                                                       |                                                                                                             |\n| :-----------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------: |\n| ![Quick mode with derived-threshold gauge](docs/screenshots/quick.png)\u003cbr\u003e_Quick mode: derived threshold R\\*, year-1 costs, warnings_ |   ![Explanation drawer](docs/screenshots/explain.png)\u003cbr\u003e_Every number opens into its formula and inputs_   |\n|      ![Results with two-lens charts](docs/screenshots/results.png)\u003cbr\u003e_Results: net worth, break-evens, \"if you sold in year t\"_      | ![Sensitivity analysis](docs/screenshots/sensitivity.png)\u003cbr\u003e_Sensitivity: tornado, verdict flips, heatmap_ |\n\n---\n\n## The core idea\n\nComparing a mortgage payment to a rent payment is the classic mistake: a mortgage payment\ncontains a **principal portion** (which becomes your wealth) and an **interest portion**\n(which is a pure cost). The correct comparison unit is the **unrecoverable cost** of each\nscenario:\n\n| Scenario          | Main unrecoverable costs                                                |\n| ----------------- | ----------------------------------------------------------------------- |\n| Rent              | Rent itself, renter fees, moving costs                                  |\n| Buy with mortgage | Interest, maintenance, taxes/fees, opportunity cost of invested capital |\n| Buy cash          | Maintenance, taxes/fees, opportunity cost of the _entire_ price         |\n\nDomusScope evaluates every scenario through **two complementary lenses**:\n\n1. **Cost lens** — year-by-year unrecoverable costs, itemized and cumulated. Fast to\n   understand, mirrors the \"5% rule\" reasoning (with the threshold _derived_ from your\n   own assumptions, not hardcoded).\n2. **Wealth lens** — a full cash-flow simulation where the renter invests the capital the\n   buyer locks into the house. Compares total net worth over time on a liquidation basis.\n\n## Product principles\n\n- **Explainable** — every number can be expanded into its formula and inputs.\n- **Neutral** — no ideological bias toward buying or renting; the model only reports.\n- **Extremely configurable** — every economic parameter (rates, growth, maintenance,\n  thresholds, cost items, presets) is data, validated by schema, never hardcoded truth.\n- **Local-first \u0026 private** — financial data never leaves the device. No backend, no\n  telemetry. Storage in the browser (IndexedDB) with JSON export/import.\n- **Deterministic** — same inputs, same outputs, always. Rounding policy is explicit.\n- **Separation of concerns** — the financial verdict is kept strictly separate from\n  personal qualitative factors (stability, flexibility, family, work).\n\n## Technology\n\n| Layer         | Choice                                | Why                                                      |\n| ------------- | ------------------------------------- | -------------------------------------------------------- |\n| Language      | TypeScript (strict)                   | One language across engine and UI, strong domain typing  |\n| Domain engine | Pure TS package, zero UI deps         | Deterministic, portable, exhaustively testable           |\n| UI            | React 19 + Vite                       | Mature ecosystem for forms, charts, components           |\n| Styling       | Tailwind CSS v4 + Radix UI primitives | Modern, accessible, fast to build a polished UI          |\n| Charts        | Recharts                              | Declarative, fits the required chart set                 |\n| State         | Dexie live queries + React state      | Stored data is the single source of truth                |\n| Persistence   | IndexedDB via Dexie                   | Local-first, schema-versioned, migratable                |\n| Validation    | Zod                                   | Schemas double as domain validation and config contracts |\n| Testing       | Vitest + fast-check + Testing Library | Golden tests, property-based invariants                  |\n| Tooling       | pnpm workspaces, ESLint, Prettier     | Monorepo hygiene                                         |\n\nFull rationale and rejected alternatives: [`docs/03-architecture.md`](docs/03-architecture.md).\n\n## Repository layout\n\n```\ndomus-scope/\n├── packages/\n│   └── engine/          # Pure domain engine: schemas, mortgage math, simulation,\n│                        # rules, sensitivity, explanation traces, presets\n├── apps/\n│   └── web/             # React SPA (PWA): scenario workspace, results, sensitivity,\n│                        # negotiation, comparison, decision journal, settings\n└── docs/                # Source document, planning set, testing guide\n```\n\n## Documentation\n\n| Document                                                     | Content                                                                                                              |\n| ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |\n| [`docs/00-source-document.md`](docs/00-source-document.md)   | The original domain document the project was built from (translated from Italian)                                    |\n| [`docs/01-critique.md`](docs/01-critique.md)                 | Critical review of the source domain document: validated strengths, weaknesses, gaps                                 |\n| [`docs/02-domain-spec.md`](docs/02-domain-spec.md)           | Refined domain specification: methodology, corrected formulas, input/output catalogs, validation rules, test vectors |\n| [`docs/03-architecture.md`](docs/03-architecture.md)         | Stack decision, monorepo layout, engine design, configuration system, persistence, testing strategy                  |\n| [`docs/04-ui-design.md`](docs/04-ui-design.md)               | Information architecture, screens, design language, chart set, component inventory                                   |\n| [`docs/05-roadmap.md`](docs/05-roadmap.md)                   | Phased implementation plan with milestones, tasks, and acceptance criteria                                           |\n| [`docs/06-testing-guide.md`](docs/06-testing-guide.md)       | Hands-on guide: setup in three commands + a guided tour of every feature                                             |\n| [`docs/07-negotiation-lens.md`](docs/07-negotiation-lens.md) | Negotiation lens (Phase 8): reservation price, ZOPA view, concessions, offer log                                     |\n| [`docs/08-variable-rate.md`](docs/08-variable-rate.md)       | Variable-rate paths \u0026 partial early repayments (Phase 10): model, conventions, W-011                                 |\n| [`docs/09-italian-taxes.md`](docs/09-italian-taxes.md)       | Italian taxes that move the verdict (Phase 11): plusvalenza, detrazione ristrutturazione, IMU                        |\n\n## Status\n\n**All six roadmap phases complete, plus a localization \u0026 guidance pass (Phase 7), a\nnegotiation lens (Phase 8), a reliability pass (Phase 9), variable-rate mortgages\nwith partial early repayments (Phase 10), the Italian taxes that move the\nverdict (Phase 11), and a quality pass (Phase 12).**\nThe blueprint question of the source document (§21)\n— _\"given this house, this rent, this mortgage and my liquidity, when does buying beat\nrenting, and how fragile is that conclusion?\"_ — is answerable end-to-end, printable,\nand remembered:\n\n- **Engine** (Phases 0–2): derived-threshold quick rule, two-lens simulation (itemized\n  unrecoverable costs + budget-symmetric net-worth), Italian cost catalog, layered\n  assumptions with provenance, warnings W-001…W-011, `runSensitivity()` with fragility\n  index and verdict heatmap. Golden vectors from the source transcript pass byte-exactly;\n  property-based tests guard the amortization and simulation invariants.\n- **App** (Phases 3–5): Quick mode with live gauge and explanation drawer, full\n  analytical workspace (sectioned inputs, cost-catalog editor, results with five\n  CVD-validated charts and traceable year table, sensitivity tab), Compare view,\n  Profile \u0026 Assumptions with presets.\n- **Phase 6**: the **decision journal** (notes, visits, pros/cons, qualitative scores ×\n  personal weights → a preference index shown beside — never summed with — the euros),\n  the **decision record** that freezes the inputs it was based on, **snapshot history\n  with input diffs** (\"why did the result change?\"), the **print report** with\n  disclaimer and single-scenario JSON export, and **PWA** packaging (installable,\n  offline-capable). Storage schema v3 with cascading deletes; v1/v2 exports still\n  import.\n- **Phase 7**: **bilingual UI (English / Italiano)** on a typed, dependency-free\n  dictionary — a missing translation is a compile error — with a language switcher in\n  Settings (Auto / English / Italiano); **field-level help**: every input carries a ⓘ\n  popover explaining what it is, why it matters, typical Italian values, common\n  pitfalls, and the _direction_ of its effect on the verdict; and a **Glossary** page\n  (`/help`) collecting the same topics. Numbers and dates stay it-IT in both\n  languages by design (spec G12).\n- **Phase 8**: the **negotiation lens**\n  ([spec](docs/07-negotiation-lens.md)). The engine separates **market value**\n  from **transaction price**, then derives your **reservation price** — the\n  walk-away boundary where buying stops beating renting-and-investing — with a\n  grey band and a stressed range (never a false point estimate). The **ZOPA\n  view** places it against the asking price and the typical negotiated discount\n  (Banca d'Italia average, editable), warning (W-010) when the discount you\n  need is atypical. A **concession converter** prices non-price variables\n  (early possession, furniture, remediation) so trades become arithmetic, and\n  the journal gains an **offer log** where every offered price is re-evaluated\n  by the engine.\n- **Phase 9**: reliability pass — error boundary with a data-export escape hatch,\n  persistent-storage request with status in Settings, 404 route, mobile-nav and\n  `\u003chtml lang\u003e` accessibility fixes.\n- **Phase 10**: **variable-rate mortgages \u0026 partial early repayments**\n  ([spec](docs/08-variable-rate.md)). The rate becomes an explicit, editable\n  **path of step changes** (e.g. Euribor stress scenarios): each step\n  re-amortizes the remaining balance over the remaining contractual months,\n  and W-011 warns when the path produces a **payment shock**. **Estinzioni\n  anticipate parziali** inject extra principal in a chosen year — lowering the\n  payment or shortening the duration — and flow through both lenses as real\n  cash flows. Sensitivity shifts the whole path, not just the initial rate.\n- **Phase 11**: **Italian taxes that move the verdict**\n  ([spec](docs/09-italian-taxes.md)). A single **primary residence** toggle\n  drives three fiscal effects: the mortgage-interest deduction (primary\n  only), the **plusvalenza** — 26% on the gain of a non-primary property\n  sold within 5 years, shown on the liquidation basis of both lenses with a\n  traced composition line — and the purchase-tax regime (2% vs 9%\n  registration, plus an **IMU** preset on the cadastral base via the new\n  `percentOfCadastral` cost kind). One-time works flagged in the cost\n  catalog earn the **detrazione ristrutturazione** (50% of the capped spend\n  over 10 years), symmetric with the interest deduction.\n- **Phase 12**: quality pass. Full a11y round (skip-to-content link, live\n  verdict regions, `prefers-reduced-motion` for CSS *and* chart animations);\n  **route-level code splitting** — Recharts and the analytical workspace load\n  on demand, cutting the initial bundle by ~40%; PNG + maskable **PWA\n  icons**; engine dead-code removal (unread toggles, orphan formulas) with\n  the numeric-tolerance policy unified and documented; test-gap closure\n  (cost-resolver semantics, all three purchase-tax regimes, hold basis).\n\n178 unit/component tests green plus a **Playwright smoke** that drives the whole journey\nin a real browser **on desktop and phone viewports** (create → verdict → full analysis →\nnegotiation → decision → reload → remembered): `pnpm --filter @domus-scope/web e2e`.\n\nRun it: `pnpm --filter @domus-scope/web dev` → http://localhost:5173\n\n## Getting started\n\n**Just want to try the app?** Follow the\n[**testing guide**](docs/06-testing-guide.md) — setup in three commands plus a guided\ntour of every feature.\n\n### Prerequisites\n\n- **Node.js ≥ 22** (24.x works) — https://nodejs.org\n- **pnpm ≥ 9** — if you don't have it: `npm install -g pnpm` (or `corepack enable pnpm`\n  in an elevated shell on Windows)\n\n### Run the app\n\n```bash\npnpm install                          # once\npnpm --filter @domus-scope/web dev    # dev server → http://localhost:5173\n```\n\nProduction build + preview (what the PWA ships):\n\n```bash\npnpm --filter @domus-scope/web build\npnpm --filter @domus-scope/web preview   # → http://localhost:4173\n```\n\nAll data stays in the browser's IndexedDB on your machine — there is no server.\n\n### Development commands\n\n| Command                              | What it does                                                                  |\n| ------------------------------------ | ----------------------------------------------------------------------------- |\n| `pnpm check`                         | The full gate: typecheck + lint + all unit/component tests                    |\n| `pnpm test`                          | Engine + web test suites (golden, property-based, components)                 |\n| `pnpm --filter @domus-scope/web e2e` | Playwright smoke in a real browser (needs `playwright install chromium` once) |\n| `pnpm typecheck` / `pnpm lint`       | TypeScript strict check / ESLint (type-checked rules)                         |\n| `pnpm format`                        | Prettier over the whole repo                                                  |\n\nTo work on a single package: `pnpm --filter @domus-scope/engine test` (add watch mode\nvia `pnpm --filter @domus-scope/engine exec vitest`).\n\n### Using the engine as a library\n\n```ts\nimport { quickAssess, quickInputSchema, defaultEngineConfig } from \"@domus-scope/engine\";\n\nconst input = quickInputSchema.parse({\n  propertyPrice: 200_000,\n  equivalentMonthlyRent: 1_250,\n  horizonYears: 10,\n  financing: { kind: \"mortgage\", downPayment: 40_000, annualRate: 0.03, durationYears: 25 },\n});\n\nconst result = quickAssess(input, defaultEngineConfig);\nconsole.log(result.rule.threshold); // derived R*, e.g. 0.028 with default assumptions\nconsole.log(result.verdict.kind); // \"BUY_MORTGAGE\" | \"BUY_CASH\" | \"RENT\" | \"GREY_ZONE\"\nconsole.log(result.yearOne.mortgage?.items); // traced year-1 cost lines\n```\n\n## Disclaimer\n\nDomusScope is a personal analysis tool. It is **not** financial, tax, notarial, or\ninvestment advice. All defaults are editable assumptions, not predictions.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Friccardomerenda%2Fdomus-scope","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Friccardomerenda%2Fdomus-scope","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Friccardomerenda%2Fdomus-scope/lists"}