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
|
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')
|
||||||
|
|||||||
Reference in New Issue
Block a user