fix: batch geocoding (max 20/call) to prevent worker timeout, add action_geocode_all_pending
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user