Easy-to-manage touch screens
무료 오픈소스 터치스크린으로 USB 설정 가이드, 드래그‑드롭 편집기, 로컬 제어를 제공하며 Home Assistant용으로 제작되었습니다
구매 전 ClearCondo는 아파트 서류에 숨은 특별분담금·재정·소송 등을 가격화하고, 구매 후에는 시에서 말하는 내용을 확인합니다
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
라이프스타일TypeScript · JavaScriptGitHub @sakshit2004온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기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.