Component · for humans & their agents
Rent-Roll Diff
verified · first-partyactively maintained$0 during beta (was $49)
A rent roll tells you who is in the building today. It cannot tell you who renewed — and asking a model to eyeball two of them produces a confident answer with no way to check it.
by parsley · Code Recycle moderator
Every claim on this page is refundable if it is untrue — refund policy.
Verified: 28 tests · 10/10 mutations caught
Renewal is a claim about two points in time. A single roster cannot answer it, so the only honest method is to compare dated snapshots and classify what moved.
Why this is not a prompt
Renewal is a claim about two points in time. A single roster cannot answer it, so the only honest method is to compare dated snapshots and classify what moved.
Hand that to a model and it reads two rosters, sees the same tenant twice, and says "renewed" — fluently, without noticing that the expiration moved EARLIER, that the suite label changed building format, or that "EKN 200 dba Trimana" and "Trimana" are one tenant while "Pacific Dental Group" and "Pacific Imaging Group" are two. The classification belongs in code. The model orchestrates, presents, and cites.
What it decides
| event | how it is observed | |---|---| | `renewal` | same tenant, same premises, later expiration than the prior snapshot | | `new_lease` | present in the later roll, absent from the earlier | | `departure` | present in the earlier roll, absent from the later | | `relocation` | same tenant, different premises | | `expansion` / `contraction` | premises added or dropped | | `holdover_suspect` | still on the roll past expiration with no new term | | `data_correction_suspect` | expiration moved earlier, or sqft shrank on the same term | | `unchanged` | matched, nothing moved |
The four things that make this harder than it looks
Renewal ≠ expiration ≠ unexercised option. An expiration that moves earlier is not a renewal — it is an amendment, an early termination, or a data-entry error, and it is surfaced as a suspect rather than silently reclassified.
"Renewed in 2026" is ambiguous. Signed-in-year and term-commencing-in-year are different questions, and rent rolls carry no signed date. Events expose both the observation window and the new term's commencement, and `renewalsDuring()` makes the caller state which one they mean.
Tenant names vary across documents. Matching is tiered — exact, token-set, alias, partial — each with its own confidence, so a weak match arrives labelled weak. Cross-vocabulary equivalences (Wabtec = Westinghouse Air Brake Technologies) come only from an alias table you supply; they are never guessed.
Month-to-month conversions never appear on a leases-signed report. A tenant sitting past expiration with no new term is the one place they surface, so it has its own event type.
What it does not do
It does not read PDFs — you supply rows. It does not decide that a partial name match is really the same tenant — it tells you the match was partial and flags the event. It carries no rent or escalation math: it classifies lease events, not economics.
Verified
28 tests, and 10 deliberate defects applied to the real source — all 10 caught. Two of them are the ones that matter commercially: an expiration moving earlier classified as a renewal, and a tenant matched on nothing but a shared industry word. The mutation set ships in the source so you can re-run it against your own edits.
Delivery
Source delivered as a private repository invite within 24 hours of purchase. Single-product commercial license: use and modify in any number of products; no redistribution or resale of the source.
Interface
What you call, and what comes back. Types and signatures only — the implementation ships with the source.
export function tenantTokens(name: string): Set<string>;
export function tenantNamesMatch(a: string, b: string, aliases?: string[][]): NameMatchTier;
export function parseSuite(raw: string): SuiteKey;
export function buildingsCompatible(a: string | null, b: string | null): boolean;
export function suitesMatch(a: string, b: string):;
export function diffRentRolls(prev: RentRollSnapshot, next: RentRollSnapshot, opts: DiffOptions = {}): LeaseChangeEvent[];
export function diffRentRollSeries(snapshots: RentRollSnapshot[], opts: DiffOptions = {}): LeaseChangeEvent[];
export function renewalsDuring( events: LeaseChangeEvent[], year: number, definition: RenewalDefinition, ):; export type SuiteKey = { building: string | null;01Capabilities
Does
- + Property portfolio management
- + Rent roll & CAM reporting
- + Structured document extraction
- + Entity resolution
Doesn’t
- No exclusions declared
02Requirements & stack
Depends on
No declared dependencies
Credentials needed
None declared
Stack
03Community
No endorsements yetNo verified confirmations yet — be the first.
Confirmations come from verified purchasers, installers, vetted reviewers, or an installation outcome your org reported through the agent tools. They grade quality — security is verified separately, and community votes can never override the security gate.
Sign in to confirm — weight comes from verified usage, not vote count.
Issues 1
Open an issue0 open · 0 answered · 0 fixed · 1 said it worked
- closedWorked for me — 28/28 vitest on Node 26.0.0, macOS 26.4Worked for me
04Trust Passport
Full passport →0/0 automated components pass. An automated score is never a security guarantee.
- publisher identity Publisher status verified; 1 verification(s) on file
- malicious pattern scan No known malicious-behavior patterns across 8 source file(s) plus listing text
- capability contract All 0 observed capability reference(s) match the declared manifest
- agent safety scan No injection patterns in agent-readable content
- provenance No release signature or provenance attestation
- behavioral sandbox Not performed in this environment — requires the production isolated runner (docs/sandbox-requirements.md). No untrusted code is ever executed on the application host.
Every listing must pass this review before it can be sold, and it is re-run on every release. Verification describes what we checked — it is not a guarantee that the software is safe.
05Versions
Full history →| Version | Channel | Released | Notes |
|---|---|---|---|
| 1.0.0 | stable | Aug 10, 2026 | First public release. |