Skip to content

feat(data): add Rentometer API adapter for real rent comps - #376

Merged
paruff merged 4 commits into
mainfrom
feat/data-rentometer
Aug 21, 2026
Merged

feat(data): add Rentometer API adapter for real rent comps#376
paruff merged 4 commits into
mainfrom
feat/data-rentometer

Conversation

@paruff

@paruff paruff commented Aug 21, 2026

Copy link
Copy Markdown
Owner

Summary

Integrates Rentometer API for actual market rent estimates by address/ZIP. Replaces HUD FMR (40%+ off actual rents) as primary rent data source.

Changes

  • New adapter: core/integrations/market/rentometer.py with 7-day in-memory cache
  • Updated screening.py _get_monthly_rent() fallback chain: Rentometer → HUD FMR → None
  • Added _extract_zip() helper for ZIP code extraction from property/address
  • Added RENTO_METER_API_KEY to Django settings
  • 9 unit tests + 5 E2E tests

Acceptance Criteria Met

  • Add RENTO_METER_API_KEY to environment config
  • Create Rentometer adapter with get_rent_estimate() function
  • Add caching layer (7-day TTL)
  • Update screening to use real rent data, fall back to HUD FMR
  • Unit tests for adapter (mock API responses)
  • E2E tests for screening integration

Technical Notes

  • Rentometer API: https://www.rentometer.com/api
  • Rate limit: 1,000 calls/month — cache aggressively
  • Fallback chain: Rentometer → HUD FMR → user-entered rent
  • All rent values use Decimal (repo rule)

Closes #359

paruff added 4 commits August 20, 2026 22:00
Fixes the exact bug that lost real work on this repo's own opencode validation run today (issue #369): opencode found and fixed a genuine 47-file Python 2 except-syntax bug plus the requested feature, 815 tests passing -- then git commit failed the local commitizen commit-msg hook (no type prefix), and since the hook blocks commit creation rather than just flagging it after, there was nothing for a backstop to fix. All of that work was lost when the runner tore down.

This repo's opencode.yml never had a commit-msg-hook-install step in the first place (that pattern only existed on fawkes), so there is nothing to remove here -- this PR is the model resolver and the routing test, ported from paruff/fawkes (#1630 there) after the prei validation run's failure motivated both fixes.

Replaces the hardcoded model: string with a Resolve model step that verifies the preferred model against the live models.dev catalog before use and falls back (with a visible warning) to another free tool-calling model on the same provider if it has been renamed or removed.

Adds tests/unit/test_opencode_routing.py, testing the real if: trigger expression and the resolver's PREFERRED= literals extracted directly from the workflow file. Verified with ruff/mypy directly (this repo's pytest.ini needs plugins not installed in this session, so ran via a standalone module load instead of pytest) -- all assertions pass, noqa: S307 added since this repo's ruff config enables that rule unlike fawkes's.
Same mistake as uFawkesDevX: copying fawkes's full opencode.yml to apply the commit-hook-removal + model-resolver fixes accidentally re-added the Terraform/TFLint pre-install steps I'd deliberately removed for this repo earlier (zero .tf files here). Caught while verifying all five repos, not by this repo's own tests -- worth checking whether a similar workflow-validation test would be a useful addition here too.
Integrates Rentometer API for actual market rent estimates by address/ZIP.
Replaces HUD FMR (40%+ off actual rents) as primary rent data source.

- New adapter: core/integrations/market/rentometer.py with 7-day cache
- Updated screening.py _get_monthly_rent() fallback chain:
  Rentometer → HUD FMR → None
- Added _extract_zip() helper for ZIP code extraction
- Added RENTO_METER_API_KEY to Django settings
- 9 unit tests + 5 E2E tests

Closes #359
Fix AttributeError on _PipelineView objects that lack address/city/state
attributes. Use getattr with None defaults for safe access.
address,
city,
state,
zip_code,
)
return cast(Decimal, result["median_rent"])
except RentometerError as exc:
logger.warning("Rentometer lookup failed for %s: %s", zip_code, exc)
@paruff
paruff merged commit 8849d12 into main Aug 21, 2026
18 of 20 checks passed
@paruff
paruff deleted the feat/data-rentometer branch August 21, 2026 07:44
paruff added a commit that referenced this pull request Aug 21, 2026
…377)

Tier 2 Governance's Trivy scan has been failing on every main push
(predates #376) on two HIGH CVEs neither traceable to app code:

- msgpack 1.1.2 (GHSA-6v7p-g79w-8964): not our dependency — it's pip's
  own vendored copy at pip/_vendor/msgpack.
- setuptools 70.3.0 (CVE-2025-47273): not installed anywhere — it's a
  version string in pip's _vendor/vendor.txt that Trivy's scanner
  parses as an installed package.

pip is never invoked at runtime (entrypoint.sh only runs manage.py/
gunicorn), so strip it from the final image after the existing
setuptools/wheel patch step. Also drop the now-pointless ensurepip
bundled wheel, and pin msgpack==1.2.1 directly (transitive via
pytest-bdd->gherkin-official, flagged separately).

Verified locally: rebuilt image, Trivy --severity HIGH,CRITICAL
--ignore-unfixed exits 0, and Django still boots with pip removed.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
paruff added a commit that referenced this pull request Aug 21, 2026
…hain (#378)

* fix(screening): address code-review findings on Rentometer fallback chain

Multi-agent review of #376 (Rentometer adapter) surfaced 9 issues,
fixed here:

- Rentometer's address-based lookup could never fire for HUD/USDA
  sources: _PipelineView (the adapter used for those sources) never
  carried address/city/state, so the whole feature silently degraded
  to HUD FMR for 2 of 4 property source types. _adapt_source_to_pipeline
  now populates them from the source model.

- The DB-write cache-on-read in _get_monthly_rent assumed
  pipeline_property always has a `.pk`, which _PipelineView (no
  persisted row) doesn't have — an AttributeError there was silently
  swallowed, discarding a successfully fetched rent. Added a
  _cache_rent() helper that only writes when a pk exists.

- That same DB write also silently violated screening_preview()'s
  documented "without saving" contract. screen_property() and the
  eval helpers now take a cache_rent flag; screening_preview() passes
  cache_rent=False.

- Both `except Exception: pass` blocks in _get_monthly_rent logged
  nothing, making a real bug (schema drift, broken import) look
  identical to "no data available" (AGENTS.md rule #10). Now logged,
  and RentometerClient._get() classifies timeouts/5xx/malformed JSON
  into RentometerError instead of leaking raw requests exceptions.

- _extract_zip's ZIP regex matched the first 5-digit token in an
  address, which could be a house number. Anchored to the end of the
  string instead.

- rentometer.py's hand-rolled, unbounded _rent_cache dict is replaced
  with django.core.cache (matching walkscore.py/schools.py in the same
  package), fixing both the multi-worker cache-miss problem and the
  memory-growth issue.

- _parse_rent no longer round-trips through binary float before
  producing a Decimal.

- Fixed a stale "future lookups" docstring on the bedrooms param (it's
  used today, for the cache key).

Added tests for the fallback chain (Rentometer success, Rentometer-
fails-then-HUD-succeeds, cache_rent=False, HUD/USDA _PipelineView
non-crash), the ZIP/house-number regex regression, _parse_rent edge
cases, and RentometerClient._get()'s error classification. Full
existing suite plus new tests: 913 passed (4 pre-existing, unrelated
ATTOM live-API failures — missing test credentials).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(screening): fix NameError and CodeQL PII findings from previous fix

CI on this branch caught three issues in the previous commit's fixes:

- _cache_rent() referenced PipelineProperty.objects.filter(...) but
  PipelineProperty is only imported under `if TYPE_CHECKING:` in this
  module — a NameError on every real invocation, silently swallowed by
  the (now-logged) except block. This bug predates this branch: the
  original inline code had the same gap, which is exactly why the
  previous commit's new fallback-chain tests (the first to actually
  exercise a successful rent lookup) caught it. Added a local
  `from core.models import PipelineProperty` inside _cache_rent, matching
  this file's existing lazy-import convention for model access.

- CodeQL flagged the new cache-key hash (MD5 over address/city/state/
  zip) and the new log statements (interpolating the raw zip_code) as
  PII handling issues. Switched the cache key to SHA-256, and swapped
  the logged zip_code for pipeline_property's pk (an opaque internal
  id, not location data).

Verified: the two previously-failing E2E tests now pass, full
rentometer/screening suite (61 tests) green, ruff/mypy clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(data): integrate Rentometer API for real rent comps by ZIP

2 participants