Lens.mp4
LeaseLens is a NJ residential lease red-flag reviewer.
Drop in a lease PDF, get clause-by-clause severity grading grounded in NJ tenant-law sources, then ask the assistant to explain clauses in plain English or draft a polite negotiation email.
LeaseLens is built to demonstrate how an LLM agent can handle high-stakes domain judgment under real engineering constraints:
- Hybrid RAG against a curated NJ tenant-law corpus
- Citation-grounded clause grading
- Parser-first PDF workflow
- Evidence highlighting inside the PDF viewer
- Tool-use loop with auditability
- Role-aware tool access
- Deterministic evals in CI
Not legal advice. LeaseLens reviews NJ residential leases and grades clauses against NJ tenant-law sources. It is not a lawyer, and its output is not legal advice. Before acting on any clause grading or draft email, consult a tenant attorney or local NJ legal-aid clinic.
LeaseLens currently runs end-to-end locally.
Current public product direction:
v1.0
Production hardening roadmap:
- Public Vercel deployment
- Hosted database, such as libSQL / Turso or Postgres
- Real authentication
- Per-user data isolation
- Cost and rate caps
- Loom walkthrough
git clone https://github.com/jar285/LeaseLens.git
cd LeaseLens
npm cicp .env.example .env.localSet the required environment variables:
ANTHROPIC_API_KEY=sk-ant-...
LEASELENS_SESSION_SECRET=<32+ chars>
LEASELENS_DB_PATH=./data/leaselens.db
LEASELENS_DEMO_MODE=false
LEASELENS_PUBLIC_ANON_MODE=false
LEASELENS_ANTHROPIC_MODEL=claude-haiku-4-5
LEASELENS_DAILY_SPEND_CEILING_USD=2
LEASELENS_LEASE_MAX_BYTES=1048576
LEASELENS_LEASE_MAX_PAGES=30
LEASELENS_AUTO_SCAN_ENABLED=true
LEASELENS_LOG_LEVEL=infoDeployment profiles — LEASELENS_DEMO_MODE and LEASELENS_PUBLIC_ANON_MODE
are independent concerns:
| Profile | Flags | Behavior |
|---|---|---|
| Local dev | both false |
No guardrails (not exposed to untrusted traffic). |
| Portfolio demo | DEMO_MODE=true |
Demo UI (role switcher, cockpit) + seeded demo identities + budget guardrails (rate limit + spend ceiling). |
| Public anonymous | PUBLIC_ANON_MODE=true |
Per-visitor isolation + quota + retention + guardrails. Fails closed at boot unless ANTHROPIC_API_KEY and a positive LEASELENS_DAILY_SPEND_CEILING_USD are set. |
DEMO_MODE controls demo UI affordances only; the cost/rate guardrails
enforce whenever the app is exposed (demo or public-anon).
npm run devOpen:
http://localhost:3000
The first npm run dev automatically seeds the database through predev.
The seed process:
- Ingests the 28-document NJ tenant-law corpus
- Generates embeddings locally through WASM
- Copies a sample NJ residential lease into the local database
- Lets reviewers try the workflow without uploading a file
Manual seed command:
npm run db:seedThe seed script is idempotent and skips work when chunks are already populated.
LeaseLens follows a parser-first workflow:
Upload lease → Parse clauses → Grade severity → Show red flags → Highlight evidence → Ask assistant
The core product experience has two modes:
The user sees a landing screen with:
- Hero dropzone
- Five-step flow
- Trust metrics
- Clear upload action
The user sees a two-column workspace:
- Left: PDF viewer
- Right: red flags, clauses, citations, and actions
- Bottom-right: floating assistant drawer
The assistant supports follow-up questions, plain-English explanations, and negotiation email drafts without taking over the main parser-first experience.
Most chat demos avoid serious domains because grounding is difficult.
LeaseLens leans into a serious domain:
NJ residential tenant law
The system is designed so the model cannot casually invent legal claims.
Key safeguards:
- The model must cite retrieved NJ tenant-law corpus chunks.
grade_clause_severityvalidates both the citedchunk_idand statute text.- Failed citation grounding throws instead of silently returning.
- Mutating actions are written to an audit trail.
- Evaluation runs measure retrieval and grading quality.
- Lease PDFs are parsed as user input, not embedded into the legal corpus.
The goal is not only to integrate an LLM.
The goal is to show product judgment, grounding discipline, UX clarity, and engineering reliability in one applied AI product.
LeaseLens uses:
- Anthropic streaming chat
- A 15-iteration tool-use loop
- Hybrid retrieval with vector search, BM25, and reciprocal-rank fusion
- A curated 28-document NJ tenant-law corpus
- Lease-specific tools exposed through a role-filtered registry
grade_clause_severity validates that:
- The cited
chunk_idexists in the retrieved set. - The cited statute string appears inside the cited chunk.
If validation fails, the tool throws.
This forces the assistant to retry or admit that it cannot ground the claim.
LeaseLens includes two evaluation tiers:
| Tier | Measures | Command |
|---|---|---|
| Tier 1 | Retrieval quality: Precision@K, Recall@K, MRR, Groundedness | npm run eval:golden |
| Tier 2 | End-to-end lease clause severity grading | npm run eval:leases |
Tier 1 is hermetic and makes no LLM calls.
Tier 2 calls Anthropic and should be gated by spend limits before running on every PR.
LeaseLens includes:
- Role-based tool filtering
- Lease ownership checks
- SQLite transactions for mutating actions
- Audit log entries for every mutation
- Operator-only rollback for auditable mutations
- CI checks for lint, typecheck, tests, build, and e2e
Every red-flagged clause can be highlighted directly inside the PDF viewer.
The user can move from:
Red flag card → exact PDF text → explanation → recommended action
This turns a severity grade into visible evidence.
Next.js App Router
│
├── Parser-first workspace
│ ├── Mode A: Lease upload landing
│ └── Mode B: PDF viewer + red-flag results
│
├── Floating AssistantFab
│ ├── Plain-English explanations
│ ├── Clause-specific follow-ups
│ └── Negotiation email drafting
│
├── API routes
│ ├── /api/leases
│ ├── /api/chat
│ ├── /api/audit
│ └── /api/workspaces
│
├── ToolRegistry
│ ├── search_corpus
│ ├── extract_clauses
│ ├── grade_clause_severity
│ ├── get_lease_findings
│ ├── draft_negotiation_email
│ └── render_workflow_diagram
│
├── SQLite
│ ├── users
│ ├── sessions
│ ├── documents / chunks
│ ├── leases / clauses
│ ├── negotiation_emails
│ └── audit_log
│
├── RAG pipeline
│ ├── Ingest
│ ├── Chunk
│ ├── Embed
│ └── Retrieve
│
└── Lease pipeline
├── parsePdf
├── segmentClauses
├── classifyClause
└── store clauses
LeaseLens keeps a strict separation between:
| Type | Stored In | Purpose |
|---|---|---|
| NJ tenant-law corpus | documents / chunks |
Legal grounding |
| Uploaded lease PDFs | leases / clauses |
User input and review target |
Lease PDFs are never embedded into the RAG index.
This keeps retrieval grounding pointed at NJ tenant-law sources, not the user's uploaded document.
grade_clause_severity works as follows:
- Retrieve relevant NJ tenant-law chunks.
- Ask the model to grade the lease clause.
- Require the model to return a
chunk_idandstatute_citation. - Validate both before returning.
The validator throws when:
- The cited
chunk_idis not in the retrieved set. - The cited statute text does not appear in the cited chunk.
draft_negotiation_email is the only mutating tool.
Its flow is intentionally split:
- The LLM prepares the email draft.
- SQLite opens a short transaction.
- The transaction inserts the negotiation email.
- The transaction inserts the audit-log row.
- If either insert fails, both roll back.
Tenant users see a copy-to-clipboard card.
Reviewer/Admin users can access operator rollback through the audit flow.
LeaseLens highlights graded clauses directly on the rendered PDF.
Implementation notes:
- Uses
react-pdftext-layer rendering. - Uses a client-side matcher in
highlight-match.ts. - Does not store coordinates.
- Does not require schema changes.
- Highlights realign on zoom and scroll.
- Passive marks stay soft.
- Active clauses get an evidence frame, halo, glow, and floating concern label.
- Gutter markers help users scan long leases.
- Severity is not communicated by color alone.
- Motion respects
prefers-reduced-motion.
Sprint 44 introduced a reliability layer:
- Structured
pinologger - Request correlation IDs
- Accessible error boundaries
- PII-redaction allowlist
- CI workflow for lint, typecheck, tests, and build
- Separate Playwright e2e job
Raw lease text, clause text, and draft-email bodies should not reach logs or persisted tool-call error messages.
| Layer | Technology |
|---|---|
| Framework | Next.js 16 App Router, React 19 |
| Language | TypeScript strict mode |
| Styling | Tailwind CSS 4 |
| Database | SQLite via better-sqlite3, WAL mode |
| LLM | Anthropic Claude, claude-haiku-4-5 default |
| Embeddings | @huggingface/transformers, WASM, local |
pdfjs-dist and react-pdf |
|
| Diagrams | mermaid@^11 |
| Animation | motion@^12 |
| MCP | @modelcontextprotocol/sdk |
| Testing | Vitest 4 and Playwright |
| Linting | Biome |
| Validation | Zod 3 |
The seeded sample lease loads automatically on the first npm run dev.
- Open the app.
- Use the seeded sample lease or upload a text-layer NJ lease PDF.
- Run the standard scan.
- Review the red-flag cards.
- Click a citation or View on page action.
- Verify the PDF evidence highlight.
- Open the AssistantFab.
- Ask for a plain-English explanation.
- Draft a negotiation email.
Run the standard scan on this lease.
Which clause is the most concerning, and why?
Show me only the high-severity red flags.
Read the security-deposit clause and tell me if it is enforceable under NJ law.
Is the late fee in this lease legal? Quote the section that talks about it.
What does the lease say about early termination, and what is NJ law's position?
Compare the attorney's-fees clause to NJ statute. Is it one-way or reciprocal?
How much can a NJ landlord legally charge for a security deposit, and is interest required?
What notice does a NJ landlord have to give before entering the apartment?
Cite the NJ statute on retaliation against a tenant who reports code violations.
Draft a polite email to my landlord about the security deposit clause.
Draft a firmer email about the late-fee structure.
Use a formal tone and request a redline of the early-termination clause.
The seeded sample lease is located at:
src/corpus/sample-lease/sample-nj-residential-lease.pdf
Supported upload type:
Text-layer NJ residential lease PDF
≤ 1 MB
≤ 30 pages
Avoid:
- Scanned-image PDFs
- Commercial leases
- Leases from other states
- Real personal leases with sensitive information
Scanned PDFs without a text layer return:
422 pdf_no_text_layer
OCR is currently out of scope.
In the running public app today, everyone is a Tenant. The role switcher is only available in demo mode.
| Role | DB Literal | Tools | Lease Access |
|---|---|---|---|
| Tenant | Creator |
search_corpus, extract_clauses, grade_clause_severity, get_lease_findings, draft_negotiation_email, render_workflow_diagram |
Own uploaded leases only |
| Reviewer | Editor |
Tenant tools + get_document_summary |
All leases in workspace |
| Admin | Admin |
Reviewer tools + list_documents |
All leases + audit log |
Role access is enforced twice:
- The registry filters the visible tool manifest.
- Tool execution re-checks permissions and lease ownership.
The main product is the PDF parser and red-flag report.
The assistant is supportive, not primary.
Every severity grade must connect to a retrieved NJ tenant-law source.
Red-flagged clauses are highlighted directly on the PDF, with active evidence framing and gutter markers.
The assistant drawer preserves draft and thread state across close/open cycles.
Red-flag cards include a plain-English action for tenant-friendly explanations.
The assistant can draft a negotiation email using clause grading and statute context.
Drafted negotiation emails are written to SQLite and paired with an audit row.
Reviewer/Admin users can inspect audit activity, spend, scheduled emails, and eval health.
The tool registry is exposed over Model Context Protocol through stdio.
render_workflow_diagram can render supported Mermaid diagram types safely on the client.
Structured logs, correlation IDs, error boundaries, and PR checks support production readiness.
# Unit + integration + contract tests
npm run test
# E2E smoke specs
npm run test:e2e
# Type checking
npm run typecheck
# Linting
npm run lint
# Tier 1 retrieval eval
npm run eval:golden
# Tier 2 lease-grading eval
npm run eval:leases
# Production build
npm run buildPlaywright runs with:
LEASELENS_E2E_MOCK=1
This swaps Anthropic for a deterministic mock during e2e tests.
Start the LeaseLens MCP server:
npm run mcp:serverExample MCP config:
{
"mcpServers": {
"leaselens": {
"command": "npx",
"args": ["tsx", "mcp/leaselens-server.ts"],
"cwd": "/path/to/LeaseLens"
}
}
}MCP-originated mutations produce audit rows attributed to:
mcp-server
The repo also includes a project-level .mcp.json for Microsoft's Playwright MCP server.
With npm run dev running, an MCP-aware client can:
- Navigate the local app
- Take accessibility snapshots
- Click and type
- Capture screenshots
- Verify UI changes interactively
npm run test:e2e remains the source of truth for regression coverage.
LeaseLens/
├── mcp/ # MCP server
├── scripts/ # evals, seeding, PDF worker copy
├── tests/e2e/ # Playwright specs
├── src/
│ ├── app/ # Next.js routes and pages
│ ├── components/ # UI components
│ │ ├── chat/ # AssistantFab and chat UI
│ │ ├── cockpit/ # Operator dashboard
│ │ ├── layout/ # Shell, footer, motion provider
│ │ └── lease/ # Parser, PDF viewer, red flags, highlights
│ ├── corpus/ # NJ tenant-law corpus and sample lease
│ ├── db/ # Seed and database setup
│ └── lib/ # Auth, RAG, tools, lease logic, logging
├── design-system/ # Design-system documentation
└── docs/
├── _architecture/ # Philosophy and architecture notes
├── _meta/ # Charter, guidelines, snapshots
└── _specs/ # Per-sprint specs and implementation QA
LeaseLens is built sprint-by-sprint using the project workflow:
Spec → QA → Sprint Plan → Implementation → QA
Phase summary:
| Phase | Sprints | Focus |
|---|---|---|
| Platform foundation | 0–12 | Tool registry, RAG, audit log, eval harness, MCP |
| LeaseLens pivot | 13–14 | NJ lease corpus, lease tools, Tier 2 eval |
| Design system + tenant UX | 15–25 | Tailwind tokens, typography, editorial brand, PDF controls |
| Parser-first workspace | 26–33 | Mode A/B router, AssistantFab, persistence, bug triage |
| Grounding + clarity | 34–35 | Citation recovery and plain-English explanations |
| Assistant polish + platform | 36–45 | Concierge surfaces, content pages, motion, observability, findings reuse |
| Evidence highlighting + brand | 46–51 | PDF evidence layer, gutter markers, v1.0 polish, Mode B depth + premium pass |
| Assistant readability | 52 | Slim drawer masthead, chat-thread overflow menu, mobile bottom sheet (half→full snap), capped reading measure |
| Technical debt | 53 | Self-hosted fonts (next/font/local) for deterministic offline builds |
| Technical debt | 54 | React-PDF render-phase test warning removed (mock timing aligned to real async callbacks) |
| Technical debt | 56 | Docs truth-up: recreated architecture doc + Current Invariants; corrected stale dead-shell references |
Full sprint history:
MIT