Spaces:
Running
Running
| """ | |
| fix_listing_6a0b82_geocode.py | |
| ============================= | |
| One-off data repair for listing 6a0b82f081458f7caa129dd8. | |
| Background: a vague neighborhood ("AGLA", a district of Cotonou, Benin) was | |
| geocoded — before the geocoder was hardened — to Општина Тител, Србија | |
| (lat=45.19, lon=20.09), and the currency was derived as RSD instead of XOF. | |
| This script re-runs the *fixed* geocoder for that listing's location and | |
| corrects latitude/longitude + currency. Idempotent — safe to run twice. | |
| Run from the AIDA/ directory: | |
| python scripts/fix_listing_6a0b82_geocode.py --dry-run # preview only | |
| python scripts/fix_listing_6a0b82_geocode.py # apply | |
| """ | |
| import asyncio | |
| import logging | |
| import os | |
| import sys | |
| sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | |
| from bson import ObjectId | |
| from app.database import connect_db, get_db, disconnect_db | |
| from app.ai.tools.listing_tool import geocode_address, get_currency_for_location | |
| logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s") | |
| logger = logging.getLogger(__name__) | |
| DRY_RUN = "--dry-run" in sys.argv | |
| LISTING_ID = "6a0b82f081458f7caa129dd8" | |
| # The bug report identifies this listing as being in Cotonou, Benin. | |
| # The stored `location` field is just "AGLA" (a Cotonou neighborhood) with | |
| # no city context, so the geocoder cannot infer the country on its own | |
| # (it would re-anchor on the AGLA literal, which resolves to Serbia and | |
| # to Bangladesh in the currency manager — that's how this script's first | |
| # run made things worse). For this one-off repair we override: | |
| KNOWN_CITY = "Cotonou, Benin" | |
| FALLBACK_CURRENCY = "XOF" | |
| # Last-resort coordinates if Nominatim is unreachable: Cotonou city center. | |
| FALLBACK_LAT = 6.3654 | |
| FALLBACK_LON = 2.4183 | |
| async def run(): | |
| await connect_db() | |
| try: | |
| db = await get_db() | |
| doc = await db.listings.find_one({"_id": ObjectId(LISTING_ID)}) | |
| if not doc: | |
| logger.error(f"Listing {LISTING_ID} not found — nothing to do.") | |
| return | |
| location = doc.get("location") or "" | |
| address = doc.get("address") or "" | |
| logger.info( | |
| f"Current → location={location!r} address={address!r} " | |
| f"lat={doc.get('latitude')} lon={doc.get('longitude')} " | |
| f"currency={doc.get('currency')}" | |
| ) | |
| # Re-geocode with the KNOWN city ("Cotonou, Benin"). Passing the | |
| # listing's own "location" field (just "AGLA") as the anchor is | |
| # what produced the Serbia/Bangladesh garbage on the first run. | |
| geo = await geocode_address(address or location, KNOWN_CITY) | |
| new_lat = geo.get("latitude") if geo.get("success") else None | |
| new_lon = geo.get("longitude") if geo.get("success") else None | |
| if new_lat is None or new_lon is None: | |
| logger.warning( | |
| "Re-geocode unsuccessful, falling back to Cotonou city center " | |
| f"({FALLBACK_LAT}, {FALLBACK_LON})." | |
| ) | |
| new_lat = FALLBACK_LAT | |
| new_lon = FALLBACK_LON | |
| # Currency: this listing is known to be Benin → XOF. We deliberately | |
| # skip get_currency_for_location() here because feeding it bare | |
| # "AGLA" returns BDT (Bangladesh) with high confidence — exactly | |
| # the bug we are trying to repair. | |
| new_currency = FALLBACK_CURRENCY | |
| logger.info( | |
| f"Proposed → lat={new_lat} lon={new_lon} currency={new_currency} " | |
| f"(approx={geo.get('approximate', False)})" | |
| ) | |
| if DRY_RUN: | |
| logger.info("DRY RUN — no changes written.") | |
| return | |
| result = await db.listings.update_one( | |
| {"_id": ObjectId(LISTING_ID)}, | |
| {"$set": { | |
| "latitude": new_lat, | |
| "longitude": new_lon, | |
| "currency": new_currency, | |
| }}, | |
| ) | |
| logger.info( | |
| f"✅ Updated listing {LISTING_ID} " | |
| f"(matched={result.matched_count}, modified={result.modified_count})" | |
| ) | |
| finally: | |
| await disconnect_db() | |
| if __name__ == "__main__": | |
| asyncio.run(run()) | |