"""Behavioural tests for the place deriver. A frequent place is a spatial cluster of photo metadata (and similar location-bearing evidence) visited repeatedly over time. """ from __future__ import annotations from datetime import timedelta from mnema.derive.derivers.places import PlaceDeriver # A fixed anchor in San Francisco; jitter of ~0.0002 degrees is ~20 metres. LAT, LON = 37.7764, -122.4242 JITTER = [ (0.0000, 0.0000), (0.0001, -0.0001), (-0.0002, 0.0001), (0.0002, 0.0002), (-0.0001, -0.0002), (0.0001, 0.0001), (-0.0002, -0.0001), (0.0002, -0.0002), (0.0000, 0.0002), (-0.0001, 0.0001), (0.0001, -0.0002), (0.0002, 0.0000), ] def _cluster_photos(kit, count, place_name=None): photos = [] for i in range(count): dlat, dlon = JITTER[i % len(JITTER)] photos.append( kit.make_photo( kit.T0 + timedelta(days=2 * i, hours=i % 5), LAT + dlat, LON + dlon, place_name=place_name, ) ) return photos def _places_run(kit, evidence): engine = kit.new_engine([PlaceDeriver()]) claims = kit.run_claims(engine, evidence) return engine, kit.by_predicate(claims, "place") def test_repeated_photo_location_yields_place_claim(kit): photos = _cluster_photos(kit, 8, place_name="Mission Coffee Club") _, places = _places_run(kit, photos) assert places, "eight photos within ~30m over three weeks must yield a place claim" claim = kit.best(places) assert 0.0 < claim.confidence <= 1.0 photo_ids = {p.evidence_id for p in photos} cited = set(claim.inputs) assert cited and cited <= photo_ids assert len(cited) >= 4, "a frequent place should cite several visits" def test_place_name_propagates_into_claim(kit): photos = _cluster_photos(kit, 8, place_name="Mission Coffee Club") _, places = _places_run(kit, photos) claim = kit.best(places) assert "mission coffee" in kit.vdump(claim), ( "when evidence carries a place name, the derived claim should surface it" ) def test_scattered_photos_yield_no_place(kit): spots = [ (51.5072, -0.1276), # London (35.6764, 139.6500), # Tokyo (-33.8688, 151.2093), # Sydney (40.7128, -74.0060), # New York (55.7558, 37.6173), # Moscow (-23.5505, -46.6333), # Sao Paulo ] photos = [ kit.make_photo(kit.T0 + timedelta(days=i * 3), lat, lon) for i, (lat, lon) in enumerate(spots) ] _, places = _places_run(kit, photos) assert places == [], "one photo per location worldwide must not yield a frequent place" def test_more_visits_do_not_lower_confidence(kit): _, few = _places_run(kit, _cluster_photos(kit, 4)) _, many = _places_run(kit, _cluster_photos(kit, 12)) assert few and many c_few = kit.best(few).confidence c_many = kit.best(many).confidence assert c_many >= c_few, ( f"confidence must be monotone in visit count: {c_many} (12) < {c_few} (4)" ) def test_place_claim_is_explainable(kit): photos = _cluster_photos(kit, 8, place_name="Mission Coffee Club") engine, places = _places_run(kit, photos) claim = kit.best(places) text = kit.explanation_text(engine, claim) assert text, "every claim must have a retrievable explanation"