尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
Dubai property investment intelligence from open Dubai Land Department data.
Valuation, yield, risk and a cited, agent-written investment memo — on a 3D Dubai.
Built for SP Jain MAIB AI 217 (AI in Finance). Solo project by Krishna Mathur.
Information, not advice. YIELDMAP explains, compares and simulates. It does not recommend
that you buy, sell or finance anything, and it cannot execute a transaction of any kind.檢視完整 README ↗Dubai property investment intelligence from open Dubai Land Department data. Valuation, yield, risk and a cited, agent-written investment memo — on a 3D Dubai.
Built for SP Jain MAIB AI 217 (AI in Finance). Solo project by Krishna Mathur.
Information, not advice. YIELDMAP explains, compares and simulates. It does not recommend that you buy, sell or finance anything, and it cannot execute a transaction of any kind.
Dubai recorded roughly 147,500 unit sales in 2025 and about 1.38 million tenancy contracts. The registry behind all of it is public, unit-level and daily — yet buyers and small investors still decide from broker claims and portal listings. YIELDMAP turns that registry into numbers you can check.
Nothing in this project is a hand-typed figure. Every value crossing the API is a Kpi object
carrying its sample size, as-of date, confidence, method id and the exact SQL that produced it.
Click any KPI tile and the query is right there. Cells with too few transactions say
"insufficient data" instead of showing a misleading number.
| What | Where | Status |
|---|---|---|
| Web app | https://yieldmap-krishnamathur008-1499s-projects.vercel.app | twenty tabs, live. Open it on a phone — the layout is built for one |
| Analytics API | https://yieldmap-api.onrender.com | free tier: sleeps after 15 minutes idle, so the first request takes about 50 seconds |
| API health | https://yieldmap-api.onrender.com/health | says which database is loaded and whether it is real or generated |
| Ingest job | Actions → Ingest DLD open data | nightly; probe and discovery reports land in docs/results/ |
| Continuous integration | Actions → CI | five jobs: python, web, docs, security, e2e |
| Notebook | docs/term4_yieldmap.ipynb | GitHub renders it with its charts |
The figures are currently generated, not real, and the site says so on every page. The Dubai
Land Department's open-data portal refuses automated clients from every network this project can
reach; docs/datasets.md records exactly what was attempted and what each host returned. The
stand-in is stamped SYNTHETIC in the database, in a banner on every page, and in every results
file, and a check in CI blocks it from reaching any submitted artefact. Point the ingest job at a
real drop and every number on the site changes; nothing else has to.
Answers on /ask and memos on /crew run on an offline fixture backend and are marked degraded
in the interface. That is the zero-cost default: setting GEMINI_API_KEY on the Render service
switches the free model tier on with no code change.
Every figure below is read from docs/results/, produced by the models in finance/.
They currently describe the labelled stand-in, not the Dubai registry — see
docs/datasets.md for why, and for how to load the real files.
| Model | Measured | Target |
|---|---|---|
| Hedonic valuation | 18.9% MAPE on a time-split holdout, against a 36.4% baseline — +48% skill, at the 19.4% noise floor | 12% MAPE, on real data |
| Repeat-sales index | 5,079 pairs over 144 months, 0 periods dropped as disconnected | reproduces direction of travel |
| Yield | median gross 7.12%, net 3.43%, gap 3.59%; 115 of 463 cells suppressed as too thin | net below gross always |
| Mortgage and DCF | 20% deposit case exact; levered 8.61% vs unlevered 5.75% at 3% growth | IRR matches a hand-computed case |
| Forecast | beats seasonal naive on 77/86 areas (90%), 10.1% vs 15.2% error | 60% of areas |
| Risk and anomalies | 1,738 of 120,346 flagged (1.44%); 86 areas scored | score in [0,100], monotone |
| Retrieval | recall@5 1.00, recall@1 0.87, MRR 0.91, faithfulness 1.00 over 30 cases in three languages — on a 117-chunk corpus, where recall@5 is easy and the rank measures are the informative ones | gated at 0.80 / 0.70 / 0.90 |
| Red team | 11/11 controls held against the OWASP ASI threat list | every attack fails to land |
| Cost | $0.00/month; the free tier binds at ~100 daily users, on embeddings before generation | stays free at course scale |
| Journey | 78 Playwright tests green on a desktop viewport and a Pixel 7, run in CI | the full journey, on a phone |
| Degraded mode | every route loaded with the API refusing: the ten that fetch on load show an error and a working retry, the six form pages stay usable, nothing crashes | never a blank page |
| Python | 1,025 tests, 14 skipped | — |
| Front end | 25 tests, including one that fails if a string renders in English under another locale | every key translated |
| Coverage | 83.9% of 6,676 statements; the 10 modules that are the sole mechanism behind a rule in CLAUDE.md are held to 85% and sit at 89–100% | 75% overall, 85% on those |
| Access control | writes need a token the API signed; the role is never something a caller sends | no credential, no write |
| Frame rate |
Reads are public. Writing a memo — the only endpoint that writes, and the only one that spends a model budget — needs an account:
uv run python -m api.auth hash 'a-password' # -> $argon2id$...
export AUTH_SECRET="$(python3 -c 'import secrets;print(secrets.token_urlsafe(48))')"
export YIELDMAP_USERS='[email protected]:analyst:$argon2id$...'
Set YIELDMAP_ENV=production and the service refuses to start without those, rather than running
in a state where tokens silently stop working on restart. GET /health reports whether
authentication is configured and whether memo storage is durable, so a deployment can be checked
from outside without reading its environment.
Optional: DATABASE_URL for durable memos (without it they go to a file store that a container
loses on restart), and SENTRY_DSN for error tracking. Neither is required to run.
| Document | What it is |
|---|---|
docs/viva_qa.md | Fifteen likely questions, answered from what the code says, each naming the file that backs it |
docs/demo_script.md | A timed three-minute run-through, with what to click and what to say |
docs/pitch.md | The one-page version |
docs/loading_real_data.md | How to replace the stand-in with the published files — one command, ten minutes |
docs/limits.md | What it costs, where the free tier stops, and what breaks first |
docs/runbook.md | What to do when something is wrong, written for whoever gets the alert |
docs/security.md | The OWASP ASI mapping and the red-team results, generated from the register and the last measured run |
docs/term4_yieldmap.ipynb | Every model, with its plots and the tables the report cites |
| docs/AI217_YIELDMAP_report.docx | The Term 4 report, generated from docs/results/ by scripts/build_report.py |
| docs/AI217_YIELDMAP_deck.pptx | Twelve slides with speaker notes, generated by scripts/build_deck.py |
| docs/REDEPLOY_RUNBOOK.md | Restore and redeploy the whole thing from a downloaded zip |
The report and the deck are generated rather than written, and both currently carry a draft
stamp: scripts/guard_synthetic.py blocks generated figures from reaching a submitted artefact,
and it blocks. Passing --draft is what allows generation, and it puts the label in three places a
reader cannot get past — a banner on the cover, a provenance column beside every model figure, and
a limitations section naming which numbers describe a stand-in. Load real data and regenerate: the
guard passes, and the provenance column disappears because it has nothing left to say.
uv sync # Python deps (3.12)
uv run pytest # Python tests
uv run ruff check . # lint
npm install && npm test # JS tests
External data is downloaded by GitHub Actions (.github/workflows/ingest.yml), not locally — see
CLAUDE.md for why. Data licences and retrieval dates are in docs/datasets.md; model choices and
their measured trade-offs are in docs/models.md; all measured numbers are in docs/results/.
Zero paid inference. The provider chain is local Ollama → Gemini free tier → Groq free tier, with request-count budgets and graceful degradation at every step. The Anthropic backend exists in the code and is switched off.
| 60fps desktop, 61fps on a Pixel 7 — measured in software rasterisation, so a floor |
| 60 / 30 |
| Accessibility | 0 violations across 7 pages, WCAG 2.1 AA, both viewports — was 7 serious | none serious |
| Page weight | 207–210 KB of JavaScript brotli-compressed on most routes, 292 KB on a chart page; three.js on none of them | gated at 340 KB, and at zero three.js |