initial
This commit is contained in:
@@ -0,0 +1,503 @@
|
||||
# USAGE.md — finn-eiendom user guide
|
||||
|
||||
How to use the tool day-to-day. Covers installation, every CLI command, every MCP tool, Claude Desktop integration, common workflows, environment variables, and troubleshooting.
|
||||
|
||||
For the why and the architecture, see [`README.md`](README.md) and [`PRD.md`](PRD.md).
|
||||
|
||||
---
|
||||
|
||||
## 1. Installation
|
||||
|
||||
### Requirements
|
||||
|
||||
* Python **3.12 or newer** (check with `python3 --version`)
|
||||
* `uv` (recommended) or `pip`
|
||||
* macOS, Linux, or WSL2 on Windows
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
git clone <your-repo-url> finn-mcp
|
||||
cd finn-mcp
|
||||
|
||||
# Option A: uv (preferred — fast)
|
||||
uv venv
|
||||
source .venv/bin/activate
|
||||
uv pip install -e ".[dev]"
|
||||
|
||||
# Option B: pip
|
||||
python3.12 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
Verify:
|
||||
|
||||
```bash
|
||||
finn-eiendom --help
|
||||
finn-eiendom-mcp --help # may exit immediately on stdio mode; that's fine
|
||||
finn-eiendom doctor # smoke-checks cache, FINN, Eiendom.no reachability
|
||||
```
|
||||
|
||||
### Updating
|
||||
|
||||
```bash
|
||||
git pull
|
||||
source .venv/bin/activate
|
||||
uv pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
If `pyproject.toml` added dependencies, the second command picks them up.
|
||||
|
||||
### Global install (optional)
|
||||
|
||||
If you want `finn-eiendom` available system-wide without activating the venv:
|
||||
|
||||
```bash
|
||||
uv tool install .
|
||||
# or
|
||||
pipx install .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. First-time setup
|
||||
|
||||
### Set up the data directory
|
||||
|
||||
```bash
|
||||
mkdir -p data
|
||||
```
|
||||
|
||||
SQLite cache lives there at `data/finn.sqlite` by default. Override with `FINN_CACHE_PATH` if you want it elsewhere.
|
||||
|
||||
### Optional: environment file
|
||||
|
||||
Create `.env` in the project root for your usual settings:
|
||||
|
||||
```bash
|
||||
FINN_CACHE_PATH=data/finn.sqlite
|
||||
FINN_MAX_SEARCH_PAGES=3
|
||||
FINN_DETAIL_LIMIT=20
|
||||
EIENDOM_NO_ENABLED=true
|
||||
EIENDOM_NO_SIMILAR_UNITS_ENABLED=true
|
||||
LOG_LEVEL=INFO
|
||||
```
|
||||
|
||||
See §7 for the full list of variables.
|
||||
|
||||
### Verify
|
||||
|
||||
```bash
|
||||
finn-eiendom doctor
|
||||
```
|
||||
|
||||
This pings the cache, reaches FINN once, reaches Eiendom.no once, and reports any failures.
|
||||
|
||||
---
|
||||
|
||||
## 3. CLI reference
|
||||
|
||||
Every command runs inside the activated venv.
|
||||
|
||||
### 3.1 Analyze a FINN search
|
||||
|
||||
```bash
|
||||
finn-eiendom analyze-search '<finn-search-url>' [options]
|
||||
```
|
||||
|
||||
| Option | Default | Purpose |
|
||||
| ------------------- | ------- | ---------------------------------------------------------- |
|
||||
| `--max-pages N` | `3` | Pages of search results to fetch. |
|
||||
| `--detail-limit N` | `20` | How many listings to detail-fetch from the result set. |
|
||||
| `--no-details` | off | Skip detail fetches; use only search-card data. |
|
||||
| `--no-eiendom` | off | Skip Eiendom.no enrichment. |
|
||||
| `--with-similar` | off | Fetch similar-units / comps for shortlisted listings. |
|
||||
| `--format FMT` | `json` | `json`, `markdown`, or `table`. |
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
# Triage in the terminal
|
||||
finn-eiendom analyze-search 'https://www.finn.no/realestate/homes/search.html?location=0.20061&min_bedrooms=2&price_collective_to=12000000' --format table
|
||||
|
||||
# Full JSON for piping into jq
|
||||
finn-eiendom analyze-search '<url>' --format json | jq '.shortlist[].title'
|
||||
|
||||
# Detailed run with comps
|
||||
finn-eiendom analyze-search '<url>' --detail-limit 30 --with-similar --format markdown
|
||||
```
|
||||
|
||||
### 3.2 Drill into one listing
|
||||
|
||||
```bash
|
||||
finn-eiendom get-ad <finnkode> [options]
|
||||
```
|
||||
|
||||
| Option | Default | Purpose |
|
||||
| ------------------- | ------- | -------------------------------------------------- |
|
||||
| `--force-refresh` | off | Bypass the 24h cache and refetch. |
|
||||
| `--no-eiendom` | off | Skip Eiendom.no enrichment. |
|
||||
| `--with-similar` | off | Fetch similar-units / comps. |
|
||||
| `--format FMT` | `json` | `json` or `markdown`. |
|
||||
|
||||
```bash
|
||||
finn-eiendom get-ad 462400360 --format markdown
|
||||
finn-eiendom get-ad 462400360 --force-refresh --with-similar
|
||||
```
|
||||
|
||||
### 3.3 Compare listings
|
||||
|
||||
```bash
|
||||
finn-eiendom compare <finnkode> <finnkode> [<finnkode>...] [options]
|
||||
```
|
||||
|
||||
| Option | Default | Purpose |
|
||||
| ---------------- | ------- | -------------------------------------- |
|
||||
| `--no-eiendom` | off | Skip Eiendom.no enrichment. |
|
||||
| `--no-comps` | off | Skip similar-units / comps. |
|
||||
| `--format FMT` | `json` | `json`, `markdown`, or `table`. |
|
||||
|
||||
```bash
|
||||
finn-eiendom compare 462400360 461153194 --format markdown
|
||||
finn-eiendom compare 462400360 461153194 462400360 --format table
|
||||
```
|
||||
|
||||
Up to 10 finnkoder per call.
|
||||
|
||||
### 3.4 Feedback
|
||||
|
||||
```bash
|
||||
finn-eiendom save-feedback <finnkode> <verdict> [--notes "..."]
|
||||
```
|
||||
|
||||
Verdict vocabulary: `liked`, `rejected`, `interesting`, `bargain_candidate`, `risk_object`, `viewing_candidate`, `viewed`, `too_expensive`, `too_small`, `too_far_out`, `too_high_risk`, `likes_location`, `likes_layout`, `dislikes_area`.
|
||||
|
||||
```bash
|
||||
finn-eiendom save-feedback 462400360 liked --notes "balcony, view, check wet rooms"
|
||||
finn-eiendom save-feedback 461153194 rejected --notes "too far from city center"
|
||||
```
|
||||
|
||||
`liked` verdicts feed the `similar-to-liked` command.
|
||||
|
||||
### 3.5 New / removed / changed listings
|
||||
|
||||
```bash
|
||||
finn-eiendom diff '<finn-search-url>' [--format FMT]
|
||||
```
|
||||
|
||||
Compares the current search results against the previous run for the same normalized URL and reports new finnkoder, removed finnkoder, and changed listings (price, common costs, status).
|
||||
|
||||
```bash
|
||||
finn-eiendom diff '<url>' --format table
|
||||
```
|
||||
|
||||
Useful as a daily cron:
|
||||
|
||||
```bash
|
||||
0 9 * * * cd /path/to/finn-mcp && .venv/bin/finn-eiendom diff 'https://www.finn.no/...' --format markdown >> diff.log
|
||||
```
|
||||
|
||||
### 3.6 Shortlist history
|
||||
|
||||
```bash
|
||||
finn-eiendom shortlist [--run-id ID] [--limit N] [--format FMT]
|
||||
```
|
||||
|
||||
Without `--run-id`, returns the latest saved shortlist.
|
||||
|
||||
### 3.7 Eiendom.no commands
|
||||
|
||||
```bash
|
||||
finn-eiendom resolve-unit '<finn-listing-url>' # find unitCode for a FINN listing
|
||||
finn-eiendom get-unit <unit_code> [--force-refresh] # fetch unit detail
|
||||
finn-eiendom enrich-ad <finnkode> [--with-similar] # FINN + Eiendom.no combined
|
||||
finn-eiendom build-vector <unit_code> # build the base64url unit_vector
|
||||
finn-eiendom decode-vector <unit_vector> # decode for inspection
|
||||
finn-eiendom similar-units <unit_vector> [--status RECENTLY_SOLD|FOR_SALE|CURRENT]
|
||||
```
|
||||
|
||||
### 3.8 Find similar to liked
|
||||
|
||||
```bash
|
||||
finn-eiendom similar-to-liked <finnkode> [--mode recommendations|comps] [--status STATUS]
|
||||
```
|
||||
|
||||
The listing must have a `liked` feedback row. Defaults to `mode=recommendations`, `status=FOR_SALE` — i.e. find active listings similar to this one. Use `--mode comps --status RECENTLY_SOLD` to get comparable sales instead.
|
||||
|
||||
### 3.9 Price analysis against comps
|
||||
|
||||
```bash
|
||||
finn-eiendom analyze-against-comps <finnkode>
|
||||
```
|
||||
|
||||
Returns `price_position` (`below_estimate` / `within_range` / `above_estimate`), `sqm_price_position` (`cheap` / `normal` / `expensive`), `comparable_score`, and a `confidence` label.
|
||||
|
||||
### 3.10 Cache management
|
||||
|
||||
```bash
|
||||
finn-eiendom cache stats # row counts and TTL summary
|
||||
finn-eiendom cache clear # purge everything except feedback
|
||||
finn-eiendom cache clear-html # only purge raw HTML
|
||||
finn-eiendom cache clear-json # only purge raw JSON
|
||||
```
|
||||
|
||||
Feedback is never purged by `cache clear` — feedback is permanent until explicitly deleted via SQL.
|
||||
|
||||
### 3.11 MCP server
|
||||
|
||||
```bash
|
||||
finn-eiendom serve # stdio (default)
|
||||
finn-eiendom serve --transport http --port 8010 # HTTP for n8n / multi-client
|
||||
```
|
||||
|
||||
In HTTP mode the server listens on `http://127.0.0.1:8010/mcp` with operational endpoints `GET /health`, `GET /version`, `GET /debug/config`.
|
||||
|
||||
There's also a shorthand `finn-eiendom-mcp` that starts stdio mode directly — that's what Claude Desktop calls.
|
||||
|
||||
### 3.12 Misc
|
||||
|
||||
```bash
|
||||
finn-eiendom config show # print resolved configuration
|
||||
finn-eiendom config path # print SQLite cache path
|
||||
finn-eiendom doctor # smoke checks
|
||||
finn-eiendom version
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. MCP tools (for Claude Desktop / n8n / agents)
|
||||
|
||||
All tools use the `finn_` prefix. They mirror the CLI commands 1:1 — same defaults, same semantics.
|
||||
|
||||
| Tool | Purpose |
|
||||
| ------------------------------------- | ---------------------------------------------------------------- |
|
||||
| `finn_analyze_search` | Analyze a FINN search URL and return a ranked shortlist. |
|
||||
| `finn_get_ad` | Fetch structured data for one finnkode. |
|
||||
| `finn_compare_ads` | Compare multiple listings side by side. |
|
||||
| `finn_save_feedback` | Store feedback/verdict/notes. |
|
||||
| `finn_get_shortlist` | Fetch a stored shortlist from a previous run. |
|
||||
| `finn_get_new_ads_since_last_run` | Detect new / removed / changed listings. |
|
||||
| `finn_resolve_eiendom_unit` | Map FINN URL → Eiendom.no `unitCode`. |
|
||||
| `finn_get_eiendom_unit` | Fetch Eiendom.no unit detail by `unitCode`. |
|
||||
| `finn_enrich_ad` | Combine FINN listing + Eiendom.no enrichment. |
|
||||
| `finn_build_unit_vector` | Build a `unit_vector` from a `unitCode`. |
|
||||
| `finn_decode_unit_vector` | Decode a `unit_vector` for inspection. |
|
||||
| `finn_get_similar_units` | Fetch comps / recommendations. |
|
||||
| `finn_find_similar_to_liked_ad` | Find properties similar to one you liked. |
|
||||
| `finn_analyze_ad_against_comps` | Evaluate a listing against `RECENTLY_SOLD` comps. |
|
||||
|
||||
Every tool accepts a `response_format` parameter (`"json"` or `"markdown"`). Errors come back as `{"error": true, "code": "<ExceptionName>", "message": "..."}`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Claude Desktop setup
|
||||
|
||||
### Config file
|
||||
|
||||
* macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
||||
* Linux: `~/.config/Claude/claude_desktop_config.json`
|
||||
|
||||
### Direct entry-point (recommended)
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"finn-eiendom": {
|
||||
"command": "/absolute/path/to/finn-mcp/.venv/bin/finn-eiendom-mcp",
|
||||
"env": {
|
||||
"FINN_CACHE_PATH": "/absolute/path/to/finn-mcp/data/finn.sqlite",
|
||||
"EIENDOM_NO_ENABLED": "true",
|
||||
"EIENDOM_NO_SIMILAR_UNITS_ENABLED": "true",
|
||||
"LOG_LEVEL": "INFO"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `command` **must** be the absolute path to the venv's `finn-eiendom-mcp` binary. Don't rely on `$PATH` here — Claude Desktop doesn't inherit your shell environment.
|
||||
|
||||
### Alternative: via `uv`
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"finn-eiendom": {
|
||||
"command": "uv",
|
||||
"args": ["run", "finn-eiendom-mcp"],
|
||||
"cwd": "/absolute/path/to/finn-mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Verify
|
||||
|
||||
1. Restart Claude Desktop.
|
||||
2. Look for `finn-eiendom` in the MCP servers indicator (usually a hammer icon).
|
||||
3. Ask in any chat: *"Use the finn-eiendom server to analyze this search: ..."*
|
||||
|
||||
If it doesn't show up, check the Claude Desktop logs:
|
||||
|
||||
* macOS: `~/Library/Logs/Claude/mcp-server-finn-eiendom.log`
|
||||
* Linux: `~/.local/share/Claude/logs/mcp-server-finn-eiendom.log`
|
||||
|
||||
stdout output from the server is a fatal error — the server must only log to stderr.
|
||||
|
||||
---
|
||||
|
||||
## 6. Common workflows
|
||||
|
||||
### 6.1 Daily triage
|
||||
|
||||
```bash
|
||||
# Morning routine
|
||||
finn-eiendom diff 'https://www.finn.no/...' --format table
|
||||
# Detail-fetch only what's new or changed
|
||||
finn-eiendom analyze-search 'https://www.finn.no/...' --detail-limit 10 --format markdown
|
||||
```
|
||||
|
||||
### 6.2 Weekly deep dive in Claude Desktop
|
||||
|
||||
> Read my latest finn-eiendom shortlist and group the top 10 by category (bargain / safe / hybel / lifestyle). For each, summarize the three most important risks and the three most important broker questions.
|
||||
|
||||
### 6.3 Pre-viewing prep
|
||||
|
||||
```bash
|
||||
# Mark candidates for viewing
|
||||
finn-eiendom save-feedback 462400360 viewing_candidate --notes "Saturday 14:00"
|
||||
# Get the full data + comps
|
||||
finn-eiendom get-ad 462400360 --with-similar --format markdown > viewing_prep_462400360.md
|
||||
```
|
||||
|
||||
Then in Claude Desktop:
|
||||
|
||||
> Read the saved markdown for finnkode 462400360 and prepare a viewing checklist: wet rooms to inspect, common-costs questions, hybel-approval question, neighbor questions.
|
||||
|
||||
### 6.4 Comparing finalists
|
||||
|
||||
```bash
|
||||
finn-eiendom compare 462400360 461153194 459333210 --format markdown > finalists.md
|
||||
```
|
||||
|
||||
### 6.5 Build a recommendation set from liked properties
|
||||
|
||||
```bash
|
||||
# After you've liked a few
|
||||
finn-eiendom save-feedback 462400360 liked
|
||||
finn-eiendom save-feedback 461153194 liked
|
||||
|
||||
# Get recommendations similar to each
|
||||
finn-eiendom similar-to-liked 462400360 --mode recommendations --status FOR_SALE
|
||||
finn-eiendom similar-to-liked 461153194 --mode recommendations --status FOR_SALE
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Environment variables
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
| ----------------------------------------- | -------------------------------: | -------------------------------- |
|
||||
| `FINN_CACHE_PATH` | `data/finn.sqlite` | SQLite DB path |
|
||||
| `FINN_MAX_SEARCH_PAGES` | `3` | Max search pages per analyze |
|
||||
| `FINN_DETAIL_LIMIT` | `20` | Max detail fetches per analyze |
|
||||
| `FINN_REQUEST_DELAY_SECONDS` | `2` | Seconds between FINN requests |
|
||||
| `FINN_USER_AGENT` | `personal-finn-eiendom-analyzer/0.1` | HTTP User-Agent |
|
||||
| `FINN_CACHE_TTL_SEARCH_MINUTES` | `60` | Search cache TTL |
|
||||
| `FINN_CACHE_TTL_AD_HOURS` | `24` | Listing cache TTL |
|
||||
| `EIENDOM_NO_ENABLED` | `true` | Enable Eiendom.no enrichment |
|
||||
| `EIENDOM_NO_BASE_URL` | `https://api.eiendom.no/api/v1` | API base URL |
|
||||
| `EIENDOM_NO_CACHE_TTL_HOURS` | `24` | Unit/similar cache TTL |
|
||||
| `EIENDOM_NO_REQUEST_DELAY_SECONDS` | `1` | Seconds between Eiendom.no calls |
|
||||
| `EIENDOM_NO_SIMILAR_UNITS_ENABLED` | `true` | Enable similar-units |
|
||||
| `EIENDOM_NO_SIMILAR_UNITS_DEFAULT_STATUS` | `RECENTLY_SOLD` | Default comps status |
|
||||
| `HJEMLA_ENABLED` | `false` | Enable optional Hjemla API |
|
||||
| `LOG_LEVEL` | `INFO` | Log level |
|
||||
| `MCP_TRANSPORT` | `stdio` | `stdio` or `streamable_http` |
|
||||
| `MCP_HTTP_HOST` | `127.0.0.1` | HTTP bind address |
|
||||
| `MCP_HTTP_PORT` | `8010` | HTTP port |
|
||||
|
||||
Set them in `.env`, in your shell, or in the Claude Desktop `env` block per §5.
|
||||
|
||||
---
|
||||
|
||||
## 8. Troubleshooting
|
||||
|
||||
### Claude Desktop doesn't see the server
|
||||
|
||||
1. The `command` path must be absolute and point at the venv's binary.
|
||||
2. Check `~/Library/Logs/Claude/mcp-server-finn-eiendom.log` (macOS) for a Python traceback.
|
||||
3. The server **must not** write to stdout — any `print()` in the code breaks JSON-RPC. If you're hacking on it and see a frame parse error, that's the cause.
|
||||
4. Restart Claude Desktop after config changes (`Cmd+Q`, not just close the window).
|
||||
|
||||
### "Module not found" when running CLI
|
||||
|
||||
The venv isn't activated, or the package isn't installed in editable mode.
|
||||
|
||||
```bash
|
||||
source .venv/bin/activate
|
||||
uv pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
### Eiendom.no enrichment is `unavailable`
|
||||
|
||||
This is graceful degradation when:
|
||||
|
||||
* The FINN URL can't be matched to a `unitCode` (rare, but happens for unusual addresses).
|
||||
* Eiendom.no rate-limited or returned 5xx.
|
||||
* The unit was deleted from Eiendom.no's index.
|
||||
|
||||
Check the log for the warning. The listing analysis continues without enrichment.
|
||||
|
||||
### Similar-units returns nothing
|
||||
|
||||
* Verify `EIENDOM_NO_SIMILAR_UNITS_ENABLED=true`.
|
||||
* The `unit_vector` might be empty / malformed — check `finn-eiendom decode-vector <unit_vector>`.
|
||||
* Try `--status FOR_SALE` if `RECENTLY_SOLD` is sparse, or vice versa.
|
||||
|
||||
### Slow first run
|
||||
|
||||
The first analyze fills the cache. Subsequent runs are much faster. Tune `FINN_REQUEST_DELAY_SECONDS` and `EIENDOM_NO_REQUEST_DELAY_SECONDS` if you're impatient — but don't drop them too low, the whole point of caching is to be polite.
|
||||
|
||||
### Stale results
|
||||
|
||||
Cache TTLs:
|
||||
|
||||
* Search: 60 minutes
|
||||
* FINN listing: 24 hours
|
||||
* Eiendom.no unit: 24 hours
|
||||
* Similar-units: 24 hours
|
||||
|
||||
Force a refresh with `--force-refresh` on `get-ad` or `get-unit`, or wipe with `finn-eiendom cache clear`.
|
||||
|
||||
### `pytest` fails after pulling new changes
|
||||
|
||||
```bash
|
||||
source .venv/bin/activate
|
||||
uv pip install -e ".[dev]" # re-sync dependencies
|
||||
pytest -x # find the first failure
|
||||
```
|
||||
|
||||
If a test fails with a network-related error, that's a bug — tests should never hit the network. Report it.
|
||||
|
||||
---
|
||||
|
||||
## 9. What this tool is not
|
||||
|
||||
* Not a public API. Don't expose the HTTP transport on the open internet.
|
||||
* Not financial, legal, or valuation advice. Scores and estimates are decision support.
|
||||
* Not a bidding agent. It will never contact a broker or place a bid for you.
|
||||
* Not a crawler. Use it for the searches you'd be manually browsing anyway — at your own pace.
|
||||
* Not a substitute for a real condition report (`tilstandsrapport`), a real lawyer, or a real broker.
|
||||
|
||||
---
|
||||
|
||||
## 10. Getting help
|
||||
|
||||
* [`README.md`](README.md) — overview
|
||||
* [`PRD.md`](PRD.md) — full product spec and architecture
|
||||
* [`AGENTS.md`](AGENTS.md) — workflow rules for contributors
|
||||
* [`.github/instructions/*.md`](.github/instructions/) — per-topic conventions
|
||||
|
||||
For bugs, open an issue in the repo with: the exact command run, the full traceback or unexpected output, the version (`finn-eiendom version`), and a redacted FINN URL if relevant.
|
||||
Reference in New Issue
Block a user