fix: batch geocoding (max 20/call) to prevent worker timeout, add action_geocode_all_pending
Some checks failed
pre-commit / pre-commit (push) Has been cancelled
tests / Detect unreleased dependencies (push) Has been cancelled
tests / test with OCB (push) Has been cancelled
tests / test with Odoo (push) Has been cancelled

This commit is contained in:
2026-03-14 02:44:41 +00:00
parent d242700b88
commit 5bcd8a5548

View File

@@ -1,4 +1,4 @@
from odoo import models, fields, api
from odoo import models, fields, api, _
from math import radians, sin, cos, sqrt, atan2
@@ -17,6 +17,8 @@ CATEGORY_MAP = {
'UNKNOWN': 'Unknown',
}
GEOCODE_BATCH_SIZE = 20
class WtLocationLog(models.Model):
_name = 'wt.location.log'
@@ -103,12 +105,25 @@ class WtLocationLog(models.Model):
return R * 2 * atan2(sqrt(a), sqrt(1 - a))
def action_geocode(self):
"""Resolve coordinates to address and category using OpenStreetMap Nominatim."""
"""Resolve coordinates to address using OpenStreetMap Nominatim.
Processes up to GEOCODE_BATCH_SIZE records per call to avoid HTTP
worker timeouts (Nominatim enforces a 1 req/sec rate limit).
Call repeatedly until the notification reports 0 remaining.
"""
import requests
import time
for rec in self:
if not rec.latitude or not rec.longitude:
continue
# From the selected/passed recordset, take only those needing geocoding
to_geocode = self.filtered(lambda r: r.latitude and r.longitude and not r.address)
if not to_geocode:
# If all selected already have addresses, allow re-geocoding but still batch
to_geocode = self.filtered(lambda r: r.latitude and r.longitude)
batch = to_geocode[:GEOCODE_BATCH_SIZE]
processed = 0
for rec in batch:
url = 'https://nominatim.openstreetmap.org/reverse'
params = {
'lat': rec.latitude,
@@ -124,8 +139,7 @@ class WtLocationLog(models.Model):
addr = result.get('address', {})
rec.address = result.get('display_name', '')
rec.place_name = (
rec.place_name = rec.place_name or (
result.get('name')
or addr.get('amenity')
or addr.get('shop')
@@ -135,7 +149,6 @@ class WtLocationLog(models.Model):
or ''
)
# Category: match OSM types to Google Timeline-style categories
osm_type = (
addr.get('amenity')
or addr.get('shop')
@@ -146,13 +159,58 @@ class WtLocationLog(models.Model):
or result.get('type', '')
or result.get('class', '')
)
rec.category = osm_type.replace('_', ' ').title() if osm_type else rec.category
if osm_type and not rec.category:
rec.category = osm_type.replace('_', ' ').title()
# Nominatim rate limit: 1 request per second
processed += 1
time.sleep(1)
except Exception:
pass
# Count all location logs still missing an address
remaining = self.search_count([
('latitude', '!=', 0),
('address', '=', False),
])
if remaining:
msg = _('%d address(es) resolved. %d still need geocoding — click "Geocode Next %d" again to continue.') % (
processed, remaining, GEOCODE_BATCH_SIZE)
notif_type = 'warning'
else:
msg = _('All done! %d address(es) resolved.') % processed
notif_type = 'success'
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Geocoding Batch Complete'),
'message': msg,
'type': notif_type,
'sticky': True,
},
}
def action_geocode_all_pending(self):
"""Action to geocode the next batch of ALL unresolved records (ignores selection)."""
pending = self.search([
('latitude', '!=', 0),
('address', '=', False),
], limit=GEOCODE_BATCH_SIZE)
if not pending:
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Nothing to Geocode'),
'message': _('All records with coordinates already have addresses.'),
'type': 'info',
'sticky': False,
},
}
return pending.action_geocode()
def action_compute_distances(self):
"""Recompute distances and travel times for all logs grouped by date."""
dates = self.mapped('date')
@@ -168,4 +226,4 @@ class WtLocationLog(models.Model):
if prev.departed_at and log.arrived_at:
delta = log.arrived_at - prev.departed_at
log.travel_time_from_previous = max(delta.total_seconds() / 3600, 0)
prev = log
prev = log