initial
This commit is contained in:
+162
@@ -0,0 +1,162 @@
|
||||
# PROJECT.md — module map
|
||||
|
||||
The repo at a glance. For the why and the rules, read [`PRD.md`](PRD.md) §12 and §17. For the workflow, read [`AGENTS.md`](AGENTS.md).
|
||||
|
||||
---
|
||||
|
||||
## Source tree
|
||||
|
||||
```
|
||||
finn-mcp/
|
||||
├── pyproject.toml
|
||||
├── Makefile
|
||||
├── README.md ← user-facing overview
|
||||
├── USAGE.md ← full user guide
|
||||
├── PRD.md ← product spec + architecture (§17 = constitution)
|
||||
├── PROJECT.md ← this file
|
||||
├── AGENTS.md ← workflow for AI agents and contributors
|
||||
├── CLEANUP.md ← pre-Phase-2 cleanup runbook
|
||||
├── IMPLEMENTATION.md ← Phase 2 build runbook (12 steps)
|
||||
│
|
||||
├── .github/
|
||||
│ ├── copilot-instructions.md
|
||||
│ └── instructions/
|
||||
│ ├── python.instructions.md
|
||||
│ ├── mcp.instructions.md
|
||||
│ ├── cli.instructions.md
|
||||
│ ├── tests.instructions.md
|
||||
│ ├── clean-code.instructions.md
|
||||
│ └── docs.instructions.md ← context7 lookup rules
|
||||
│
|
||||
├── finn_eiendom/ ← the package
|
||||
│ ├── __init__.py
|
||||
│ ├── __main__.py ← python -m finn_eiendom → CLI
|
||||
│ ├── config.py ← env vars, defaults, TTLs
|
||||
│ ├── models.py ← Pydantic v2 models
|
||||
│ ├── parser.py ← Norwegian number/area/URL/finnkode normalization
|
||||
│ ├── http.py ← async httpx client w/ retry + delay
|
||||
│ ├── cache.py ← SQLite schema + persistence
|
||||
│ ├── search.py ← FINN search HTML parsing
|
||||
│ ├── ad.py ← FINN listing HTML parsing
|
||||
│ ├── eiendom_no.py ← Eiendom.no unit search/detail, unit_vector, comps
|
||||
│ ├── scoring.py ← score model + classifications
|
||||
│ ├── feedback.py ← verdicts + soft preference signal
|
||||
│ ├── analysis.py ← shortlist + summary assembly
|
||||
│ ├── service.py ← get_or_fetch_* + thin facade for MCP and CLI
|
||||
│ ├── formatting.py ← render_* helpers (json/markdown/table) — shared by MCP and CLI
|
||||
│ ├── mcp_server.py ← FastMCP wrappers around service.py
|
||||
│ └── cli.py ← typer wrappers around service.py
|
||||
│
|
||||
├── tests/
|
||||
│ ├── conftest.py
|
||||
│ ├── fixtures.py
|
||||
│ ├── fixtures/ ← HTML + JSON samples
|
||||
│ ├── test_parser.py
|
||||
│ ├── test_search.py
|
||||
│ ├── test_ad.py
|
||||
│ ├── test_eiendom_no.py
|
||||
│ ├── test_scoring.py
|
||||
│ ├── test_cache.py
|
||||
│ ├── test_http.py ← retry + delay behavior
|
||||
│ ├── test_service.py ← get_or_fetch_* + analyze_*
|
||||
│ ├── test_formatting.py ← render_* roundtrips
|
||||
│ ├── test_models.py ← Pydantic v2 roundtrips
|
||||
│ ├── test_mcp_server.py ← tool registration + error envelope
|
||||
│ ├── test_cli.py ← Typer CliRunner
|
||||
│ └── test_architecture.py ← import-graph invariants (PRD A10)
|
||||
│
|
||||
└── data/ ← gitignored; SQLite cache lives here
|
||||
└── finn.sqlite
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Module responsibilities
|
||||
|
||||
Single-home rule: every concern lives in exactly one module. See `PRD.md` §17.2 for the full table.
|
||||
|
||||
| Module | Owns | Imports allowed |
|
||||
| --------------- | --------------------------------------------------------------------- | ---------------------------------------------------------- |
|
||||
| `config.py` | env-var loading, defaults, TTL constants | stdlib |
|
||||
| `models.py` | Pydantic v2 models | stdlib, `pydantic` |
|
||||
| `parser.py` | Norwegian text normalization (numbers, dates, URLs, finnkode) | stdlib |
|
||||
| `http.py` | async `httpx.AsyncClient`, retry on 5xx, delay, user-agent | stdlib, `httpx` |
|
||||
| `cache.py` | SQLite schema, reads, writes, TTL | stdlib, `sqlite3`, `models` |
|
||||
| `search.py` | FINN search HTML → cards (BeautifulSoup) | stdlib, `bs4`, `parser`, `http`, `cache`, `models` |
|
||||
| `ad.py` | FINN listing HTML → `FinnAd` (BeautifulSoup) | stdlib, `bs4`, `parser`, `http`, `cache`, `models` |
|
||||
| `eiendom_no.py` | Eiendom.no unit search/detail, unit_vector, similar-units (msgpack) | stdlib, `msgpack`, `http`, `cache`, `models` |
|
||||
| `scoring.py` | 9 score components, total clamping, category classifier | stdlib, `models` |
|
||||
| `feedback.py` | feedback storage and retrieval | stdlib, `cache`, `models` |
|
||||
| `analysis.py` | shortlist + summary assembly | stdlib, `search`, `ad`, `eiendom_no`, `scoring`, `feedback`|
|
||||
| `service.py` | cache-aware orchestration; the only place that combines fetch + cache | stdlib, `config`, `cache`, `analysis`, `ad`, `eiendom_no`, `feedback`, `scoring`, `models` |
|
||||
| `formatting.py` | render_* helpers (json/markdown/table) | stdlib, `models` |
|
||||
| `mcp_server.py` | FastMCP tool definitions, error wrapping, stdio/HTTP entry | stdlib, `mcp`, `pydantic`, `service`, `formatting`, `config`, `models` |
|
||||
| `cli.py` | typer command definitions, --format dispatch | stdlib, `typer`, `service`, `formatting`, `config`, `models` |
|
||||
|
||||
`mcp_server.py` and `cli.py` are siblings — they never import each other. `service.py` never imports `mcp_server` or `cli`. `tests/test_architecture.py` enforces all of this.
|
||||
|
||||
---
|
||||
|
||||
## Entry points
|
||||
|
||||
Defined in `pyproject.toml`:
|
||||
|
||||
```toml
|
||||
[project.scripts]
|
||||
finn-eiendom-mcp = "finn_eiendom.mcp_server:main"
|
||||
finn-eiendom = "finn_eiendom.cli:app"
|
||||
```
|
||||
|
||||
So you have:
|
||||
|
||||
* `finn-eiendom-mcp` — MCP server over stdio (what Claude Desktop calls).
|
||||
* `finn-eiendom` — CLI with all subcommands.
|
||||
* `python -m finn_eiendom` — same as `finn-eiendom` (via `__main__.py`).
|
||||
* `import finn_eiendom` — the library, for tests and notebooks.
|
||||
|
||||
---
|
||||
|
||||
## Dependency graph
|
||||
|
||||
```
|
||||
cli.py mcp_server.py
|
||||
↓ ↓
|
||||
└──> formatting.py <──┘
|
||||
│
|
||||
↓
|
||||
service.py
|
||||
↓
|
||||
analysis.py
|
||||
↓
|
||||
┌───────────┼──────────────┐
|
||||
↓ ↓ ↓
|
||||
search.py ad.py eiendom_no.py scoring.py feedback.py
|
||||
│ │ │ │ │
|
||||
↓ ↓ ↓ ↓ ↓
|
||||
parser.py parser.py cache.py models.py cache.py
|
||||
│ │ │
|
||||
↓ ↓ ↓
|
||||
http.py http.py http.py
|
||||
```
|
||||
|
||||
Bottom layer: `parser.py`, `http.py`, `cache.py`, `models.py`, `config.py`. They depend only on stdlib + one third-party library each.
|
||||
|
||||
The graph is acyclic and points downward. Every arrow can be drawn; no arrow can be drawn upward.
|
||||
|
||||
---
|
||||
|
||||
## Where to add things
|
||||
|
||||
| You want to… | Add it to… |
|
||||
| ----------------------------------------- | --------------------------------------- |
|
||||
| Parse a new FINN field | `ad.py` or `search.py` + `models.py` |
|
||||
| Add a new score component | `scoring.py` |
|
||||
| Add a new env var | `config.py` |
|
||||
| Add a new MCP tool | `mcp_server.py` (after `service.py`) |
|
||||
| Add a new CLI command | `cli.py` (after `service.py`) |
|
||||
| Change how something renders | `formatting.py` |
|
||||
| Add a new orchestration / workflow | `service.py` (then add MCP + CLI) |
|
||||
| Speak to a new external API | new module next to `eiendom_no.py` |
|
||||
| Add a new SQLite table | `cache.py` |
|
||||
|
||||
For anything else — read `PRD.md` §17.2 and §17.7.
|
||||
Reference in New Issue
Block a user