尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Before you buy, ClearCondo prices what's buried in a condo's documents — special assessments, thin reserves, lawsuits — every claim traced to its page. After you buy, it checks what the City says your building is worth and the tax that comes with it.
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗Stop overpaying for your condo — before you buy, and after. Before
you buy, ClearCondo reads a condo's document stack — reserve fund study, bylaws,
board/AGM minutes, information statements — and prices what's buried in it:
severity-grouped red flags, verbatim evidence quotes with source documents,
dollar exposures, and questions to take to a realtor or lawyer. For Calgary
buildings it adds the public record (assessed value against similar
buildings, major permits) and a preview of what owning costs every year
(property tax and assessment appeal history), free and without sign-in at
/calgary.
Built for Build North 2026 (Georgian). This is a real product, not a demo throwaway: per-report Stripe monetization (test mode), Clerk auth, and a SurrealDB building-memory layer where every analysis accumulates.
Upload (PDFs, 4 MB total — Vercel request-body cap)
→ /api/analyze/start → per-page text extraction (unpdf, server-side)
scanned/image-only PDFs → Claude native-PDF OCR
transcription in page-range chunks (10 pages/chunk,
up to 4 concurrent, per-page JSON, zod-validated)
missing-documents check (Alberta resale checklist)
→ /api/analyze/run → Claude Messages API, strict JSON contract
(flags + structured facts, file_id/page/verbatim
evidence), validated by zod (one repair retry)
→ VERIFY every cited quote against stored page text
(exact page → exact file → fuzzy ≥0.9); unverified
claims downgraded, money figures stripped
→ SurrealDB persist (building/document/report/flag +
facts_snapshot)
→ remember verified facts in SurrealDB Agent Memory
(best-effort, never fails the request)
→ diff vs previous snapshot ("what changed")
→ report page
maxDuration = 300 (the Vercel Hobby/Fluid maximum). Claude calls carry
their own abort timeouts (analysis 240s, OCR chunk 120s — both env-tunable),
and timeouts surface as a clear "timed out, try again" state in the UI,
never a stuck spinner./app/buildings/[id] shows
the accumulated memory: every report, every distinct issue, first-seen
dates. Every number the UI shows comes from these tables.lib/agent-memory.ts, env-gated): after each
persist, the verified facts are remembered as factual text with
infer: "full" — the service extracts entities/attributes/relations
itself, reconciles them against what it already believes about the
building, and end-dates superseded beliefs instead of overwriting them
(tri-temporal history). The building page recalls them with hybrid
retrieval (AgentMemoryPanel). This is the "Best Use of SurrealDB Agent
Memory" story: the tables are the auditable record, Agent Memory is the
belief layer that reasons over time. Partitioning uses labels
(app=clearcondo, owner=…, building=…) so no scope pre-registration
is needed; everything degrades gracefully when SPECTRON_* is unset.lib/calgary/): each report gets a
"Public records (City of Calgary). Context, not a verdict." section built
from City of Calgary Open Data (Socrata, no key needed): current roll
4bsw-nn7w, historical rolls 4ur7-wsgc, building permits c2es-76ed.
The building is matched by condo plan number (roll short_legal prefix
before ;), with a normalized Calgary street address as fallback only when
it maps to exactly one plan; anything ambiguous shows "Couldn't match this
building to City records" rather than a guess. The section shows an
identity check (plan/units/address, docs vs roll), the assessed value trend
vs same-use buildings in the same community, and major permits (roof,
envelope, parkade, balcony, structural) from the last 5 years. A permit is
flagged "not mentioned" only when a keyword search of the extracted pages
finds nothing, and the section lists the terms and documents it searched.
Fetches happen server-side with per-request timeouts
(CALGARY_DATA_TIMEOUT_MS, default 8s). Results are cached per building
for 24h (public_record_cache). The section streams in under Suspense, and
failures show "City data unavailable". Matched permits go into building
memory as public_record_fact rows (a separate source type from document
evidence) and into Agent Memory under a source=public-record label.
Optional: CALGARY_SOCRATA_APP_TOKEN raises the City's rate limit./app/own, signed in): an owner searches their
address with unit and sees the unit's City assessment, change since last
year, estimated tax and how it sits against the building's median unit,
plus the building's public record. Each checked unit is saved to
SurrealDB (owner_property, one row per owner + plan + unit, refreshed
on every check). "Get my savings estimate" stores the owner's details and
the facts open data lacks (size, condition, parking…) in owner_request
via POST /api/owner-requests; the team replies with a number. An
approximate saving shows only when the private OWNER_SAVINGS_CUT is set.
Pricing: 30% of the tax saved plus $10/month, free during beta.ANTHROPIC_API_KEY, /api/analyze/run
returns a clear "add your key" state; without SurrealDB, reports render
ephemerally; without Clerk keys, /app/* shows a setup notice. /demo
always works — it renders a hand-written sample-data/sample-report.json.npm install
cp .env.example .env.local # then fill in keys (see checklists below)
npm run dev # http://localhost:3000
npm run smoke # PDF extraction smoke test (no LLM needed)
npm run build # must pass before deploy
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY.CLERK_SECRET_KEY.ANTHROPIC_API_KEY.ANTHROPIC_MODEL (default claude-sonnet-5-5). Analysis and
OCR run for speed: thinking off and low effort (lib/model-config.ts).
ANTHROPIC_EFFORT (low…max) and ANTHROPIC_THINKING=on trade speed back
for depth.wss://<instance-id>.surreal.cloud/rpc → SURREAL_URL.SURREAL_NS=clearcondo, SURREAL_DB=prod (any values work; schema is
applied automatically on first connect from lib/schema.surql).SURREAL_USER + SURREAL_PASS (root, or a scoped user you create), orSURREAL_TOKEN (a record/token string).SPECTRON_URL (the exact host shown in Studio), SPECTRON_CONTEXT_ID,
and SPECTRON_API_KEY in the deploy environment.app=clearcondo, owner=…, building=…) and land in the key's default
write region. Verify: run an analysis, then open the building page — an
"Agent memory" section with recalled beliefs should appear below the
deterministic building memory.STRIPE_ENABLED=false (default) for beta-free mode — the demo never
blocks on payment.STRIPE_SECRET_KEY
(test key), create a webhook endpoint pointing at
https://<your-app>/api/stripe/webhook listening to
checkout.session.completed → STRIPE_WEBHOOK_SECRET, then set
STRIPE_ENABLED=true. Price via STRIPE_REPORT_PRICE_CENTS (default 4900)..env.example
(at minimum the Clerk + Anthropic + SurrealDB sets above). Set
NEXT_PUBLIC_APP_URL to your production URL.proxy.ts (Next 16's middleware convention) protects /app/*
via Clerk; /, /demo, and /api/stripe/webhook stay public.Platform limits (Vercel Hobby with Fluid compute, checked Oct 2026 — changelog):
| Limit | Value | What we do about it |
|---|---|---|
| Function max duration | 300s (Hobby max) | Both analyze routes set maxDuration = 300; Claude calls abort earlier (240s analysis, 120s OCR chunk) so timeouts are ours, with clear errors |
| Request/response body | 4.5 MB | Total upload capped at 4 MB in /api/analyze/start (clear 413 message); run-route responses are bounded by the 1200-page payload cap |
Measured on the demo stack (node --experimental-strip-types scripts/measure-pipeline.mjs;
17 pages / 23.7k chars; Claude numbers are token-math estimates, no API key in CI):
| Stage | Measured | 100-page text stack (150k chars, extrapolated) |
|---|---|---|
| PDF text extraction | 24 ms/page | ~2.4s |
| Evidence verification | 0.2 ms/quote | <0.1s |
| Claude analysis (~42k in-tokens, report JSON out, thinking off) | — | less than the ~30–90s estimated with thinking on (not yet measured) |
| DB persist | — | <2s (assumed) |
| Total, sync | — | well under the 300s budget |
Cost (Sonnet 5.5 list pricing: $2 / $10 per MTok input/output, checked Oct 2026; thinking tokens bill as output):
| Stack | Input | Output | ≈ Cost |
|---|---|---|---|
| Demo stack (17 pp, 23.7k chars) | ~10k tok | ~8k tok | ≈ $0.10 |
| 100-page text stack (150k chars) | ~42k tok | ~8k tok | ≈ $0.16 |
| Worst case (12 files × 150k chars ≈ 450k tok) | ~450k tok | ~8k tok | ≈ $0.98 |
OCR transcription (scanned PDFs) is chunked: 10 pages/chunk, up to 8
concurrent (OCR_CHUNK_PAGES, OCR_MAX_CONCURRENCY). A 100-page scan is
~10 chunks ≈ 2 waves ≈ 1–2 min worst case — inside the 300s start-route
budget. Beyond that, the route returns a clear timeout error, not a silent
empty report.
Background-job verdict: not needed. The whole synchronous pipeline fits in the 300s Hobby budget with margin for any stack the upload caps allow (≤12 files, ≤100 pages/file, ≤150k chars/doc). The 240s analysis abort is the hard guard: a worst-case 450k-token stack may approach it and get a clear "timed out, try again" instead of ever hitting the 300s platform limit. The upload caps — not a job queue — are the backpressure mechanism. If stacks grow beyond that later, revisit.
On timeout: the UI shows "The analysis timed out — your files are fine" with a Try again button. Persistence is sequential, not atomic: the run route verifies after the Claude call, then writes the report, flags, documents, and facts with individual statements. If an invocation is killed mid-persist, a partial report can remain — re-running the analysis creates a fresh, complete report.
lib/schema.surql (applied automatically on first connection):
| Table | Purpose |
|---|---|
building | One row per condo corp per user (dedupe_key unique on owner + corp/name) |
document | Each uploaded PDF → building, filename, type, page/char counts |
report | Each analysis → building, summary, exposure total, questions, model, facts |
flag | Each red flag → report and building, severity, evidence quotes, money exposure |
credit | Stripe report-credit balance per user |
| Route | Auth | Description |
|---|---|---|
/ | — | Landing page |
/demo | — | Hand-written sample report (labeled as sample) |
/sign-in, /sign-up | — | Clerk |
/app | ✓ | Dashboard: my reports |
/app/new | ✓ | Upload + two-step analysis flow |
/app/reports/[id] | ✓ | Red-flag report |
/app/buildings | ✓ | Buildings with memory |
/app/buildings/[id] | ✓ | Building history: accumulated flags + report timeline |
POST /api/analyze/start | ✓ | PDF → per-page text (+OCR fallback, missing-docs check) |
POST /api/analyze/run | ✓ | Text → Claude → verify → SurrealDB → report + deltas |
GET /api/buildings/match | ✓ | Building candidates for confirm/merge on upload |
GET /api/checkout | ✓ | Stripe Checkout (flag-gated) |
POST /api/stripe/webhook | — | Credit fulfillment |
GET /api/me/credits | ✓ | Credit balance |
DELETE /api/reports/[id] | ✓ | Delete report + flags + documents (+ best-effort Agent Memory forget) |
GET /api/retention/cleanup | cron secret | Daily retention cleanup (Vercel Cron) |
/privacy | — | Plain-language privacy & retention note |
The full policy lives in one place: lib/retention.ts (the RETENTION_POLICY
table). Summary:
| Data | Retention | Why |
|---|---|---|
| Raw PDF bytes | Never stored (memory only during extraction) | Minimizes sensitive data at rest |
| Extracted page text + document rows | Auto-deleted after RETENTION_DAYS (default 30) via daily Vercel Cron → GET /api/retention/cleanup (authorized by CRON_SECRET) | Bulk sensitive text; only needed for the verification audit window |
| Report shell (summary, flags, facts, verification stats) | Kept until the user deletes the report | The deliverable; never auto-deleted |
| Flags + evidence quotes | Kept with the report | Short excerpts the user already saw |
| Building row | Kept until its last report is deleted | Dedupe anchor; removed when empty |
| Agent Memory beliefs (hosted service) | Best-effort semantic forget on report delete; no auto-delete | The service exposes no bulk age-based purge (documented limitation) |
User-facing controls:
/privacy page, linked from the footer, the landing
page's "Your documents, your control" section, and the upload form.Env vars: RETENTION_DAYS (default 30, max 3650, invalid values fall back to
the default), CRON_SECRET (generate with openssl rand -hex 32; Vercel
sends it as Authorization: Bearer <CRON_SECRET> on cron requests — the
route fails closed when unset).
npm run eval -- <stack-dir> [--analysis <analysis.json>] scores a produced
analysis against a hand-labeled eval case — no API key needed, because quotes
are re-verified against freshly extracted page text (never the verified
fields stamped on a stored report).
An eval case is a directory eval/<stack-name>/ with two files:
case.json — { "name", "docs_dir", "docs": ["a.pdf", ...], "analysis" }
(paths relative to the case dir). docs_dir points at the source PDFs;
analysis points at a produced analysis JSON (a run output, the ephemeral
{analysis} payload, or a checked-in sample report). --analysis overrides it.expected.json — { "flags": [...] }, one entry per flag a correct report
MUST contain. Each entry:{
"id": "special-assessment-parkade",
"severity": "critical",
"title_keywords": ["special assessment", "parkade", "8,500"],
"min_amount_cad": 714000,
"evidence_quote": "BE IT RESOLVED that a special assessment of $8,500 per unit ...",
"source_doc": "demo-02-agm-minutes-2026-04-22.pdf"
}
title_keywords (all must appear in the produced flag's title, normalized)
or a single title substring decides the match; each produced flag is matched
at most once, best keyword coverage wins. min_amount_cad is checked against
the flag's headline money figure (amount_cad ?? per_unit_cad).
evidence_quote is the labeler's canonical quote — it must itself verify
against source_doc, otherwise the case is flagged as stale.
Three metrics, printed with a per-flag table:
| Metric | Definition |
|---|---|
| recall | expected flags matched / expected flags |
| fabricated | evidence quotes (flags + structured facts) that do NOT verify — hard fail if > 0 |
| % verified | verified quotes / total quotes in the analysis |
Exit code is 0 only when recall >= EVAL_MIN_RECALL (default 0.8) AND
fabricated quotes = 0. The committed case eval/north-hill-terrace-2026
is a synthetic seeded case: 7 hand-labeled flags scored against a
hand-written sample report and fictional demo PDFs (currently 100% recall,
0 fabricated, 100% verified). It proves the harness works — not that the
model finds real red flags.
To measure the real pipeline, label a genuine resale package by hand:
/private-samples/<building>/ — gitignored). Never commit real condo
documents to this public repo.eval/real-stack-template.json to eval/<stack-name>/expected.json
and fill it in. Create eval/<stack-name>/case.json:
{ "name", "docs_dir", "docs": [...], "analysis" } — docs_dir and
analysis may be absolute paths outside the repo.{ analysis } payload from the
/api/analyze/run response. Label first, look later — write the
expected flags from your own reading of the PDFs before opening the
produced analysis, so the eval measures the model instead of echoing it.npm run eval -- eval/<stack-name> (no API key needed — quotes are
re-verified against freshly extracted page text).eval/<stack-name>/ to
.gitignore) or keep it entirely outside the repo and pass its path.Per the Build North rules, everything below was created during Build Week (Oct 16–25, 2026). Nothing existed before Oct 16 except the idea:
demo-docs/generate_demo_docs.py (a 2026 set with
six baked-in red flags, and a 2024 set with different numbers for the
memory-diff demo). Full origin/licence notes in demo-docs/SOURCES.md.m3-sample-reserve-fund-study.pdf — M3 Consulting's own
publicly posted sample reserve fund study, used only to sanity-check
extraction against a genuine third-party document.sample-data/sample-report-*.json
files — fictional "North Hill Terrace" building, generated Oct 18, 2026.
Every synthetic page is watermarked "SAMPLE DOCUMENT — DEMO DATA ONLY".
The /demo page renders two hand-verified sample reports (2024 + 2026)
whose every quote was machine-verified against the synthetic PDFs
(scripts/enrich-sample.mjs fails loudly on any unverified quote).ClearCondo summarizes condominium documents. It is not legal, financial, or real-estate advice. Always have a qualified lawyer review the actual documents before buying.