Imported from xuxoramos/renter-shield (
AGENTS.md). Install upstream withnpx skills add xuxoramos/renter-shield. Copyright stays with the author.
Project Guidelines
Purpose
Toolkit for scoring U.S. landlords by housing code violation severity across
multiple jurisdictions. Built for the New York Office of the Attorney General
(NYOAG) investigation. The companion analysis repo
(nyoag-landlord-investigation) consumes this package for notebooks and
evidentiary snapshots.
Architecture
- Adapter pattern: each city is a
JurisdictionAdaptersubclass inrenter_shield/jurisdictions/. Adapters download raw data, normalize it into the universal schema (models.py), and returnpolars.LazyFrames. New cities only need a new adapter + a one-line entry inconfig.py:JURISDICTION_REGISTRY. - Universal schema: three tables — violations, properties, contacts —
defined in
models.py. All adapters must produce these columns; extra columns are ignored downstream. - Scoring via DuckDB: DuckDB reads Parquet files directly at query time.
No persistent
.duckdbfile — Parquet is the storage layer. - FastAPI server (
api.py): read-only queries against scored Parquet output. API key auth via header. - CLI (
cli.py): flag-based (--download,--parallel,--strict,-j/--jurisdictions). Downloads (optionally in parallel), validates each adapter's schema for drift, then scores. Data dir defaults todata/(relative, gitignored).
Code Style
- Python 3.11+. Type hints on public functions.
- Polars for all DataFrame work — never pandas. Use
LazyFramewhere possible;.collect()only at boundaries (file write, API response). - stdlib
urllibfor HTTP downloads — norequestsorhttpxdep. Some adapters usesodapyfor Socrata but it's optional. - Parquet files use zstd compression.
- Severity mapping keywords are defined at the top of each adapter. When adding or changing keywords, keep them sorted and comment the rationale.
Key Constraints
MIN_DATE = "2022-01-01"inconfig.py— do not change without reading the COVID-era inspection-gap rationale in the comment above it and inrenter_shield/README.md § Time Horizon.SCORE_WEIGHTSmust sum to 1.0.- BBL formats are jurisdiction-specific (e.g.
sf-{block}{lot},bos-{sam_id}). The BBL is the primary join key across all three tables. - Adapters that lack owner data return an empty
LazyFramewith the correctCONTACTS_SCHEMAcolumns — neverNoneor missing columns. - HUD REAC is federal, not city-level. It produces synthetic violation records from inspection scores, not individual code violations.
Build and Test
pip install -e ".[dev]" # install with dev deps
pytest # run tests
ruff check . # lint
Adding a New Jurisdiction
- Create
renter_shield/jurisdictions/<city>.pysubclassingJurisdictionAdapter. - Implement
download(),load_violations(),load_properties(),load_contacts(). - Map local violation codes to severity tiers 1-4 using keyword heuristics.
- Add one entry to
JURISDICTION_REGISTRYinconfig.py. - Verify with:
python -c "from renter_shield.jurisdictions.<city> import <Cls>; print('OK')".
Conventions
- Data files go in
data/(gitignored). Adapters write<jurisdiction>_<dataset>.parquet. - Never commit data. The analysis repo handles archival + checksums.
- Adapter
download()methods should be idempotent — overwrite existing files, don't append. - When an open-data API changes schema, update the adapter and note the
change in
renter_shield/README.md.