Skip to content

fix: exclude room-less Property from every price bound - #15

Merged
kernvex merged 1 commit into
mainfrom
fix/10-price-from-min-floor
Jul 21, 2026
Merged

fix: exclude room-less Property from every price bound#15
kernvex merged 1 commit into
mainfrom
fix/10-price-from-min-floor

Conversation

@kernvex

@kernvex kernvex commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Closes #10.

The bug

filterHotels (src/domain/search.ts) guarded only the max_price side of the band. A room-less Property has price_from = Infinity, and Infinity < minPrice is false, so such a Property passed a min_price floor — e.g. GET /hotels?min_price=1000 would surface a Property whose price_from is null.

Root cause

Confirmed with a red test first: the asymmetric guard. Infinity > maxPrice already excluded room-less Properties from the ceiling, but nothing excluded them from the floor.

Fix

A Property with no bookable rate has no price and should satisfy neither bound. Guard the non-finite price_from up front so it fails both sides symmetrically.

Documentation

  • ADR-0003 (consequences) and ASSUMPTIONS.md now record the room-less ruling.

Verification

  • Red → green: extended the room-less case in test/search.test.ts to cover the min_price floor and a full band (the existing max_price ceiling assertion stays).
  • npm run verify (typecheck → lint → test): clean, 54/54 pass.

Impact

Defensive-path only — the seed has 0 room-less Properties (40/40 have Rooms), so there is no observable change today.

🤖 Generated with Claude Code

Close #10. `filterHotels` guarded only the max_price side of the band.
A room-less Property has `price_from = Infinity`, and `Infinity < minPrice`
is `false`, so such a Property wrongly *passed* a `min_price` floor — a
null-priced hotel surfacing inside a price-bounded result.

Root cause (confirmed by a red test): the asymmetric guard. A Property with
no bookable rate has no price and should satisfy neither bound. Guard the
non-finite `price_from` up front so it fails both the floor and the ceiling
symmetrically.

- Regression test: extend the room-less case in search.test.ts to cover the
  min_price floor and a full band, alongside the existing max_price ceiling.
- Document the ruling in ADR-0003 (consequences) and ASSUMPTIONS.md.

The seed has 0 room-less Properties (40/40 have Rooms), so this is a
defensive-path correctness fix with no observable change today.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kernvex

kernvex commented Jul 21, 2026

Copy link
Copy Markdown
Owner Author

Post-mortem — what would have caught this earlier

The original GET /hotels work (PR #9) added a room-less test that asserted only the max_price side (filterHotels([roomless], { maxPrice: 1000 }) === []). Because Infinity > maxPrice is true, that path happened to be correct — which masked the asymmetric guard. A symmetric test that also exercised the min_price floor for the same room-less fixture would have gone red at the time and caught this on PR #9.

The regression test in this PR closes that gap: the room-less case now covers the floor, the ceiling, and a full band together, so the two sides can't drift apart again. No architectural change needed — the seam was fine; the test coverage was one-sided.

@kernvex
kernvex merged commit 7b4e66d into main Jul 21, 2026
2 checks passed
@kernvex
kernvex deleted the fix/10-price-from-min-floor branch July 21, 2026 18:59
@kernvex kernvex mentioned this pull request Jul 21, 2026
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.

price_from filter: room-less Property passes min_price floor

1 participant