Skip to content

Scope known_external_ids per-user in pipeline (bd-m6g) - #64

Merged
MukhammadIbrokhimov merged 1 commit into
mainfrom
fix/pipeline-known-ids-per-user
May 19, 2026
Merged

Scope known_external_ids per-user in pipeline (bd-m6g)#64
MukhammadIbrokhimov merged 1 commit into
mainfrom
fix/pipeline-known-ids-per-user

Conversation

@MukhammadIbrokhimov

Copy link
Copy Markdown
Owner

Summary

The scrape pass used to derive known_external_ids from every row in flats, regardless of who had actually had a matcher decision on each flat. That's fine in single-user mode, but on Phase 5 multi-user, user A's scrape can mark a flat as "seen" via INSERT OR IGNORE and user B's paginating scraper terminates early on flats user B has never been evaluated against.

This PR scopes the seen set to (platform, user_id):

  • run_scrape_pass now joins flats against matches on (flat_id, user_id) so only flats the current user has personally had a decision on are considered "known."
  • A brand-new user starts with an empty set; flats scraped by others still propagate through the full fetch/match path under the new user's profile.
  • user_id threads through from run_pipeline_oncerun_pipeline_scraperun_scrape_pass as a kw-only arg defaulting to DEFAULT_USER_ID, so single-user CLI callers don't change.

Indexes

The auto-index for matches' UNIQUE(user_id, flat_id, profile_version_hash, decision) covers the leftmost (user_id, flat_id) prefix the join needs, and flats.id is the primary key. No new index required at current expected volumes; we can add matches(user_id, flat_id) later if profiling shows pressure.

Test plan

  • pytest tests/test_pipeline_known_ids.py — 2/2 pass.
  • Full suite: pytest — 566/566 pass, coverage 80%.
  • ruff check on touched files — clean.
  • Existing test updated to seed matches alongside flats for DEFAULT_USER_ID and assert the same per-platform sets it used to.
  • New test seeds a flat that user 1 has decided on and a different flat that user 2 has decided on, runs run_scrape_pass(user_id=2), asserts user 2 sees only their own flat in known_external_ids and not user 1's.

The pipeline used to derive each scraper's known_external_ids from
every row in flats, regardless of which user had actually had a
matcher decision on them. Single-user mode tolerates this; Phase 5
multi-user does not — user A's scrape would mark a flat as "seen"
and user B's paginating scraper could terminate early on flats user
B has never been evaluated against.

Change run_scrape_pass to filter the seen set via INNER JOIN matches
on (flat_id, user_id). A brand-new user starts with an empty set so
flats scraped by others still propagate through the full
fetch/match path. user_id threads through from run_pipeline_once →
run_pipeline_scrape → run_scrape_pass (kw-only, defaults to
DEFAULT_USER_ID so single-user callers don't change).

Index check: the auto-index for matches' UNIQUE(user_id, flat_id,
profile_version_hash, decision) covers the (user_id, flat_id) prefix
the join needs, and flats.id is the primary key — no new index
required for current expected volumes.
Copilot AI review requested due to automatic review settings May 19, 2026 10:07

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a multi-user correctness issue in the scraping pipeline where a flat could be treated as “already seen” for the wrong user, causing paginating scrapers to terminate early. It scopes known_external_ids to the current (platform, user_id) by loading “seen” IDs from matches rather than all rows in flats.

Changes:

  • Thread user_id through run_pipeline_oncerun_pipeline_scraperun_scrape_pass (kw-only, defaulting to DEFAULT_USER_ID).
  • Build per-platform known_external_ids by joining flats to matches and filtering by m.user_id.
  • Update/add tests to validate both the single-user default behavior and cross-user isolation.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/flatpilot/pipeline.py Passes user_id through scrape orchestration and scopes known_external_ids via matches.user_id.
tests/test_pipeline_known_ids.py Refactors seeding helpers and adds a new test proving other users’ decisions don’t leak into known_external_ids.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/flatpilot/pipeline.py
Comment on lines +166 to +176
# bd-m6g: scope "seen" to flats this user has already had a match
# decision on. A brand-new user starts with an empty set so flats
# other users scraped still get evaluated under this user's profile.
# Paginating scrapers may walk more pages on a new user's first
# pass — accepted trade-off for multi-user correctness.
known_external_ids = frozenset(
row[0]
for row in conn.execute(
"SELECT external_id FROM flats WHERE platform = ?",
(plat,),
"SELECT f.external_id FROM flats f "
"INNER JOIN matches m ON m.flat_id = f.id "
"WHERE f.platform = ? AND m.user_id = ?",
Comment thread src/flatpilot/pipeline.py
Comment on lines 171 to 178
known_external_ids = frozenset(
row[0]
for row in conn.execute(
"SELECT external_id FROM flats WHERE platform = ?",
(plat,),
"SELECT f.external_id FROM flats f "
"INNER JOIN matches m ON m.flat_id = f.id "
"WHERE f.platform = ? AND m.user_id = ?",
(plat, user_id),
)
@MukhammadIbrokhimov
MukhammadIbrokhimov merged commit 1b90d67 into main May 19, 2026
7 checks passed
@MukhammadIbrokhimov
MukhammadIbrokhimov deleted the fix/pipeline-known-ids-per-user branch May 19, 2026 11:30
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.

2 participants