5d60d33db1
Volledige Foodlinkk Command Center uitbreiding met social automatisering, reclamefolder filters, Proxmox monitoring en documentatie.
156 lines
6.8 KiB
Python
156 lines
6.8 KiB
Python
"""Halal certification registry sync and supermarket matching."""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
import urllib.parse
|
|
import urllib.request
|
|
from typing import Any, Optional
|
|
|
|
from app.db import execute, execute_returning, fetch_all, fetch_one, json_param
|
|
|
|
OVERPASS_URL = "https://overpass.kumi.systems/api/interpreter"
|
|
|
|
# Known halal-friendly retail brands (indicative — verified via certifier when possible)
|
|
HALAL_FRIENDLY_CHAINS = {
|
|
"Spar": {"has_halal_section": True, "note": "chain policy varies by franchise"},
|
|
"Ekoplaza": {"halal_certified": False, "has_halal_section": True},
|
|
}
|
|
|
|
|
|
def _fetch_overpass(query: str) -> list[dict[str, Any]]:
|
|
data = urllib.parse.urlencode({"data": query}).encode()
|
|
req = urllib.request.Request(OVERPASS_URL, data=data, method="POST")
|
|
with urllib.request.urlopen(req, timeout=120) as resp:
|
|
payload = json.loads(resp.read().decode())
|
|
return payload.get("elements", [])
|
|
|
|
|
|
def sync_osm_halal_tags() -> dict[str, Any]:
|
|
"""Mark supermarkets with OSM diet:halal=yes and import certification records."""
|
|
query = (
|
|
'[out:json][timeout:120];area["ISO3166-1"="NL"]->.nl;'
|
|
'(node["shop"="supermarket"]["diet:halal"="yes"](area.nl);'
|
|
'way["shop"="supermarket"]["diet:halal"="yes"](area.nl););out tags center;'
|
|
)
|
|
elements = _fetch_overpass(query)
|
|
matched = created = 0
|
|
for el in elements:
|
|
tags = el.get("tags") or {}
|
|
external_id = f"osm:{el.get('type')}:{el.get('id')}"
|
|
store = fetch_one("SELECT id, name FROM supermarkets WHERE external_id = %s", (external_id,))
|
|
if not store:
|
|
name = tags.get("name") or tags.get("brand") or "Unknown"
|
|
store = fetch_one(
|
|
"SELECT id, name FROM supermarkets WHERE name ILIKE %s LIMIT 1",
|
|
(f"%{name[:40]}%",),
|
|
)
|
|
if not store:
|
|
continue
|
|
matched += 1
|
|
execute(
|
|
"""UPDATE supermarkets SET halal_certified = TRUE, has_halal_section = TRUE,
|
|
halal_certifier = COALESCE(halal_certifier, 'OSM diet:halal'),
|
|
last_updated = NOW() WHERE id = %s""",
|
|
(store["id"],),
|
|
)
|
|
existing = fetch_one(
|
|
"SELECT id FROM halal_certifications WHERE supermarket_id = %s AND registry_source = 'osm'",
|
|
(store["id"],),
|
|
)
|
|
if not existing:
|
|
execute_returning(
|
|
"""INSERT INTO halal_certifications (
|
|
supermarket_id, certifier, business_name, status, registry_source,
|
|
matched_confidence, raw_data
|
|
) VALUES (%s, 'OSM', %s, 'active', 'osm', 0.85, %s) RETURNING id""",
|
|
(store["id"], store["name"], json_param(tags)),
|
|
)
|
|
created += 1
|
|
return {"osm_halal_elements": len(elements), "stores_matched": matched, "certs_created": created}
|
|
|
|
|
|
def sync_osm_contact_tags(limit: int = 500) -> dict[str, Any]:
|
|
"""Pull phone/email/website/operator from OSM for existing stores."""
|
|
stores = fetch_all(
|
|
"""SELECT id, external_id, phone, email, website, manager_name
|
|
FROM supermarkets WHERE external_id LIKE %s
|
|
AND (phone IS NULL OR email IS NULL OR manager_name IS NULL)
|
|
LIMIT %s""",
|
|
("osm:%", limit),
|
|
)
|
|
updated = contacts = 0
|
|
for store in stores:
|
|
parts = (store.get("external_id") or "").split(":")
|
|
if len(parts) != 3:
|
|
continue
|
|
osm_type, osm_id = parts[1], parts[2]
|
|
query = f'[out:json][timeout:30];{osm_type}({osm_id});out tags;'
|
|
try:
|
|
elements = _fetch_overpass(query)
|
|
except Exception:
|
|
continue
|
|
if not elements:
|
|
continue
|
|
tags = elements[0].get("tags") or {}
|
|
phone = tags.get("phone") or tags.get("contact:phone")
|
|
email = tags.get("email") or tags.get("contact:email")
|
|
website = tags.get("website") or tags.get("contact:website")
|
|
operator = tags.get("operator") or tags.get("contact:name")
|
|
manager = tags.get("manager") or tags.get("contact:manager") or operator
|
|
|
|
sets, params = [], []
|
|
if phone and not store.get("phone"):
|
|
sets.append("phone = %s"); params.append(str(phone)[:20])
|
|
if email and not store.get("email"):
|
|
sets.append("email = %s"); params.append(str(email)[:255])
|
|
if website and not store.get("website"):
|
|
sets.append("website = %s"); params.append(str(website)[:255])
|
|
if manager and not store.get("manager_name"):
|
|
sets.append("manager_name = %s"); params.append(str(manager)[:255])
|
|
if sets:
|
|
params.append(store["id"])
|
|
execute(f"UPDATE supermarkets SET {', '.join(sets)}, last_updated = NOW() WHERE id = %s", tuple(params))
|
|
updated += 1
|
|
|
|
if manager or phone or email:
|
|
existing = fetch_one(
|
|
"SELECT id FROM supermarket_contacts WHERE supermarket_id = %s AND source = 'osm' LIMIT 1",
|
|
(store["id"],),
|
|
)
|
|
if not existing:
|
|
execute(
|
|
"""INSERT INTO supermarket_contacts (
|
|
supermarket_id, role, full_name, phone, email, source, confidence
|
|
) VALUES (%s, 'manager', %s, %s, %s, 'osm', 0.6)""",
|
|
(store["id"], manager, phone, email),
|
|
)
|
|
contacts += 1
|
|
execute(
|
|
"""INSERT INTO supermarket_profiles (supermarket_id, manager_name, manager_phone,
|
|
manager_email, web_data, last_scraped_at, data_completeness)
|
|
VALUES (%s,%s,%s,%s,%s,NOW(),0.4)
|
|
ON CONFLICT (supermarket_id) DO UPDATE SET
|
|
manager_name = COALESCE(EXCLUDED.manager_name, supermarket_profiles.manager_name),
|
|
manager_phone = COALESCE(EXCLUDED.manager_phone, supermarket_profiles.manager_phone),
|
|
manager_email = COALESCE(EXCLUDED.manager_email, supermarket_profiles.manager_email),
|
|
web_data = supermarket_profiles.web_data || EXCLUDED.web_data,
|
|
last_scraped_at = NOW()""",
|
|
(store["id"], manager, phone, email, json_param({"osm_tags": tags})),
|
|
)
|
|
return {"scanned": len(stores), "stores_updated": updated, "contacts_added": contacts}
|
|
|
|
|
|
def list_halal_certified(limit: int = 500) -> list[dict[str, Any]]:
|
|
return fetch_all(
|
|
"""
|
|
SELECT s.*, h.certifier, h.certificate_number, h.expiry_date, h.registry_source,
|
|
h.matched_confidence
|
|
FROM supermarkets s
|
|
LEFT JOIN halal_certifications h ON h.supermarket_id = s.id AND h.status = 'active'
|
|
WHERE s.halal_certified = TRUE OR s.has_halal_section = TRUE OR h.id IS NOT NULL
|
|
ORDER BY s.chain, s.city LIMIT %s
|
|
""",
|
|
(limit,),
|
|
)
|