Rent Roll to T-12 Tie-Out
Reconciles a normalized rent roll against a normalized T-12 on a stated, consistent basis (annualized contractual vs recognized accrual; collected cash out of scope) and never forces a tie.
Download the CRE Skills Plugin
Latest release, portable bundle (signed). Review the SKILL.md files before installing into your agent.
Takes a normalized rent roll and a normalized T-12 and reconciles them on a stated basis: annualized contractual income versus recognized accrual. Every untied dimension is classified and routed to a human reviewer; nothing is plugged.
Before IC, someone has to answer whether the rent roll actually supports the operating statement revenue. That question is easy to state and tedious to answer: the two documents measure different things, use different account structures, and have different annualization periods. Teams either skip it and carry forward the seller's revenue claim unchallenged, or they burn analyst hours doing it ad hoc in a spreadsheet where a plug is always one cell-edit away.
An analyst manually ties base rent in a spreadsheet by copying totals from the rent roll and matching them to the T-12 revenue lines. Recoveries get reconciled separately from other income even though the operating statement combines them. Variances get explained in a comment or overridden to force agreement, and the basis (contractual versus cash versus accrual) often goes unstated. One analyst's bridge rarely matches another's.
Reach for it
Run after both the rent roll and the T-12 have been normalized (via rent-roll-to-database and t12-to-database) and before feeding revenue inputs to the NOI bridge or an IC memo. The natural trigger is: the two source documents exist and the next step is to prove the revenue.
Not the right tool
Not a substitute for normalizing source documents: run rent-roll-to-database and t12-to-database first. Not the right tool for rollover analysis, WALT, or mark-to-market rent work -- use rent-roll-analyzer for those. OpEx-to-NOI work is owned downstream.
Inputs
- OM
- Rent Roll
- T-12
A 120-unit multifamily deal is under LOI. The analyst runs the tie-out after normalizing both documents. The EGI bridge shows the T-12 recognized $47k more in recovery income than the contractual run-rate: the engine classifies it as a CAM timing variance, not a gap, and routes it to review with the confidence band. Base rent ties within 0.6 percent. One occupancy dimension comes back one-sided because the T-12 carries no occupancy metric, so it is flagged as unquantifiable rather than forced to a number. The IC memo cites the reconciled EGI figure and discloses the open recovery item.
Agent personas that pair well with this skill
The skill reconciles what the two normalized payloads say. If the upstream normalizations misclassify a charge or map an account incorrectly, those errors carry through. Occupancy cannot be reconciled when the T-12 carries no occupancy metric; that dimension is marked one-sided, not estimated. The tool does not replace analyst judgment on how to resolve a MISSING classification.
Rent Roll to T-12 Tie-Out
You are a CRE underwriter's reconciliation engine. You take a normalized rent roll and a normalized T-12 and prove — on a stated, consistent basis — whether the rent roll's contractual income explains the operating statement's recognized revenue. You do NOT make the two sides agree. A variance is a finding, not a defect: you classify it, quantify the part you cannot explain, and route it to a human. The deliverable is a defensible bridge an IC can challenge, not a green checkmark.
This skill is backed by a deterministic, stdlib-only calculator, reconcile_rent_roll_t12.py (it is not a black box). It takes one input dict — {rent_roll, t12, tolerance_overrides?, run_id, as_of} — where rent_roll and t12 are the canonical payloads emitted by normalize_tokens.py. The same input produces byte-identical output: no wall clock, no network, no plug.
When to Activate
Explicit triggers:
- "tie out the rent roll to the T-12" / "reconcile the rent roll to actuals"
- "does the rent roll prove the revenue in the operating statement?"
- "build the NOI bridge from the rent roll" / "revenue leakage check"
- "reconcile contractual rent to recognized revenue"
Implicit triggers:
- A normalized rent roll (from
rent-roll-to-database) and a normalized T-12 (fromt12-to-database/operating-statement-to-database) both exist, and the next step is to prove the revenue inputs that drive NOI before underwriting or IC.
Do NOT activate for:
- Producing the normalized rent roll itself — use
rent-roll-to-database. - Normalizing the operating statement itself — use
t12-to-database(ort12-normalizerfor the underwriting restatement). - Rent-roll analysis (rollover, WALT, mark-to-market) — use
rent-roll-analyzer. - The full proforma / NOI build (the OpEx → NOI leg) — that is owned downstream, not by the tie-out.
Input Schema
One input dict passed to reconcile_rent_roll_t12.py via --json (or stdin). Selectors live in the payload, never as argv flags.
| Field | Type | Required | Notes |
|---|---|---|---|
run_id | string | yes | Stamps the run; the only field that legitimately varies output between runs. |
as_of | string | yes | ISO date; injected (no wall clock is read). |
rent_roll | object | yes | A normalize_tokens.py output with doc_type: rent_roll (charge-schedule records, aggregates). |
t12 | object | yes | A normalize_tokens.py output for the operating statement (records with canonical_account + amount, aggregates.periods_present). |
tolerance_overrides | object | no | Per-dimension fractional tolerance overrides ({base_rent, recoveries, other_income, occupancy, egi_bridge}). |
tenant_id | string | no | Tenancy/workspace label (path-validated; NOT an auth token). |
The tie-out consumes only the two normalized payloads. It does not re-extract, re-map, or re-grade — those are upstream. Tolerances are data, not code; see references/tie-out-methodology.md for the dimension-specific defaults and the override contract.
Process
Step 1: Fix the basis (and label it on every row)
The rent roll is annualized contractual in-place income; the T-12 is recognized accrual (annualized from the months actually present, scaled by 12 / periods_present); collected cash is out of scope (there is no AR feed). These bases are stamped on every reconciled row. Comparing contractual to accrual produces legitimate variances — free rent, vacancy, CAM true-ups — so the engine classifies them; it does not call them errors.
Step 2: Reconcile the EGI / NOI-revenue bridge first
The EGI bridge — rent-roll annualized contractual gross vs T-12 recognized total revenue — is the most important dimension and is computed first, because whether the total ties drives how every per-category gap is classified. This proves the revenue that feeds NOI. The OpEx → NOI leg is owned by t12-to-database, not the tie-out.
Step 3: Reconcile the line dimensions
- Base rent (two-sided, tight tolerance ~1%): annualized contractual base rent vs T-12 base-rent actual.
- Other rental (tolerance ~15% for CAM float): recoveries + other income, reconciled jointly because the canonical chart combines them in
revenue_other_rental. The rent-roll-side breakdown (recoveries vs parking/storage/percentage rent) is reported on the row so the joint figure is auditable. - Occupancy (physical, count basis, tight): marked one-sided / not reconcilable when the T-12 carries no occupancy metric — never fabricated to force a comparison.
Step 4: Classify each untied dimension (deterministic signature)
Keyed on whether the EGI total reconciles:
- MAPPING — total ties and per-category variances offset → a charge was reclassified into the wrong account.
- TIMING — total ties but a single category drifts (estimate-vs-true-up / period attribution); OR total does not tie and T-12 recovery income exceeds the contractual run-rate → CAM estimate-vs-annual-true-up.
- MISSING — total does not tie otherwise → income present in one source and absent in the other (a collections / vacancy finding).
- TIED — within the dimension's tolerance.
Step 5: Surface residuals and route to human review
Every untied dimension carries residual_unexplained == |variance| (zero only when tied; never absorbed into a plug). Each untied dimension is appended to a human-review queue with its difference type, variance, residual, and a confidence band. The output also reports egi_ties, the tied/untied counts, the residual total, and the basis block (including t12_annualization_months).
Output Format
JSON: {dimensions, summary, human_review_items, basis, run_id, as_of}.
dimensions[]— one row per dimension (base_rent,other_rental,occupancy,egi_bridge), each withrent_roll_value,t12_value, the labeledbasis,variance,variance_pct,tolerance_pct,tie_status(tied | untied),difference_type(within_tolerance | mapping | timing | missing | unclassified),candidate_explanation,confidence, andresidual_unexplained.other_rentaladditionally carriesrent_roll_breakdown; a one-sided dimension carriesone_sided: true.summary—dimension_count,tied,untied,residual_unexplained_total,egi_ties.human_review_items[]— the untied dimensions, each withreason,variance,residual_unexplained,confidence, and anaction.basis—rent_roll_basis,t12_basis,collected_basis,t12_annualization_months.
Red Flags
- A reconciliation that "ties to the penny." A contractual-vs-accrual comparison should leave residuals; a zero residual on a property with free rent or CAM estimates means a number was forced.
tie_statusis onlytied | untiedand nothing adjusts a value — if you see a plug, it did not come from this engine. - Treating a legitimate variance as a data error. Free rent, vacancy, and CAM true-ups are classified (timing / missing), not flagged as extraction failures.
- Comparing on mixed bases — annualizing the T-12 wrong (e.g. multiplying a partial-year statement instead of scaling by
12 / periods_present), or sliding collected cash in where there is no AR feed. The basis is stated and consistent or the bridge is meaningless. - Reconciling recoveries and other income separately and "explaining" an offset. The canonical chart combines them; reconcile
other_rentaljointly and report the breakdown — do not invent a precision the chart does not support. - A one-sided dimension (no T-12 occupancy metric) reported as a tie or a quantified variance. It is
one_sided, not reconcilable here, residual unquantifiable. - An untied dimension delivered without a
residual_unexplainedand a review item. Every gap is owned by a human; nothing is silently closed.
Chain Notes
Upstream (produce the two payloads this skill reconciles): rent-roll-to-database (the normalized rent roll), t12-to-database / operating-statement-to-database (the normalized T-12). Downstream (consume this reconciliation): document-to-database (orchestration + the human-review queue), acquisition-underwriting-engine (the proven revenue inputs to the NOI bridge — the tie-out reconciles the revenue inputs; the OpEx → NOI leg is owned by t12-to-database).