Skip to content

Fix saved list CID parsing#49

Merged
michaelmwu merged 1 commit into
mainfrom
michaelmwu/fix-s2-cell-cids
Jul 7, 2026
Merged

Fix saved list CID parsing#49
michaelmwu merged 1 commit into
mainfrom
michaelmwu/fix-s2-cell-cids

Conversation

@michaelmwu

@michaelmwu michaelmwu commented Jul 7, 2026

Copy link
Copy Markdown
Member

Summary

Fix saved-list place CID extraction so the parser prefers the fingerprint in the structured metadata slot instead of returning the positive S2 cell ID. The fallback path is now narrower so timestamp-like values and owner profile IDs are not treated as place CIDs.

Validation

  • uv run python -m unittest tests.test_parser
  • ./scripts/lint.sh
  • ./scripts/typecheck.sh
  • git diff --check

Note

Medium Risk
CID values drive deduplication and identity for scraped places; wrong CIDs would mis-link or drop entries, though the change is localized to parser heuristics with expanded unit tests.

Overview
Fixes place CID extraction for Google Maps saved lists so Place.cid reflects the business fingerprint, not the positive S2 cell ID in structured metadata slot 6.

When slot 6 is a list with two or more long integer strings, the parser now always uses the second value (dropping the old “first non-negative” shortcut). Negative fingerprints are still normalized via unsigned 64-bit conversion. Fallback scanning is split from structured slot parsing: it only accepts a single long integer per value, requires at least 15 digits (excluding sign), and no longer treats ambiguous multi-number lists or bare timestamp-like integers as CIDs.

Tests were updated so fixtures model cell ID + fingerprint pairs, expectations use the fingerprint constants, and new cases cover slot-6 preference, negative normalization, and ignoring timestamp fallbacks when slot 6 is empty.

Reviewed by Cursor Bugbot for commit cad5629. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • Bug Fixes

    • Improved place ID extraction so the app more reliably identifies entries from different result formats.
    • Better handles edge cases where numeric values could be mistaken for an ID, reducing incorrect matches.
    • Added protections against timestamp-like or owner-like values being treated as valid IDs.
  • Tests

    • Expanded regression coverage for ID parsing and deduplication scenarios, including tricky fingerprint and fallback cases.

@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_ffd843cf-7753-4c2f-aff6-db0e0a8dac0d)

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR refactors CID extraction in the Google Maps scraper parser by splitting a single helper into structured-slot and fallback-value parsing paths with new normalization and rejection rules. Tests are updated with shared cell ID/CID constants and new regression cases covering slot-6 fingerprint and fallback edge cases.

Changes

CID Extraction Refactor

Layer / File(s) Summary
Dispatch and structured slot parsing
src/gmaps_scraper/parser.py
_find_cid routes to _find_cid_in_structured_slot for slot 6 and _find_cid_in_fallback_value for other values; structured-slot logic now normalizes the second numeric token when multiple matches exist.
Fallback CID parsing and normalization
src/gmaps_scraper/parser.py
New _find_cid_in_fallback_value and _normalize_fallback_cid_token require exactly one long-integer token, reject owner-like list payloads, and enforce a minimum length before normalizing.
Shared test fixtures
tests/test_parser.py
Adds _NORTHWIND_CELL_ID/_NORTHWIND_CID/_HARBOR_CELL_ID/_HARBOR_CID constants and updates _LIST_NODE fixture fields to use them instead of literals.
Assertions and regression tests
tests/test_parser.py
Updates existing assertions to use the new constants and adds tests for non-positive/negative slot-6 fingerprints and timestamp-like fallback values.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • 508-dev/gmaps-scraper#37: Both PRs change place CID extraction in src/gmaps_scraper/parser.py, adding guardrails against deriving CIDs from incorrect list/owner-like payloads and updating related CID tests.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: fixing saved list CID parsing.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch michaelmwu/fix-s2-cell-cids

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@michaelmwu
michaelmwu merged commit dfa440f into main Jul 7, 2026
8 of 9 checks passed
@michaelmwu
michaelmwu deleted the michaelmwu/fix-s2-cell-cids branch July 7, 2026 08:33

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/gmaps_scraper/parser.py (2)

640-660: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Duplicate owner-rejection and numeric-extraction logic across both helpers.

The owner-rejection block (647-649) is identical to (668-670), and the numeric-token extraction comprehension (651-655) is identical to (671-675). Extracting these into shared helpers would remove the duplication and keep the two selection-rule differences (index-1 vs. single-token) as the only distinguishing logic.

♻️ Proposed extraction
+def _is_rejected_owner_list(value: list[JSONValue]) -> bool:
+    owner = _parse_list_owner(value)
+    return owner is not None and (owner.photo_url is not None or owner.profile_id is not None)
+
+
+def _extract_long_integer_texts(value: list[JSONValue]) -> list[str]:
+    return [
+        text
+        for text in (_clean_text(item) for item in value)
+        if text is not None and _LONG_INTEGER_PATTERN.fullmatch(text) is not None
+    ]
+
+
 def _find_cid_in_structured_slot(value: JSONValue | None) -> str | None:
     ...
     if not isinstance(value, list):
         return None
-    owner = _parse_list_owner(value)
-    if owner is not None and (owner.photo_url is not None or owner.profile_id is not None):
+    if _is_rejected_owner_list(value):
         return None

-    numeric_texts = [
-        text
-        for text in (_clean_text(item) for item in value)
-        if text is not None and _LONG_INTEGER_PATTERN.fullmatch(text) is not None
-    ]
+    numeric_texts = _extract_long_integer_texts(value)
     ...

Apply the same substitution in _find_cid_in_fallback_value.

Also applies to: 663-678

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/gmaps_scraper/parser.py` around lines 640 - 660, The CID parsing logic in
_find_cid_in_structured_slot and _find_cid_in_fallback_value duplicates the same
owner-rejection check and numeric-token extraction, so factor those shared steps
into helper functions and reuse them in both paths. Keep the existing selection
differences isolated to each caller (index-1 vs single-token behavior), and make
sure the shared helpers cover the _parse_list_owner, _clean_text, and
_LONG_INTEGER_PATTERN-based filtering consistently.

640-660: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject short numeric CID candidates in the structured-slot path. _find_cid_in_structured_slot still normalizes bare ints/strings and single-element lists with _normalize_cid_token, so 10–14 digit timestamp-like values can slip through even though the fallback path now requires 15+ digits. Reuse the length-gated normalizer here as well, or share a single candidate check.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/gmaps_scraper/parser.py` around lines 640 - 660, The structured-slot CID
parser still accepts bare ints/strings and single numeric list values without
the same length gate as the fallback path, allowing short timestamp-like numbers
to be normalized. Update _find_cid_in_structured_slot to apply the same
minimum-length CID validation used elsewhere, or centralize the candidate check
so _normalize_cid_token is only called for valid 15+ digit CID candidates,
including the numeric_texts selection logic.

Source: Coding guidelines

🧹 Nitpick comments (2)
tests/test_parser.py (1)

343-343: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse _NORTHWIND_CELL_ID instead of the duplicated literal.

"7451636382641713350" is the same value as _NORTHWIND_CELL_ID defined at Line 18. Since this PR's whole point is consolidating these magic numbers into shared constants, this line should use the constant for consistency.

♻️ Proposed fix
-        second_metadata[6] = ["7451636382641713350", _NORTHWIND_CID]
+        second_metadata[6] = [_NORTHWIND_CELL_ID, _NORTHWIND_CID]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_parser.py` at line 343, Replace the duplicated Northwind cell ID
literal in the test data with the shared _NORTHWIND_CELL_ID constant so the
parser tests consistently use the centralized magic-number definition. Update
the assignment in the test case that builds second_metadata to reference
_NORTHWIND_CELL_ID alongside _NORTHWIND_CID, keeping the existing test behavior
unchanged while aligning with the constant-based pattern used elsewhere in
tests/test_parser.py.
src/gmaps_scraper/parser.py (1)

658-659: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Hardcoded numeric_texts[1] assumes a fixed two-element ordering.

When 2+ numeric tokens exist, the code unconditionally takes index 1 as the fingerprint. This matches the observed [cellId, fingerprint] fixtures, but if a third numeric token ever appears (schema drift), index 1 may not be the fingerprint. Given the guideline to keep extraction defensive against schema drift, consider a more resilient selection (e.g., picking the largest-magnitude token, or the one that differs in sign from a plausible S2 cell ID) rather than a fixed position.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/gmaps_scraper/parser.py` around lines 658 - 659, The fingerprint
extraction in the parser currently assumes a fixed ordering by returning
numeric_texts[1] when multiple numeric tokens are present, which is brittle if
the token layout changes. Update the logic in the numeric token handling path to
select the fingerprint more defensively instead of relying on the second
element, using a heuristic such as a magnitude- or cell-id-aware choice while
keeping _normalize_cid_token as the final normalization step.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_parser.py`:
- Around line 270-280: The test in
test_ignores_timestamp_like_fallback_values_when_cid_is_missing only checks the
final parsed cid, so it can still pass if _find_cid stops scanning fallback
values. Update this test to assert the fallback scan itself by verifying that
the appended timestamp-like value is actually handled through
_find_cid_in_fallback_value when parse_saved_list_artifacts runs. Use the
existing helpers and symbols (_find_cid, _find_cid_in_fallback_value,
parse_saved_list_artifacts, and place_metadata) so the test fails if fallback
scanning regresses.

---

Outside diff comments:
In `@src/gmaps_scraper/parser.py`:
- Around line 640-660: The CID parsing logic in _find_cid_in_structured_slot and
_find_cid_in_fallback_value duplicates the same owner-rejection check and
numeric-token extraction, so factor those shared steps into helper functions and
reuse them in both paths. Keep the existing selection differences isolated to
each caller (index-1 vs single-token behavior), and make sure the shared helpers
cover the _parse_list_owner, _clean_text, and _LONG_INTEGER_PATTERN-based
filtering consistently.
- Around line 640-660: The structured-slot CID parser still accepts bare
ints/strings and single numeric list values without the same length gate as the
fallback path, allowing short timestamp-like numbers to be normalized. Update
_find_cid_in_structured_slot to apply the same minimum-length CID validation
used elsewhere, or centralize the candidate check so _normalize_cid_token is
only called for valid 15+ digit CID candidates, including the numeric_texts
selection logic.

---

Nitpick comments:
In `@src/gmaps_scraper/parser.py`:
- Around line 658-659: The fingerprint extraction in the parser currently
assumes a fixed ordering by returning numeric_texts[1] when multiple numeric
tokens are present, which is brittle if the token layout changes. Update the
logic in the numeric token handling path to select the fingerprint more
defensively instead of relying on the second element, using a heuristic such as
a magnitude- or cell-id-aware choice while keeping _normalize_cid_token as the
final normalization step.

In `@tests/test_parser.py`:
- Line 343: Replace the duplicated Northwind cell ID literal in the test data
with the shared _NORTHWIND_CELL_ID constant so the parser tests consistently use
the centralized magic-number definition. Update the assignment in the test case
that builds second_metadata to reference _NORTHWIND_CELL_ID alongside
_NORTHWIND_CID, keeping the existing test behavior unchanged while aligning with
the constant-based pattern used elsewhere in tests/test_parser.py.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fb7a3efb-0f45-44b9-a8e3-7b6bfafb3049

📥 Commits

Reviewing files that changed from the base of the PR and between 86bc7c8 and cad5629.

📒 Files selected for processing (2)
  • src/gmaps_scraper/parser.py
  • tests/test_parser.py

Comment thread tests/test_parser.py
Comment on lines +270 to +280
def test_ignores_timestamp_like_fallback_values_when_cid_is_missing(self) -> None:
runtime_state = copy.deepcopy(["noise", _LIST_NODE])
place_metadata = runtime_state[1][8][0][1]
assert isinstance(place_metadata, list)
place_metadata[6] = [None]
place_metadata.append(1776063335)

parsed = parse_saved_list_artifacts(_LIST_URL, runtime_state=runtime_state)

self.assertEqual(parsed.places[0].cid, None)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect the _find_cid dispatch function to see what value is passed to
# _find_cid_in_fallback_value, and confirm place_metadata's appended item
# is part of that scanned value.
rg -n -B5 -A30 'def _find_cid\(' src/gmaps_scraper/parser.py
rg -n '_LONG_INTEGER_PATTERN\s*=' src/gmaps_scraper/parser.py

Repository: 508-dev/gmaps-scraper

Length of output: 1425


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the helper chain around fallback CID detection and the test context.
sed -n '520,700p' src/gmaps_scraper/parser.py
printf '\n--- TEST CONTEXT ---\n'
sed -n '230,310p' tests/test_parser.py

Repository: 508-dev/gmaps-scraper

Length of output: 8821


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the exact fallback token normalization logic in isolation.
rg -n -A20 -B10 'def _find_cid_in_fallback_value|def _normalize_fallback_cid_token|def _find_cid\(' src/gmaps_scraper/parser.py

Repository: 508-dev/gmaps-scraper

Length of output: 3115


Assert the fallback scan, not just the final None. This still passes if _find_cid stops iterating fallback values entirely; add an assertion that the appended timestamp-like value is actually consumed by _find_cid_in_fallback_value so the regression test breaks when fallback scanning regresses.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_parser.py` around lines 270 - 280, The test in
test_ignores_timestamp_like_fallback_values_when_cid_is_missing only checks the
final parsed cid, so it can still pass if _find_cid stops scanning fallback
values. Update this test to assert the fallback scan itself by verifying that
the appended timestamp-like value is actually handled through
_find_cid_in_fallback_value when parse_saved_list_artifacts runs. Use the
existing helpers and symbols (_find_cid, _find_cid_in_fallback_value,
parse_saved_list_artifacts, and place_metadata) so the test fails if fallback
scanning regresses.

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.

1 participant