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 from math import radians, sin, cos, sqrt, atan2
@@ -17,6 +17,8 @@ CATEGORY_MAP = {
'UNKNOWN': 'Unknown', 'UNKNOWN': 'Unknown',
} }
GEOCODE_BATCH_SIZE = 20
class WtLocationLog(models.Model): class WtLocationLog(models.Model):
_name = 'wt.location.log' _name = 'wt.location.log'
@@ -103,12 +105,25 @@ class WtLocationLog(models.Model):
return R * 2 * atan2(sqrt(a), sqrt(1 - a)) return R * 2 * atan2(sqrt(a), sqrt(1 - a))
def action_geocode(self): 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 requests
import time import time
for rec in self:
if not rec.latitude or not rec.longitude: # From the selected/passed recordset, take only those needing geocoding
continue 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' url = 'https://nominatim.openstreetmap.org/reverse'
params = { params = {
'lat': rec.latitude, 'lat': rec.latitude,
@@ -124,8 +139,7 @@ class WtLocationLog(models.Model):
addr = result.get('address', {}) addr = result.get('address', {})
rec.address = result.get('display_name', '') rec.address = result.get('display_name', '')
rec.place_name = rec.place_name or (
rec.place_name = (
result.get('name') result.get('name')
or addr.get('amenity') or addr.get('amenity')
or addr.get('shop') or addr.get('shop')
@@ -135,7 +149,6 @@ class WtLocationLog(models.Model):
or '' or ''
) )
# Category: match OSM types to Google Timeline-style categories
osm_type = ( osm_type = (
addr.get('amenity') addr.get('amenity')
or addr.get('shop') or addr.get('shop')
@@ -146,13 +159,58 @@ class WtLocationLog(models.Model):
or result.get('type', '') or result.get('type', '')
or result.get('class', '') 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) time.sleep(1)
except Exception: except Exception:
pass 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): def action_compute_distances(self):
"""Recompute distances and travel times for all logs grouped by date.""" """Recompute distances and travel times for all logs grouped by date."""
dates = self.mapped('date') dates = self.mapped('date')
@@ -168,4 +226,4 @@ class WtLocationLog(models.Model):
if prev.departed_at and log.arrived_at: if prev.departed_at and log.arrived_at:
delta = log.arrived_at - prev.departed_at delta = log.arrived_at - prev.departed_at
log.travel_time_from_previous = max(delta.total_seconds() / 3600, 0) log.travel_time_from_previous = max(delta.total_seconds() / 3600, 0)
prev = log prev = log