Skip to content

Commit 3a9617a

Browse files
MilesMorelclaude
andcommitted
Filter placeholder records from RescueGroups (e.g. "More Dogs Soon!")
Some rescues publish placeholder entries pointing users at their website rather than a real adoptable animal. These were getting picked up and posted to social. Skip records whose name matches an entry in adoption_sources/rescue_groups_blocklist.json (case-insensitive). Closes #94 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 6fe0326 commit 3a9617a

3 files changed

Lines changed: 37 additions & 2 deletions

File tree

adoption_sources/rescue_groups.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@
1818

1919
logger = logging.getLogger(__name__)
2020

21+
_blocklist_path = __file__.replace(".py", "_blocklist.json")
22+
with open(_blocklist_path) as _f:
23+
_blocklist = json.loads(_f.read())
24+
25+
# Substrings (case-insensitive) that mark a record as a placeholder rather than
26+
# an actual adoptable pet. Some rescues publish entries like "More Dogs Soon!"
27+
# to point users at their website; those should never be posted.
28+
PLACEHOLDER_NAME_SUBSTRINGS: tuple[str, ...] = tuple(s.lower() for s in _blocklist["name_substrings"])
29+
2130

2231
class SourceRescueGroups(PetSource):
2332
"""
@@ -104,8 +113,12 @@ def fetch_pets(self) -> Iterator[AdoptablePet]:
104113

105114
for animal in data:
106115
pet = self._parse_animal(animal, orgs_by_id)
107-
if pet:
108-
yield pet
116+
if not pet:
117+
continue
118+
if self._is_placeholder_name(pet.name):
119+
logger.info(f"Skipping placeholder record: {pet.name!r}")
120+
continue
121+
yield pet
109122

110123
def _parse_animal(self, animal: dict, orgs_by_id: dict) -> AdoptablePet | None:
111124
"""Parse a single animal record from the API response."""
@@ -163,6 +176,10 @@ def _parse_animal(self, animal: dict, orgs_by_id: dict) -> AdoptablePet | None:
163176
logger.warning(f"Failed to parse animal {animal.get('id', 'unknown')}: {e}")
164177
return None
165178

179+
def _is_placeholder_name(self, name: str) -> bool:
180+
lowered = (name or "").lower()
181+
return any(needle in lowered for needle in PLACEHOLDER_NAME_SUBSTRINGS)
182+
166183
def _clean_name(self, name: str) -> str:
167184
"""
168185
Clean up pet name by removing promotional text.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name_substrings": [
3+
"more dogs soon"
4+
]
5+
}

tests/test_rescue_groups.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,18 @@ def test_falls_back_to_org_url_when_neither_pet_nor_org_has_adoption_url(self):
5858
self.assertEqual(pet.adoption_url, "https://org.example.com")
5959

6060

61+
class PlaceholderNameTests(unittest.TestCase):
62+
def setUp(self):
63+
self.source = SourceRescueGroups(api_key="dummy")
64+
65+
def test_more_dogs_soon_is_placeholder(self):
66+
self.assertTrue(self.source._is_placeholder_name("More Dogs Soon!"))
67+
self.assertTrue(self.source._is_placeholder_name("more dogs soon"))
68+
69+
def test_real_pet_name_is_not_placeholder(self):
70+
self.assertFalse(self.source._is_placeholder_name("Pippin"))
71+
self.assertFalse(self.source._is_placeholder_name("Buddy"))
72+
73+
6174
if __name__ == "__main__":
6275
unittest.main()

0 commit comments

Comments
 (0)