Platform bundle: marketing publish, IT ops, packaging, agents mesh.
Volledige Foodlinkk Command Center uitbreiding met social automatisering, reclamefolder filters, Proxmox monitoring en documentatie.
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
"""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,),
|
||||
)
|
||||
Reference in New Issue
Block a user