Filter egg harvest events to only include adult female ducks
Males, juveniles, and other non-laying animals were incorrectly being associated with egg collection events. Added life_stage='adult' and sex='female' filters to resolve_ducks_at_location() query. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -55,7 +55,9 @@ ar = APIRouter()
|
||||
|
||||
|
||||
def resolve_ducks_at_location(db: Any, location_id: str, ts_utc: int) -> list[str]:
|
||||
"""Resolve all duck animal IDs at a location at given timestamp.
|
||||
"""Resolve layer-eligible duck IDs at a location at given timestamp.
|
||||
|
||||
Only includes adult female ducks that can lay eggs.
|
||||
|
||||
Args:
|
||||
db: Database connection.
|
||||
@@ -63,7 +65,7 @@ def resolve_ducks_at_location(db: Any, location_id: str, ts_utc: int) -> list[st
|
||||
ts_utc: Timestamp in ms since Unix epoch.
|
||||
|
||||
Returns:
|
||||
List of animal IDs (ducks at the location, alive at ts_utc).
|
||||
List of animal IDs (adult female ducks at the location, alive at ts_utc).
|
||||
"""
|
||||
query = """
|
||||
SELECT DISTINCT ali.animal_id
|
||||
@@ -74,6 +76,8 @@ def resolve_ducks_at_location(db: Any, location_id: str, ts_utc: int) -> list[st
|
||||
AND (ali.end_utc IS NULL OR ali.end_utc > ?)
|
||||
AND ar.species_code = 'duck'
|
||||
AND ar.status = 'alive'
|
||||
AND ar.life_stage = 'adult'
|
||||
AND ar.sex = 'female'
|
||||
ORDER BY ali.animal_id
|
||||
"""
|
||||
rows = db.execute(query, (location_id, ts_utc, ts_utc)).fetchall()
|
||||
|
||||
@@ -278,3 +278,90 @@ class TestEggsRecentEvents:
|
||||
|
||||
# The response should contain a link to the event detail
|
||||
assert f"/events/{event_id}" in resp.text
|
||||
|
||||
|
||||
class TestEggCollectionAnimalFiltering:
|
||||
"""Tests that egg collection only associates adult females."""
|
||||
|
||||
def test_egg_collection_excludes_males_and_juveniles(
|
||||
self, client, seeded_db, location_strip1_id
|
||||
):
|
||||
"""Egg collection only associates adult female ducks, not males or juveniles."""
|
||||
# Setup: Create mixed animals at location
|
||||
event_store = EventStore(seeded_db)
|
||||
registry = ProjectionRegistry()
|
||||
registry.register(AnimalRegistryProjection(seeded_db))
|
||||
registry.register(EventAnimalsProjection(seeded_db))
|
||||
registry.register(IntervalProjection(seeded_db))
|
||||
registry.register(ProductsProjection(seeded_db))
|
||||
|
||||
animal_service = AnimalService(seeded_db, event_store, registry)
|
||||
ts_utc = int(time.time() * 1000)
|
||||
|
||||
# Create adult female (should be included)
|
||||
female_payload = AnimalCohortCreatedPayload(
|
||||
species="duck",
|
||||
count=1,
|
||||
life_stage="adult",
|
||||
sex="female",
|
||||
location_id=location_strip1_id,
|
||||
origin="purchased",
|
||||
)
|
||||
female_event = animal_service.create_cohort(female_payload, ts_utc, "test_user")
|
||||
female_id = female_event.entity_refs["animal_ids"][0]
|
||||
|
||||
# Create adult male (should be excluded)
|
||||
male_payload = AnimalCohortCreatedPayload(
|
||||
species="duck",
|
||||
count=1,
|
||||
life_stage="adult",
|
||||
sex="male",
|
||||
location_id=location_strip1_id,
|
||||
origin="purchased",
|
||||
)
|
||||
male_event = animal_service.create_cohort(male_payload, ts_utc, "test_user")
|
||||
male_id = male_event.entity_refs["animal_ids"][0]
|
||||
|
||||
# Create juvenile female (should be excluded)
|
||||
juvenile_payload = AnimalCohortCreatedPayload(
|
||||
species="duck",
|
||||
count=1,
|
||||
life_stage="juvenile",
|
||||
sex="female",
|
||||
location_id=location_strip1_id,
|
||||
origin="purchased",
|
||||
)
|
||||
juvenile_event = animal_service.create_cohort(juvenile_payload, ts_utc, "test_user")
|
||||
juvenile_id = juvenile_event.entity_refs["animal_ids"][0]
|
||||
|
||||
# Collect eggs
|
||||
resp = client.post(
|
||||
"/actions/product-collected",
|
||||
data={
|
||||
"location_id": location_strip1_id,
|
||||
"quantity": "6",
|
||||
"nonce": "test-nonce-filter",
|
||||
},
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
|
||||
# Get the egg collection event
|
||||
event_row = seeded_db.execute(
|
||||
"SELECT id FROM events WHERE type = 'ProductCollected' ORDER BY id DESC LIMIT 1"
|
||||
).fetchone()
|
||||
event_id = event_row[0]
|
||||
|
||||
# Check which animals are associated with the event
|
||||
animal_rows = seeded_db.execute(
|
||||
"SELECT animal_id FROM event_animals WHERE event_id = ?",
|
||||
(event_id,),
|
||||
).fetchall()
|
||||
associated_ids = {row[0] for row in animal_rows}
|
||||
|
||||
# Only the adult female should be associated
|
||||
assert female_id in associated_ids, "Adult female should be associated with egg collection"
|
||||
assert male_id not in associated_ids, "Male should NOT be associated with egg collection"
|
||||
assert juvenile_id not in associated_ids, (
|
||||
"Juvenile should NOT be associated with egg collection"
|
||||
)
|
||||
assert len(associated_ids) == 1, "Only adult females should be associated"
|
||||
|
||||
Reference in New Issue
Block a user