{
  "openapi": "3.1.0",
  "info": {
    "title": "RentRollIQ Validation API",
    "version": "0.1.0",
    "description": "Public validation surface for CRE document AI integration partners. POST extracted artifacts or a source PDF and receive a validation report driven by RentRollIQ's institutional-grade rules registry. Narrative spec at docs/monetization/validation-api-spec.md.",
    "contact": {
      "name": "RentRollIQ integrations",
      "url": "https://rentrolliq.com"
    }
  },
  "paths": {
    "/v1/checks": {
      "get": {
        "tags": [
          "validation"
        ],
        "summary": "List validation checks in the current registry",
        "description": "Registry catalog. Returns every check the validator knows about\nso a partner's UI can pre-render the catalog before any inputs\nhave been submitted.",
        "operationId": "list_checks_v1_checks_get",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "X-Api-Key"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChecksCatalogResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid X-API-Key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/validate": {
      "post": {
        "tags": [
          "validation"
        ],
        "summary": "Validate an OM / rent roll, by PDF or by pre-extracted JSON",
        "description": "Run the validation registry against caller-supplied input.\n\nSee docs/monetization/validation-api-spec.md for the public schema;\nthis handler dispatches to services/validation_api.py and renders\nthe response.",
        "operationId": "validate_v1_validate_post",
        "parameters": [
          {
            "name": "include_internal",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "description": "Include internal-audience checks (extraction / parser QA) in the response. Default false \u2014 client-audience only.",
              "default": false,
              "title": "Include Internal"
            },
            "description": "Include internal-audience checks (extraction / parser QA) in the response. Default false \u2014 client-audience only."
          },
          {
            "name": "async",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "description": "Reserved for v0.2 \u2014 async / webhook delivery. Setting true on v0.1 returns 501. See validation-api-spec.md \u00a79.",
              "default": false,
              "title": "Async"
            },
            "description": "Reserved for v0.2 \u2014 async / webhook delivery. Setting true on v0.1 returns 501. See validation-api-spec.md \u00a79."
          },
          {
            "name": "X-Registry-Version",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "X-Registry-Version"
            }
          },
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "X-Api-Key"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidateResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request \u2014 malformed JSON, missing field, or schema violation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid X-API-Key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "413": {
            "description": "PDF payload exceeds the size ceiling.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "415": {
            "description": "Content-Type is not application/pdf or application/json.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded; see Retry-After header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "501": {
            "description": "Requested feature not implemented in this registry version.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "500": {
            "description": "Extraction or validator failure; correlate via validation_id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "503": {
            "description": "Mode A extraction not configured on this deploy; Mode B remains available.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "description": "Either a source PDF (Content-Type: application/pdf) or a pre-extracted JSON context (Content-Type: application/json).",
          "content": {
            "application/pdf": {
              "schema": {
                "type": "string",
                "format": "binary",
                "description": "Source PDF for Mode A. Up to 50 MB. Runs the RentRollIQ pipeline (section discovery \u2192 core extractors \u2192 validation registry)."
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ValidateRequest"
              },
              "examples": {
                "deal_summary_only": {
                  "summary": "Minimal Mode B \u2014 deal summary only",
                  "value": {
                    "extraction_context": {
                      "deal_summary": {
                        "property_name": "Example Tower",
                        "property_type": "multifamily",
                        "asking_price": "12500000",
                        "broker_cap_rate": 0.055,
                        "unit_count": 24
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ApiError": {
        "properties": {
          "error": {
            "type": "string",
            "title": "Error",
            "description": "Machine-readable error code, e.g. 'bad_request'."
          },
          "message": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Message"
          },
          "field_path": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Field Path",
            "description": "Dot-path to the offending field on 400/422 responses."
          }
        },
        "type": "object",
        "required": [
          "error"
        ],
        "title": "ApiError",
        "description": "JSON error envelope for non-2xx responses on /v1/*."
      },
      "CheckCatalogEntry": {
        "properties": {
          "check_id": {
            "type": "string",
            "title": "Check Id"
          },
          "check_name": {
            "type": "string",
            "title": "Check Name"
          },
          "category": {
            "type": "string",
            "title": "Category"
          },
          "audience": {
            "type": "string",
            "enum": [
              "client",
              "internal"
            ],
            "title": "Audience"
          },
          "severity_default": {
            "type": "string",
            "enum": [
              "pass",
              "info",
              "warn",
              "fail",
              "skipped"
            ],
            "title": "Severity Default"
          }
        },
        "type": "object",
        "required": [
          "check_id",
          "check_name",
          "category",
          "audience",
          "severity_default"
        ],
        "title": "CheckCatalogEntry"
      },
      "ChecksCatalogResponse": {
        "properties": {
          "registry_version": {
            "type": "string",
            "title": "Registry Version"
          },
          "checks_catalog_size": {
            "type": "integer",
            "title": "Checks Catalog Size"
          },
          "checks": {
            "items": {
              "$ref": "#/components/schemas/CheckCatalogEntry"
            },
            "type": "array",
            "title": "Checks"
          }
        },
        "type": "object",
        "required": [
          "registry_version",
          "checks_catalog_size",
          "checks"
        ],
        "title": "ChecksCatalogResponse"
      },
      "HTTPValidationError": {
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ValidationError"
            },
            "type": "array",
            "title": "Detail"
          }
        },
        "type": "object",
        "title": "HTTPValidationError"
      },
      "ValidateResponse": {
        "properties": {
          "validation_id": {
            "type": "string",
            "title": "Validation Id",
            "description": "Opaque id; useful for support correlation."
          },
          "input_mode": {
            "type": "string",
            "enum": [
              "pdf",
              "json"
            ],
            "title": "Input Mode"
          },
          "summary": {
            "$ref": "#/components/schemas/ValidationSummary"
          },
          "results": {
            "items": {
              "$ref": "#/components/schemas/ValidationCheckResult"
            },
            "type": "array",
            "title": "Results"
          },
          "citations": {
            "items": {
              "$ref": "#/components/schemas/ValidationCitation"
            },
            "type": "array",
            "title": "Citations"
          },
          "telemetry": {
            "$ref": "#/components/schemas/ValidationTelemetry"
          }
        },
        "type": "object",
        "required": [
          "validation_id",
          "input_mode",
          "summary",
          "results",
          "telemetry"
        ],
        "title": "ValidateResponse"
      },
      "ValidationCheckResult": {
        "properties": {
          "check_id": {
            "type": "string",
            "title": "Check Id",
            "description": "Stable identifier; see GET /v1/checks for the full catalog."
          },
          "check_name": {
            "type": "string",
            "title": "Check Name"
          },
          "category": {
            "type": "string",
            "title": "Category"
          },
          "status": {
            "type": "string",
            "enum": [
              "pass",
              "info",
              "warn",
              "fail",
              "skipped"
            ],
            "title": "Status"
          },
          "audience": {
            "type": "string",
            "enum": [
              "client",
              "internal"
            ],
            "title": "Audience"
          },
          "message": {
            "type": "string",
            "title": "Message"
          },
          "details": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Details"
          },
          "affected_fields": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Affected Fields"
          },
          "suggested_fix": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Suggested Fix"
          },
          "data": {
            "type": "object",
            "title": "Data"
          }
        },
        "type": "object",
        "required": [
          "check_id",
          "check_name",
          "category",
          "status",
          "audience",
          "message"
        ],
        "title": "ValidationCheckResult",
        "description": "One row from the validator's response. Mirrors\n`services/validation/base.py::ValidationResult` with `audience`\nsurfaced so a buyer's UI can route internal-audience checks to\nengineering and client-audience checks to the underwriter view."
      },
      "ValidationCitation": {
        "properties": {
          "field": {
            "type": "string",
            "title": "Field"
          },
          "source_page": {
            "type": "integer",
            "title": "Source Page"
          },
          "source_label": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Source Label"
          },
          "bbox": {
            "anyOf": [
              {
                "items": {
                  "type": "number"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Bbox",
            "description": "[x0, y0, x1, y1] in PDF points when available."
          }
        },
        "type": "object",
        "required": [
          "field",
          "source_page"
        ],
        "title": "ValidationCitation",
        "description": "Per-field source-page citation. Populated by Mode A on artifacts\nwhere the upstream extractor exposed page-level provenance; empty\nlist in Mode B today."
      },
      "ValidationError": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "ValidationSummary": {
        "properties": {
          "checks_run": {
            "type": "integer",
            "title": "Checks Run"
          },
          "checks_passed": {
            "type": "integer",
            "title": "Checks Passed"
          },
          "checks_info": {
            "type": "integer",
            "title": "Checks Info",
            "description": "Count of checks that returned 'info' \u2014 neutral observations the validator wants the reader to see but that aren't themselves problems. Kept separate from 'passed' so checks_run = passed + info + warned + failed + skipped is an arithmetic identity, not an approximation.",
            "default": 0
          },
          "checks_warned": {
            "type": "integer",
            "title": "Checks Warned"
          },
          "checks_failed": {
            "type": "integer",
            "title": "Checks Failed"
          },
          "checks_skipped": {
            "type": "integer",
            "title": "Checks Skipped"
          },
          "overall_status": {
            "type": "string",
            "enum": [
              "pass",
              "warn",
              "fail"
            ],
            "title": "Overall Status"
          },
          "headline_concern": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Headline Concern",
            "description": "First fail message, else first warn, else null."
          }
        },
        "type": "object",
        "required": [
          "checks_run",
          "checks_passed",
          "checks_warned",
          "checks_failed",
          "checks_skipped",
          "overall_status"
        ],
        "title": "ValidationSummary"
      },
      "ValidationTelemetry": {
        "properties": {
          "latency_ms": {
            "type": "integer",
            "title": "Latency Ms"
          },
          "registry_version": {
            "type": "string",
            "title": "Registry Version"
          },
          "checks_catalog_size": {
            "type": "integer",
            "title": "Checks Catalog Size"
          }
        },
        "type": "object",
        "required": [
          "latency_ms",
          "registry_version",
          "checks_catalog_size"
        ],
        "title": "ValidationTelemetry"
      },
      "ValidateRequest": {
        "description": "Top-level POST /v1/validate request body for Mode B (JSON in).\nMode A (PDF in) does not use this shape \u2014 the body is the raw PDF.",
        "properties": {
          "extraction_context": {
            "$ref": "#/components/schemas/PublicExtractionContext",
            "description": "Partial snapshot of extracted artifacts. Send whatever you have; missing artifacts skip the checks that need them."
          }
        },
        "title": "ValidateRequest",
        "type": "object"
      },
      "DealSummary": {
        "properties": {
          "property_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "The property's branded or street name (e.g. 'Royal Palms', 'Hartwell Tower').",
            "title": "Property Name"
          },
          "property_name_candidate": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "When the LLM-extracted property name fails the verbatim check and we fall back to an address-based name, the original (untrusted) candidate is preserved here so the analyst can see what was rejected. Null when the LLM's name passed.",
            "title": "Property Name Candidate"
          },
          "property_name_verbatim_status": {
            "anyOf": [
              {
                "enum": [
                  "pass",
                  "warn",
                  "fail"
                ],
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Result of the post-extraction verbatim check on property_name. 'pass' = appears verbatim in source; 'warn' = did not appear verbatim, fell back to address-based name; 'fail' = no name could be determined. Null when no extraction was attempted.",
            "title": "Property Name Verbatim Status"
          },
          "property_address": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Full street address. Single line; include city/state/zip when given.",
            "title": "Property Address"
          },
          "market": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "MSA or submarket (e.g. 'Dallas-Fort Worth', 'Phoenix', 'Brooklyn').",
            "title": "Market"
          },
          "property_type": {
            "anyOf": [
              {
                "enum": [
                  "multifamily",
                  "office",
                  "retail",
                  "industrial",
                  "mixed_use",
                  "hotel",
                  "self_storage",
                  "land",
                  "other"
                ],
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Property Type"
          },
          "year_built": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Year Built"
          },
          "year_renovated": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Year Renovated"
          },
          "unit_count": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Total units (apartments, suites, keys). Null for non-multifamily where not applicable.",
            "title": "Unit Count"
          },
          "rentable_sf": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Total rentable / net leasable square feet, when stated. Null when not given.",
            "title": "Rentable Sf"
          },
          "stated_occupancy": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Occupancy rate as the BROKER states it on the OM cover or property summary pages, as a decimal (0.88 for 88%). Null if not stated. We surface this alongside our rent-roll-derived occupancy so an analyst can spot conflicts (broker says 88%, our roll-derived math says 100%) without having to flip back to the OM.",
            "title": "Stated Occupancy"
          },
          "asking_price": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Asking / list / offering price as stated in the OM. Null when 'price upon request' or unstated.",
            "title": "Asking Price"
          },
          "asking_price_needs_review": {
            "default": false,
            "description": "Set by the post-extraction reconciliation step when the asking price disagrees with other extracted facts (e.g. broker cap rate \u00d7 NOI implies a different price). Surfaced on the Deal Summary sheet so an analyst knows to verify against the OM cover.",
            "title": "Asking Price Needs Review",
            "type": "boolean"
          },
          "price_candidates": {
            "description": "Every distinct currency value seen on the cover / exec-summary pages near the asking-price block. Used to (a) filter out struck-through stale prices, and (b) propose a corrected price during cap-rate reconciliation when the originally selected asking_price doesn't tie to broker_cap_rate \u00d7 NOI.",
            "items": {
              "$ref": "#/components/schemas/PriceCandidate"
            },
            "title": "Price Candidates",
            "type": "array"
          },
          "property_tax_footnote_price": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Some OMs footnote the property-tax line with the price the tax assumes ('Property tax based on $3,350,000 asking price'). When present, this is the most reliable cross-check on asking price.",
            "title": "Property Tax Footnote Price"
          },
          "broker_noi": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "The broker's stated NOI used to back into the cap rate. Annual.",
            "title": "Broker Noi"
          },
          "broker_cap_rate": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Broker's stated cap rate as a decimal (0.0468 for 4.68%). Convert any percent-of-100 in the source (e.g. '4.68%') to decimal.",
            "title": "Broker Cap Rate"
          },
          "grm": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Broker's stated Gross Rent Multiplier (e.g. 8.75), when the OM headline discloses one. asking_price \u00f7 grm implies the broker's GPI \u2014 the single cheapest cross-check for a dropped income line on the operating statement (CLAUDE.md VIII.7). Null when not stated; never computed.",
            "title": "Grm"
          },
          "stabilized_noi": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Year-1 stabilized / pro-forma NOI when broker shows it separately from current NOI.",
            "title": "Stabilized Noi"
          },
          "stabilized_cap_rate": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Stabilized / pro-forma cap rate as a decimal.",
            "title": "Stabilized Cap Rate"
          },
          "listing_broker": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Listing Broker"
          },
          "offer_due_date": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Best-and-final / call-for-offers date if stated. Free-text \u2014 many brokers write 'TBD'.",
            "title": "Offer Due Date"
          },
          "source_pages": {
            "description": "1-indexed pages the deal_summary was extracted from. Set by pipeline, not the model.",
            "items": {
              "type": "integer"
            },
            "title": "Source Pages",
            "type": "array"
          },
          "asking_price_search_pages": {
            "description": "1-indexed pages we searched for the asking price. Useful for debugging when asking_price is None despite the OM clearly stating a price somewhere in the financial summary.",
            "items": {
              "type": "integer"
            },
            "title": "Asking Price Search Pages",
            "type": "array"
          },
          "commercial_tenant_credit_flags": {
            "description": "Distinct credit_flags surfaced across every extracted commercial lease abstract. Mirrors lease_abstracts[].credit_flags so a downstream IC packet can read deal-level risk from one field without iterating into per-tenant detail. Set by the pipeline.",
            "items": {
              "type": "string"
            },
            "title": "Commercial Tenant Credit Flags",
            "type": "array"
          },
          "non_arms_length_leases_count": {
            "default": 0,
            "description": "Count of leases (commercial OR residential) where the source discloses a relationship between tenant and ownership \u2014 e.g. 'Acquaintance of the owner', employee leases, family rentals. Common in seller-friendly small-deal rent rolls to inflate occupancy; useful underwriting signal because those rents may not reflect market.",
            "title": "Non Arms Length Leases Count",
            "type": "integer"
          },
          "rent_roll_completeness_score": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "0.0\u20131.0 score reflecting how thoroughly the broker disclosed the rent roll (sf, beds/baths, lease dates, deposits, market rent, tenant names). Mirrors rent_roll.rent_roll_completeness_score so the Deal Summary surfaces disclosure quality at a glance. Null when no rent roll was extracted, or when the rent roll is synthesized.",
            "title": "Rent Roll Completeness Score"
          },
          "material_disclosures": {
            "description": "Structured disclosure findings surfaced by the post-extraction disclosure scanner. Each entry: {category, phrase, quote}. Categories: broker_affiliate_ownership, dual_agency, related_party. Set by the pipeline, not the LLM.",
            "items": {
              "type": "object"
            },
            "title": "Material Disclosures",
            "type": "array"
          },
          "deal_classification": {
            "default": "stabilized",
            "description": "Lifecycle bucket for the deal. Default 'stabilized' keeps existing institutional-OM behavior unchanged. Set by the pipeline, not the LLM.",
            "enum": [
              "stabilized",
              "value_add",
              "distressed",
              "lease_up",
              "development",
              "owner_user",
              "land"
            ],
            "title": "Deal Classification",
            "type": "string"
          },
          "classification_signals": {
            "description": "Distinct heuristic signals that fed the classification (e.g. 'distressed_keyword', 'occupancy_below_25pct', 'renovation_capex_present'). Surfaced on the Deal Summary sheet and the Audit sheet so analysts can see why the classifier landed where it did.",
            "items": {
              "type": "string"
            },
            "title": "Classification Signals",
            "type": "array"
          }
        },
        "title": "DealSummary",
        "type": "object"
      },
      "LeaseAbstract": {
        "description": "One per-tenant lease summary block.\n\nOptional fields stay null when the OM doesn't disclose the value;\nwe never invent. credit_flags drives the commercial_tenant_credit_review\nvalidator, so every flag should be backed by a verbatim quote in\ncredit_flag_source_text.",
        "properties": {
          "tenant_name": {
            "description": "Tenant name as it appears on the lease abstract block. Use the legal entity name when given (e.g. 'Triple J Industries LLC'), not a shortened DBA.",
            "title": "Tenant Name",
            "type": "string"
          },
          "space_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Suite / unit identifier. Use the same string the rent roll uses when the unit appears on both, so cross-validation can match rows.",
            "title": "Space Id"
          },
          "use": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Permitted use clause summary, e.g. 'Laundromat & coffee shop'. Free-text from the abstract.",
            "title": "Use"
          },
          "lease_commencement": {
            "anyOf": [
              {
                "format": "date",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Lease Commencement"
          },
          "lease_expiration": {
            "anyOf": [
              {
                "format": "date",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Lease Expiration"
          },
          "current_base_rent_monthly": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Current contractual base rent in $/month.",
            "title": "Current Base Rent Monthly"
          },
          "current_base_rent_annual": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Current contractual base rent in $/year. Compute as monthly \u00d7 12 when only monthly is shown, or vice versa, only if doing so does not disagree with another stated value in the abstract.",
            "title": "Current Base Rent Annual"
          },
          "escalation_type": {
            "anyOf": [
              {
                "enum": [
                  "fixed_pct",
                  "cpi",
                  "flat",
                  "step"
                ],
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Escalation Type"
          },
          "escalation_rate": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "When escalation_type is fixed_pct, this is the per-year rate as a decimal (0.03 for 3%). Null otherwise.",
            "title": "Escalation Rate"
          },
          "renewal_option_count": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Number of renewal options the lease grants the tenant.",
            "title": "Renewal Option Count"
          },
          "renewal_term_years": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Length of each renewal option in years.",
            "title": "Renewal Term Years"
          },
          "renewal_starting_rent": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Starting monthly rent at the first renewal, when stated.",
            "title": "Renewal Starting Rent"
          },
          "renewal_ending_rent": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Ending monthly rent at the last year of the renewal term, when the OM shows a renewal-period rent ramp ('renewal at $6,720\u2192$7,565').",
            "title": "Renewal Ending Rent"
          },
          "holdover_rate_monthly": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Holdover rent in $/month (often 125-150% of expiring base).",
            "title": "Holdover Rate Monthly"
          },
          "tenant_pays": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Free-text summary of expense categories the tenant pays (e.g. 'utilities, interior maintenance, taxes').",
            "title": "Tenant Pays"
          },
          "landlord_pays": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Free-text summary of expense categories the landlord pays.",
            "title": "Landlord Pays"
          },
          "cam_reimbursement_pct": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Tenant's pro-rata share of CAM (common-area maintenance) expenses as a decimal (0.20 for 20%). Null when the lease abstract doesn't disclose a CAM reimbursement structure.",
            "title": "Cam Reimbursement Pct"
          },
          "expense_categories_reimbursed": {
            "description": "Free-text categories the tenant reimburses, with a per-category share when the OM is specific. Examples: ['cam_general', 'water_40pct'], ['cam_general', 'water_partial'], ['taxes', 'insurance', 'cam']. Empty when no reimbursement structure is disclosed.",
            "items": {
              "type": "string"
            },
            "title": "Expense Categories Reimbursed",
            "type": "array"
          },
          "credit_flags": {
            "description": "Detected credit-risk markers. Populated from any disclosed concerns in the abstract or its footnotes. A bankruptcy disclosure must populate this list.",
            "items": {
              "enum": [
                "bankruptcy_protection",
                "in_default",
                "deferred_rent",
                "personal_guarantee",
                "corporate_guarantee",
                "subordinated",
                "lease_guaranty",
                "non_paying",
                "holdover",
                "non_arms_length"
              ],
              "type": "string"
            },
            "title": "Credit Flags",
            "type": "array"
          },
          "credit_flag_source_text": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Verbatim quote from the OM that triggered each credit flag. Surfaced in the validation message so a reviewer can see exactly what the abstract said without flipping to source.",
            "title": "Credit Flag Source Text"
          },
          "notes": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Any remaining narrative detail not captured above.",
            "title": "Notes"
          },
          "source_page": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "1-indexed page number where this abstract appears.",
            "title": "Source Page"
          }
        },
        "required": [
          "tenant_name"
        ],
        "title": "LeaseAbstract",
        "type": "object"
      },
      "OSLineItem": {
        "properties": {
          "category": {
            "enum": [
              "income",
              "operating_expense",
              "capex"
            ],
            "title": "Category",
            "type": "string"
          },
          "gl_account": {
            "title": "Gl Account",
            "type": "string"
          },
          "raw_label": {
            "title": "Raw Label",
            "type": "string"
          },
          "annual_value": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              }
            ],
            "title": "Annual Value"
          },
          "proforma_value": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Proforma Value"
          },
          "note": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Footnote / annotation text from the source, carried verbatim through merge from OSRawLineItem.note. Surfaced on the Operating Statement sheet and consumed by the IC-memo scaffold (ancillary-income breakdown, property-tax basis reconciliation). Null when the source line carries no footnote.",
            "title": "Note"
          },
          "monthly_value": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Monthly value as printed in the source when the document exposes both a Monthly and an Annual column. Carried through merge so the os_monthly_annual_consistency check can run on the post-merge OperatingStatement. Null when the source didn't print a monthly column for this row.",
            "title": "Monthly Value"
          },
          "confidence": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Confidence"
          },
          "needs_review": {
            "default": false,
            "title": "Needs Review",
            "type": "boolean"
          },
          "indent_level": {
            "default": 0,
            "title": "Indent Level",
            "type": "integer"
          },
          "is_subtotal": {
            "default": false,
            "title": "Is Subtotal",
            "type": "boolean"
          },
          "is_component": {
            "default": false,
            "title": "Is Component",
            "type": "boolean"
          },
          "parent_label": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Label of the parent subtotal this line rolls up into, if any. E.g. 'Total Reimbursement Income' for CAM, Insurance, RE Taxes and Mgmt Fees on a commercial NNN OS.",
            "title": "Parent Label"
          },
          "contributes_to_total": {
            "default": true,
            "description": "Gating flag for aggregation. True iff this line should be summed into Total Income / Total OpEx / NOI. False for components that roll up into a parent subtotal \u2014 the subtotal is the contributor, never both. Replaces the older subtotal-drop pattern that lost reimbursement income on NNN office OMs.",
            "title": "Contributes To Total",
            "type": "boolean"
          },
          "row_type": {
            "default": "line_item",
            "description": "Renderer-facing classification, written to the Operating Statement sheet's 'Row Type' column. The aggregator only sums rows where row_type == 'line_item'. Subtotals stay visible (broker's stated total alongside our components) but never contribute. 'header' is reserved for label-only section rows; 'noi' for the synthetic NOI row written by the renderer.",
            "enum": [
              "line_item",
              "subtotal",
              "header",
              "noi"
            ],
            "title": "Row Type",
            "type": "string"
          },
          "column_match_state": {
            "default": "both",
            "description": "When this row was detected as a subtotal, which columns qualified as a value-sum match? 'both' is the common case. 'current_only' / 'proforma_only' surface mixed-state roll-ups where only one column's components sum to the subtotal \u2014 rendered with a Note rather than silently dropped.",
            "enum": [
              "both",
              "current_only",
              "proforma_only",
              "none"
            ],
            "title": "Column Match State",
            "type": "string"
          },
          "source_sign": {
            "default": "positive",
            "description": "Sign convention as the broker presented the value in the source. 'positive' = the source printed a positive number (or parens-style accounting that we already inverted); 'negative' = the source printed a negative or parenthesized value. We standardize the stored annual_value AFTER capturing this \u2014 OpEx is always stored positive and subtracted during aggregation regardless of how the broker presented it. Surfaced on the OS Mapping Audit sheet so analysts can see how the source presented each line without contaminating math.",
            "enum": [
              "positive",
              "negative"
            ],
            "title": "Source Sign",
            "type": "string"
          },
          "is_recurring": {
            "default": true,
            "description": "True when this line represents recurring operating spend, False when the line is a one-time / non-recurring cost mixed into the OpEx column (e.g. 'New Intercom Installation', 'Appliance Purchase & Installation'). Capex lines are always is_recurring=False by construction. Drives steady_state_opex and steady_state_noi on the OperatingStatement so an underwriting model can isolate the in-place run-rate from trailing one-time costs.",
            "title": "Is Recurring",
            "type": "boolean"
          }
        },
        "required": [
          "category",
          "gl_account",
          "raw_label",
          "annual_value"
        ],
        "title": "OSLineItem",
        "type": "object"
      },
      "OperatingStatement": {
        "properties": {
          "property_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Property Name"
          },
          "source_property_label": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "The property name / address printed VERBATIM on the operating-statement / financial page header, captured before any canonicalization. Feeds the cross_property_consistency check: a financial page labeled with a DIFFERENT address than the subject (the 120 Storms Ave defect \u2014 its P&L page read '904 Bergenline Avenue, Union City') means the NOI/cap describe the wrong building. Null when the page carries no property header.",
            "title": "Source Property Label"
          },
          "period_start": {
            "anyOf": [
              {
                "format": "date",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Period Start"
          },
          "period_end": {
            "anyOf": [
              {
                "format": "date",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Period End"
          },
          "period_description": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Period Description"
          },
          "is_proforma": {
            "default": false,
            "title": "Is Proforma",
            "type": "boolean"
          },
          "has_proforma_column": {
            "default": false,
            "title": "Has Proforma Column",
            "type": "boolean"
          },
          "line_items": {
            "items": {
              "$ref": "#/components/schemas/OSLineItem"
            },
            "title": "Line Items",
            "type": "array"
          },
          "total_income": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Aggregated total income. NULL when our aggregation disagrees with the broker's stated EGR by >1% AND the value-based subtotal detector did not flag any roll-up \u2014 better to ship a missing number than a wrong number. See section_summary.os_aggregation.fallback_to_broker.",
            "title": "Total Income"
          },
          "total_operating_expenses": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Total Operating Expenses"
          },
          "noi": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Standardized NOI. NULL when total_income is NULL (the self-check fired) \u2014 never compute NOI on top of an unreliable Total Income.",
            "title": "Noi"
          },
          "expense_ratio": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Expense Ratio"
          },
          "proforma_total_income": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Proforma Total Income"
          },
          "proforma_total_operating_expenses": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Proforma Total Operating Expenses"
          },
          "proforma_noi": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Proforma Noi"
          },
          "broker_noi": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Broker Noi"
          },
          "capex_reclassified": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              }
            ],
            "default": "0",
            "title": "Capex Reclassified"
          },
          "broker_egr": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Broker Egr"
          },
          "broker_stated_noi": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Broker Stated Noi"
          },
          "broker_stated_noi_proforma": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Broker Stated Noi Proforma"
          },
          "proforma_noi_standardized": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Proforma Noi Standardized"
          },
          "proforma_noi_broker_stated": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Proforma Noi Broker Stated"
          },
          "proforma_capex_reclassified": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              }
            ],
            "default": "0",
            "title": "Proforma Capex Reclassified"
          },
          "proforma_noi_convention_delta": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "proforma_noi_standardized - proforma_noi_broker_stated. Equal to proforma_capex_reclassified by construction; duplicated here so the analyst can read the reconciliation without re-doing the math.",
            "title": "Proforma Noi Convention Delta"
          },
          "noi_standardized": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Our standardized NOI: total_income - sum(operating_expense). Capex / reserves are excluded by construction. Equal to `noi` in the common case; surfaced as its own field so an analyst reading the OS Summary doesn't have to recompute when the broker uses a different convention.",
            "title": "Noi Standardized"
          },
          "noi_broker_stated": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "The broker-stated NOI for the current period. Prefers the OS source's printed NOI line (broker_stated_noi). Falls back to DealSummary.broker_noi (cover / exec-summary page) when the OS doesn't print an NOI subtotal. Null when neither source discloses an NOI.",
            "title": "Noi Broker Stated"
          },
          "noi_convention_note": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Human-readable derivation note explaining the gap between noi_standardized and noi_broker_stated when the gap is explained by a capex/reserves convention difference. Example: 'Broker NOI includes $5,242 of capex (Intercom, Appliance) in OpEx; standardized NOI excludes.' Null when the two views agree or when no broker NOI is available.",
            "title": "Noi Convention Note"
          },
          "steady_state_opex": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Sum of operating_expense line items where is_recurring=True. Equal to total_operating_expenses when no non-recurring items are present. Null when total_operating_expenses is null.",
            "title": "Steady State Opex"
          },
          "steady_state_noi": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "total_income - steady_state_opex. Excludes one-time costs the broker bundled into OpEx so underwriting can isolate the in-place run-rate from trailing non-recurring spend. Null when total_income or steady_state_opex is null.",
            "title": "Steady State Noi"
          },
          "one_time_costs": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              }
            ],
            "default": "0",
            "description": "Sum of operating_expense annual_value where is_recurring=False. Used to render the 'One-Time Costs (Excluded from NOI)' line with its component breakdown on the OS Summary sheet.",
            "title": "One Time Costs"
          },
          "proforma_period_description": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Header of the forward column (carried through from the raw extraction) \u2014 'Year 1 Adjusted', 'Current Budget', etc. Null when the source had no second column or it was unlabeled.",
            "title": "Proforma Period Description"
          },
          "opex_basis_columns": {
            "anyOf": [
              {
                "items": {
                  "$ref": "#/components/schemas/OpexBasisColumn"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Per-column opex-basis classification. None when the classifier never ran (legacy direct construction).",
            "title": "Opex Basis Columns"
          },
          "noi_by_basis": {
            "anyOf": [
              {
                "additionalProperties": {
                  "anyOf": [
                    {
                      "type": "number"
                    },
                    {
                      "type": "string"
                    }
                  ]
                },
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "One standardized NOI per detected opex basis: {'actuals': ..., 'buyer_budget': ..., 'pro_forma': ...}. The broker headline NOI is matched against these to decide whether a gap is the new-buyer cost load (INFO) or a real defect (WARN).",
            "title": "Noi By Basis"
          },
          "current_opex_basis": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Basis our standardized rebuild (the Current column) sits on \u2014 almost always 'actuals'. Used to decide whether a broker-vs-rebuild gap is cross-basis (new-buyer cost load) or same-basis (genuine reconciliation defect).",
            "title": "Current Opex Basis"
          },
          "opex_basis_unknown": {
            "default": false,
            "description": "True when at least one labeled column could not be classified deterministically \u2014 drives a WARN so the basis is reviewed manually rather than silently defaulted.",
            "title": "Opex Basis Unknown",
            "type": "boolean"
          }
        },
        "required": [
          "line_items"
        ],
        "title": "OperatingStatement",
        "type": "object"
      },
      "OpexBasisColumn": {
        "description": "One operating-statement column classified to an opex basis.\n\nPopulated post-merge by app.services.opex_basis. Surfaced on the OS\nMapping Audit sheet and fed to the IC-memo contract so the broker-vs-\nstandardized NOI gap can be reframed as the new-buyer cost load\n(CLAUDE.md VIII.2) instead of flagged as a defect.",
        "properties": {
          "header": {
            "description": "Column header as classified (verbatim or 'Current'/'Pro Forma' fallback).",
            "title": "Header",
            "type": "string"
          },
          "basis": {
            "enum": [
              "actuals",
              "buyer_budget",
              "pro_forma",
              "unknown"
            ],
            "title": "Basis",
            "type": "string"
          },
          "matched_pattern": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "The regex that matched the header, or null when the basis was defaulted.",
            "title": "Matched Pattern"
          },
          "source": {
            "description": "Which OS column this row came from (annual_value vs proforma_value).",
            "enum": [
              "current",
              "proforma"
            ],
            "title": "Source",
            "type": "string"
          },
          "noi": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Standardized NOI built from this column, when available.",
            "title": "Noi"
          }
        },
        "required": [
          "header",
          "basis",
          "source"
        ],
        "title": "OpexBasisColumn",
        "type": "object"
      },
      "PriceCandidate": {
        "description": "One currency value seen near the OM's asking-price block.\n\nCarries enough metadata to distinguish a struck-through original price\nfrom the active asking price (the 2226 Whittier Blvd bug class) and to\ndo a cross-page consensus check when multiple candidates appear.",
        "properties": {
          "value": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              }
            ],
            "description": "The currency value with no symbols/commas (e.g. 3350000).",
            "title": "Value"
          },
          "page": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "1-indexed PDF page where the value appears.",
            "title": "Page"
          },
          "is_struck_through": {
            "default": false,
            "description": "True if the value is rendered with strikethrough / line-through styling, an HTML <s>/<strike>/<del> tag, or an explicit visual cross-out. A struck-through price is almost always a stale previous asking price the broker dropped from.",
            "title": "Is Struck Through",
            "type": "boolean"
          },
          "context": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Short label for where the candidate was found (e.g. 'cover', 'exec_summary', 'property_tax_footnote').",
            "title": "Context"
          }
        },
        "required": [
          "value"
        ],
        "title": "PriceCandidate",
        "type": "object"
      },
      "ProFormaRentRoll": {
        "properties": {
          "property_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Property Name"
          },
          "as_of_date": {
            "anyOf": [
              {
                "format": "date",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Effective / projection-base date of the pro forma.",
            "title": "As Of Date"
          },
          "units": {
            "items": {
              "$ref": "#/components/schemas/ProFormaUnit"
            },
            "title": "Units",
            "type": "array"
          },
          "total_base_rent_annual": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              }
            ],
            "description": "Sum of base_rent_annual across all units.",
            "title": "Total Base Rent Annual"
          },
          "total_reimbursements_annual": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              }
            ],
            "description": "Sum of all reimbursement amounts across all units.",
            "title": "Total Reimbursements Annual"
          },
          "total_annual_revenue": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              }
            ],
            "description": "Total stabilized annual revenue = total_base_rent_annual + total_reimbursements_annual. The headline pro forma number a buyer pastes into IC.",
            "title": "Total Annual Revenue"
          },
          "signed_unit_count": {
            "default": 0,
            "description": "Count of units with lease_status in {signed_renewal, signed_new}.",
            "title": "Signed Unit Count",
            "type": "integer"
          },
          "assumed_unit_count": {
            "default": 0,
            "description": "Count of units with lease_status == assumed_new.",
            "title": "Assumed Unit Count",
            "type": "integer"
          }
        },
        "required": [
          "units",
          "total_base_rent_annual",
          "total_reimbursements_annual",
          "total_annual_revenue"
        ],
        "title": "ProFormaRentRoll",
        "type": "object"
      },
      "ProFormaSummary": {
        "description": "Single-period stabilized projection with value-add underwriting.",
        "properties": {
          "gross_rental_income": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Gross Rental Income / Gross Scheduled Rent / GPR \u2014 annual base rent at market for every unit, before vacancy. Strip $ and commas.",
            "title": "Gross Rental Income"
          },
          "other_income": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Other income (parking, laundry, application fees, RUBS not separately reimbursed, pet rent, etc.). Annual.",
            "title": "Other Income"
          },
          "reimbursements": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "RUBS / CAM / expense pass-throughs. Annual.",
            "title": "Reimbursements"
          },
          "gross_potential_income": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Gross Potential Income (GPI) = gross_rental_income + other_income + reimbursements. The broker's stated total before vacancy.",
            "title": "Gross Potential Income"
          },
          "vacancy_credit_loss": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Vacancy + credit loss as a NEGATIVE Decimal value (e.g. -129760) OR the broker's stated absolute deduction amount; extractor normalizes to positive-deduction below.",
            "title": "Vacancy Credit Loss"
          },
          "vacancy_credit_loss_pct": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Vacancy / credit loss as a decimal (0.10 for 10%).",
            "title": "Vacancy Credit Loss Pct"
          },
          "effective_gross_income": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "EGI = GPI \u2212 vacancy_credit_loss.",
            "title": "Effective Gross Income"
          },
          "operating_expenses_detail": {
            "additionalProperties": {
              "anyOf": [
                {
                  "type": "number"
                },
                {
                  "type": "string"
                }
              ]
            },
            "description": "Free-form expense line items keyed by the broker's label (e.g. 'Property Taxes', 'Insurance', 'Utilities \u2014 Electric'). Annual amounts. Captured verbatim so analysts can trace back to source.",
            "title": "Operating Expenses Detail",
            "type": "object"
          },
          "property_taxes": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Property Taxes"
          },
          "insurance": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Insurance"
          },
          "utilities": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Utilities"
          },
          "repairs_maintenance": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Repairs Maintenance"
          },
          "contract_services": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Contract Services"
          },
          "payroll": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Payroll"
          },
          "management_fee": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Management Fee"
          },
          "management_fee_pct": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Management fee as a decimal of EGI (0.035 for 3.5%).",
            "title": "Management Fee Pct"
          },
          "general_admin": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "General Admin"
          },
          "replacement_reserves": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Replacement Reserves"
          },
          "total_operating_expenses": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Total annual OpEx. Should equal sum of the detail bag.",
            "title": "Total Operating Expenses"
          },
          "net_operating_income": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "NOI = EGI \u2212 total_operating_expenses.",
            "title": "Net Operating Income"
          },
          "purchase_price": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Purchase / asking / list price \u2014 when the pro forma page explicitly footnotes the price used to compute going-in cap. May duplicate DealSummary.asking_price; left here so the Pro Forma sheet stands alone.",
            "title": "Purchase Price"
          },
          "renovation_capex": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Total renovation / value-add capital budget. Label variants: 'CapEx Budget', 'Renovation Budget', 'Estimated Renovation/Capital Expenditures', 'Value-Add Budget'. All map here.",
            "title": "Renovation Capex"
          },
          "renovation_capex_per_unit": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Renovation capex divided by unit count, when stated.",
            "title": "Renovation Capex Per Unit"
          },
          "holding_costs": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Carry / interest reserve / construction-period operating shortfall. Labels: 'Holding Costs', 'Construction Period Carry', 'Interest Reserve'.",
            "title": "Holding Costs"
          },
          "total_project_cost": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Total all-in basis: purchase_price + renovation_capex + holding_costs. Labels: 'Total Project Cost', 'Total Investment', 'All-in Basis'.",
            "title": "Total Project Cost"
          },
          "going_in_cap_rate": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Broker's stated going-in cap rate, as a decimal (0.087 for 8.7%). For value-add deals this is typically computed on total_project_cost, NOT purchase_price \u2014 see the cap_rate_basis_reconciliation validator.",
            "title": "Going In Cap Rate"
          },
          "exit_cap_rate": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Stabilized exit / disposition cap rate as a decimal.",
            "title": "Exit Cap Rate"
          },
          "projected_roi": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Total projected return as a decimal (0.238 for 23.8%).",
            "title": "Projected Roi"
          },
          "projected_irr": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Projected IRR as a decimal (0.18 for 18%). Often present alongside ROI on value-add OMs.",
            "title": "Projected Irr"
          },
          "purchase_price_per_unit": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Purchase Price Per Unit"
          },
          "total_cost_per_unit": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Total Cost Per Unit"
          },
          "noi_per_unit": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Noi Per Unit"
          },
          "source_pages": {
            "items": {
              "type": "integer"
            },
            "title": "Source Pages",
            "type": "array"
          },
          "extraction_confidence": {
            "default": 0.0,
            "description": "0.0\u20131.0 \u2014 fraction of headline fields the LLM could populate. Computed post-extraction; default 0.0 so missing extractions read as low confidence.",
            "title": "Extraction Confidence",
            "type": "number"
          },
          "is_stabilized_projection": {
            "default": true,
            "description": "True for value-add / distressed deals where the pro forma is a stabilized projection rather than a trailing actual.",
            "title": "Is Stabilized Projection",
            "type": "boolean"
          }
        },
        "title": "ProFormaSummary",
        "type": "object"
      },
      "ProFormaUnit": {
        "description": "One row of the pro forma rent roll. Keep the schema close to the\nOM's tabular shape \u2014 base rent + a list of reimbursements per tenant \u2014\nrather than coercing into a single 'rent' number. Buyers price each\ncomponent differently.",
        "properties": {
          "unit_number": {
            "description": "Unit / suite identifier as it appears on the OM. Use the same string the in-place rent roll uses when the unit appears on both, so cross-validation can match rows.",
            "title": "Unit Number",
            "type": "string"
          },
          "tenant_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Tenant name. Use 'New Tenant' or similar when the OM marks the row as a placeholder for assumed lease-up.",
            "title": "Tenant Name"
          },
          "square_feet": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Square Feet"
          },
          "lease_status": {
            "description": "Status of this row's lease. See LeaseStatus docstring for the five options. Infer from color bands when present, or from the tenant name + date pattern.",
            "enum": [
              "in_place",
              "signed_renewal",
              "signed_new",
              "assumed_new",
              "expiring_holdover"
            ],
            "title": "Lease Status",
            "type": "string"
          },
          "base_rent_annual": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Annual contractual base rent in dollars (excludes reimbursements).",
            "title": "Base Rent Annual"
          },
          "reimbursements": {
            "description": "Per-line reimbursement amounts. The OM typically itemizes tax pass-throughs and CAM contributions as separate rows under each tenant \u2014 capture them as separate entries, not folded into base_rent_annual.",
            "items": {
              "$ref": "#/components/schemas/ReimbursementLine"
            },
            "title": "Reimbursements",
            "type": "array"
          },
          "total_annual_revenue": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Total annual revenue for this row = base_rent_annual + sum of reimbursements. Compute it explicitly so the workbook can be reconciled without re-summing reimbursements.",
            "title": "Total Annual Revenue"
          },
          "lease_start": {
            "anyOf": [
              {
                "format": "date",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Lease commencement date in ISO YYYY-MM-DD.",
            "title": "Lease Start"
          },
          "lease_end": {
            "anyOf": [
              {
                "format": "date",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Lease expiration date in ISO YYYY-MM-DD.",
            "title": "Lease End"
          },
          "effective_date": {
            "anyOf": [
              {
                "format": "date",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "When the pro-forma rent begins. For signed_renewal / signed_new, this is the lease commencement date; for assumed_new, it's the broker's assumed lease-up date.",
            "title": "Effective Date"
          },
          "notes": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Notes"
          },
          "source_page": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "1-indexed page number where this row appears in the source PDF.",
            "title": "Source Page"
          }
        },
        "required": [
          "unit_number",
          "lease_status"
        ],
        "title": "ProFormaUnit",
        "type": "object"
      },
      "PublicExtractionContext": {
        "description": "A partial snapshot of extracted artifacts. Every field is optional;\nchecks whose prerequisites aren't supplied return `skipped` results.\n\nField names mirror `services/validation/base.py::ExtractionContext`\nso a buyer who reads the internal source isn't surprised by a\ndifferent shape on the wire.",
        "properties": {
          "deal_summary": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DealSummary"
              },
              {
                "type": "null"
              }
            ],
            "default": null
          },
          "rent_roll": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/RentRoll"
              },
              {
                "type": "null"
              }
            ],
            "default": null
          },
          "pro_forma_rent_roll": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ProFormaRentRoll"
              },
              {
                "type": "null"
              }
            ],
            "default": null
          },
          "pro_forma_summary": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ProFormaSummary"
              },
              {
                "type": "null"
              }
            ],
            "default": null
          },
          "operating_statement": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/OperatingStatement"
              },
              {
                "type": "null"
              }
            ],
            "default": null
          },
          "t12": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/T12"
              },
              {
                "type": "null"
              }
            ],
            "default": null
          },
          "lease_abstracts": {
            "anyOf": [
              {
                "items": {
                  "$ref": "#/components/schemas/LeaseAbstract"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Lease Abstracts"
          },
          "document_type": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Document Type"
          },
          "section_summary": {
            "anyOf": [
              {
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Section Summary"
          }
        },
        "title": "PublicExtractionContext",
        "type": "object"
      },
      "ReimbursementLine": {
        "properties": {
          "reimbursement_type": {
            "description": "What's being reimbursed: 'Real Estate Tax Reimbursement', 'CAM Contribution', 'Operating Expense Pass-Through', etc. Use the OM's label verbatim.",
            "title": "Reimbursement Type",
            "type": "string"
          },
          "annual_amount": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              }
            ],
            "description": "Annual reimbursement amount in dollars.",
            "title": "Annual Amount"
          }
        },
        "required": [
          "reimbursement_type",
          "annual_amount"
        ],
        "title": "ReimbursementLine",
        "type": "object"
      },
      "RentRoll": {
        "properties": {
          "property_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Property Name"
          },
          "source_property_label": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "The property name / address printed VERBATIM on the rent-roll's own page header, captured before any canonicalization. Used by the cross_property_consistency check to detect when the rent roll belongs to a DIFFERENT building than the subject (the 120 Storms Ave defect: a 101-unit development OM with an unrelated 8-unit building's rent roll stapled in, labeled '904 Bergenline Ave'). Null when the page carries no property header.",
            "title": "Source Property Label"
          },
          "as_of_date": {
            "anyOf": [
              {
                "format": "date",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Effective date of the rent roll",
            "title": "As Of Date"
          },
          "units": {
            "items": {
              "$ref": "#/components/schemas/RentRollUnit"
            },
            "title": "Units",
            "type": "array"
          },
          "total_units": {
            "title": "Total Units",
            "type": "integer"
          },
          "occupied_units": {
            "title": "Occupied Units",
            "type": "integer"
          },
          "vacant_units": {
            "title": "Vacant Units",
            "type": "integer"
          },
          "total_monthly_rent": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              }
            ],
            "title": "Total Monthly Rent"
          },
          "average_rent": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              }
            ],
            "title": "Average Rent"
          },
          "occupancy_rate": {
            "description": "occupied_units / total_units, 0.0 to 1.0",
            "title": "Occupancy Rate",
            "type": "number"
          },
          "total_banked_monthly": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Sum of banked_monthly across units. Null when no unit disclosed banked rent \u2014 distinguishes 'building has no banked rent' from 'we summed across nothing'.",
            "title": "Total Banked Monthly"
          },
          "total_banked_annual": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Sum of banked_annual across units. Null when none disclosed.",
            "title": "Total Banked Annual"
          },
          "units_with_banked_rent": {
            "default": 0,
            "description": "Count of units carrying any disclosed banked rent.",
            "title": "Units With Banked Rent",
            "type": "integer"
          },
          "banked_rent_pct_of_gpi": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "total_banked_annual / (total_monthly_rent \u00d7 12). Null when either side is missing or zero. Reflects how much potential upside the building's banked rent represents as a share of current GPI \u2014 useful sizing signal for SFRB-constrained deals.",
            "title": "Banked Rent Pct Of Gpi"
          },
          "banked_rent_recovery_constrained": {
            "default": false,
            "description": "True when the rent roll is in a jurisdiction with a per-unit annual recovery cap (e.g. SF Rent Board \u00a71.21 caps banked rent recovery at 10% per unit per year). Surfaces the constraint so downstream models don't treat banked rent as immediately recoverable.",
            "title": "Banked Rent Recovery Constrained",
            "type": "boolean"
          },
          "tenant_names_redacted_in_source": {
            "default": false,
            "description": "True when the source explicitly states tenant names are withheld pre-NDA / CA execution ('Tenant names available upon CA execution'). Lets downstream consumers tell 'no tenant data' apart from 'tenant data redacted by design'.",
            "title": "Tenant Names Redacted In Source",
            "type": "boolean"
          },
          "non_arms_length_leases_count": {
            "default": 0,
            "description": "Count of units in this rent roll where the source footnote discloses a tenant relationship to ownership (acquaintance, family, employee, etc.). Aggregated by the pipeline from the per-unit non_arms_length flag.",
            "title": "Non Arms Length Leases Count",
            "type": "integer"
          },
          "residential_units": {
            "default": 0,
            "description": "Count of units in this rent roll classified as residential. Set by the post-extraction reclassification pass; defaults to 0 until the pass runs. On a pure-residential rent roll this equals total_units.",
            "title": "Residential Units",
            "type": "integer"
          },
          "commercial_units": {
            "default": 0,
            "description": "Count of units classified as commercial (retail / office / ground-floor tenancy on a mixed-use roll). Driven by the post-extraction commercial-classification heuristic so the Operating Statement can split residential vs commercial income downstream.",
            "title": "Commercial Units",
            "type": "integer"
          },
          "commercial_income_pct_of_gpi": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Sum of commercial monthly_rent \u00d7 12 \u00f7 (sum of all unit monthly_rent \u00d7 12). Null when there are no commercial units or no in-place rent. Surfaces how much of the building's gross potential income comes from commercial tenancy.",
            "title": "Commercial Income Pct Of Gpi"
          },
          "rent_roll_completeness_score": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "0.0\u20131.0 score reflecting how many of the 'standard' rent-roll fields (sf, beds, baths, lease_start, lease_end, monthly_rent, market_rent, security_deposit, tenant) are populated across every unit. Buyers use this to triage disclosure quality before committing to LOI. Null on synthesized rolls \u2014 the score is meaningless when the units themselves are derived.",
            "title": "Rent Roll Completeness Score"
          },
          "source": {
            "default": "per_unit",
            "description": "Where the per-row data came from. 'per_unit' is a real broker rent roll. 'synthesized' is one row per unit type aggregated from the OM's unit-mix table when no per-unit roll was given.",
            "enum": [
              "per_unit",
              "synthesized"
            ],
            "title": "Source",
            "type": "string"
          },
          "synthesis_signals": {
            "description": "When source='synthesized', the names of the heuristic signals that contributed: 'unit_mix_table', 'kpi_callouts', 'comp_subject_row'. Empty for 'per_unit'.",
            "items": {
              "type": "string"
            },
            "title": "Synthesis Signals",
            "type": "array"
          }
        },
        "required": [
          "units",
          "total_units",
          "occupied_units",
          "vacant_units",
          "total_monthly_rent",
          "average_rent",
          "occupancy_rate"
        ],
        "title": "RentRoll",
        "type": "object"
      },
      "RentRollUnit": {
        "properties": {
          "unit_number": {
            "description": "Unit number / suite identifier as it appears on the doc. For type-aggregated rolls (e.g. '1 Bed, 1 Bath \u00d7 37'), use the type label as the unit_number and set unit_count=37.",
            "title": "Unit Number",
            "type": "string"
          },
          "unit_count": {
            "default": 1,
            "description": "Number of units this row represents. 1 for unit-by-unit rolls (default). >1 only when the source aggregates multiple units of the same type into one row, in which case the rent values are averages.",
            "title": "Unit Count",
            "type": "integer"
          },
          "tenant_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Tenant or lessee name. Use null for vacant or 'VACANT' rows or aggregated rows.",
            "title": "Tenant Name"
          },
          "square_feet": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Square Feet"
          },
          "bedrooms": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Bedrooms"
          },
          "bathrooms": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Bathrooms"
          },
          "lease_start": {
            "anyOf": [
              {
                "format": "date",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Lease start in ISO YYYY-MM-DD. Infer year from context if ambiguous.",
            "title": "Lease Start"
          },
          "lease_end": {
            "anyOf": [
              {
                "format": "date",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Lease End"
          },
          "monthly_rent": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Current monthly rent. Strip currency symbols/commas.",
            "title": "Monthly Rent"
          },
          "market_rent": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Market Rent"
          },
          "security_deposit": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Security Deposit"
          },
          "is_occupied": {
            "default": true,
            "description": "false if VACANT, no tenant, or zero rent",
            "title": "Is Occupied",
            "type": "boolean"
          },
          "notes": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Notes"
          },
          "banked_pct": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Banked rent percentage as a decimal (0.115 for 11.5%). The share of allowable rent the landlord has not yet imposed. Null when the source doesn't disclose.",
            "title": "Banked Pct"
          },
          "banked_monthly": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Banked rent in $/month for this unit when disclosed.",
            "title": "Banked Monthly"
          },
          "banked_annual": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Banked rent in $/year for this unit. Compute as banked_monthly \u00d7 12 only when the source is silent on annual but explicit on monthly. Null when the source discloses neither.",
            "title": "Banked Annual"
          },
          "unit_designation": {
            "anyOf": [
              {
                "enum": [
                  "resident_manager",
                  "model",
                  "office",
                  "down",
                  "common_area",
                  "renovation",
                  "commercial",
                  "address_unit",
                  "garage",
                  "parking",
                  "storage",
                  "laundry_room"
                ],
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Special designation explaining a footnote symbol on the unit label (e.g. 'Unit 121*' marked as the resident manager's unit). Null when the unit has no special designation. Drives the unit_label_sanity check's footnote-aware suppression. 'commercial' covers retail / office tenancy on a mixed-use rent roll; 'address_unit' covers corner-property units that carry their own street number (e.g. '139 8th' on a building addressed 135 8th). 'garage' / 'parking' / 'storage' / 'laundry_room' mark non-residential income lines that must be excluded from the residential unit count (CLAUDE.md VIII.8) \u2014 their rent flows to Ancillary Income.",
            "title": "Unit Designation"
          },
          "commercial_address_distinct": {
            "default": false,
            "description": "True when the unit_number is itself a street-address-style identifier separate from the building's primary address ('139 8th' on a 135 8th Street building). Suppresses the OCR-collision false-positive on the unit_label_sanity check.",
            "title": "Commercial Address Distinct",
            "type": "boolean"
          },
          "non_arms_length": {
            "default": false,
            "description": "True when a source footnote describes the tenant as related to ownership in a way that makes the lease non-arm's-length (e.g. 'Acquaintance of the owner', employee, family, related party). Tenant rent on these units may not reflect market and should be discounted in underwriting.",
            "title": "Non Arms Length",
            "type": "boolean"
          },
          "non_arms_length_source_text": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "Verbatim source footnote that triggered non_arms_length. Surfaced in validation messaging so a reviewer can see the OM's exact wording without flipping to source.",
            "title": "Non Arms Length Source Text"
          },
          "source_page": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "description": "1-indexed page number where this unit appears in the source PDF",
            "title": "Source Page"
          }
        },
        "required": [
          "unit_number"
        ],
        "title": "RentRollUnit",
        "type": "object"
      },
      "T12": {
        "properties": {
          "property_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Property Name"
          },
          "period_start": {
            "anyOf": [
              {
                "format": "date",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Period Start"
          },
          "period_end": {
            "anyOf": [
              {
                "format": "date",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Period End"
          },
          "line_items": {
            "items": {
              "$ref": "#/components/schemas/T12LineItem"
            },
            "title": "Line Items",
            "type": "array"
          },
          "total_income": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              }
            ],
            "title": "Total Income"
          },
          "total_operating_expenses": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              }
            ],
            "title": "Total Operating Expenses"
          },
          "noi": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              }
            ],
            "title": "Noi"
          },
          "expense_ratio": {
            "title": "Expense Ratio",
            "type": "number"
          }
        },
        "required": [
          "line_items",
          "total_income",
          "total_operating_expenses",
          "noi",
          "expense_ratio"
        ],
        "title": "T12",
        "type": "object"
      },
      "T12LineItem": {
        "properties": {
          "category": {
            "enum": [
              "income",
              "operating_expense",
              "capex"
            ],
            "title": "Category",
            "type": "string"
          },
          "gl_account": {
            "title": "Gl Account",
            "type": "string"
          },
          "raw_label": {
            "title": "Raw Label",
            "type": "string"
          },
          "monthly_values": {
            "items": {
              "anyOf": [
                {
                  "type": "number"
                },
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ]
            },
            "title": "Monthly Values",
            "type": "array"
          },
          "total": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              }
            ],
            "title": "Total"
          },
          "confidence": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Confidence"
          },
          "needs_review": {
            "default": false,
            "title": "Needs Review",
            "type": "boolean"
          }
        },
        "required": [
          "category",
          "gl_account",
          "raw_label",
          "monthly_values",
          "total"
        ],
        "title": "T12LineItem",
        "type": "object"
      }
    },
    "securitySchemes": {
      "HTTPBearer": {
        "type": "http",
        "scheme": "bearer"
      },
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Issued out-of-band per integration partner. See docs/monetization/validation-api-spec.md \u00a72."
      }
    }
  },
  "servers": [
    {
      "url": "https://api.rentrolliq.com",
      "description": "Production"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ]
}
