"""Halal market opportunity scoring for retail locations.""" from __future__ import annotations from typing import Any from app.db import execute, fetch_all, fetch_one, json_param def compute_all_scores(limit: int = 5000) -> dict[str, Any]: rows = fetch_all( """ SELECT s.id, s.chain, s.city, s.postcode, s.halal_certified, s.has_halal_section, s.partnership_status, a.population, a.avg_income, (a.religious_composition->>'muslim_proxy_pct')::float AS muslim_pct, (a.ethnic_composition->>'niet_westers_pct')::float AS niet_westers_pct FROM supermarkets s LEFT JOIN area_analysis a ON a.postcode = s.postcode WHERE s.postcode <> '0000AA' LIMIT %s """, (limit,), ) computed = 0 for r in rows: muslim = float(r.get("muslim_pct") or 0) niet_w = float(r.get("niet_westers_pct") or 0) pop = int(r.get("population") or 0) income = float(r.get("avg_income") or 0) halal_gap = 0.0 if not r.get("halal_certified") and not r.get("has_halal_section"): halal_gap = min(100, muslim * 1.5 + niet_w * 0.5) elif r.get("has_halal_section") and not r.get("halal_certified"): halal_gap = min(80, muslim * 0.8) market_potential = 0.0 if pop > 0: market_potential += min(40, pop / 25000) if income > 0: market_potential += min(30, income / 1500) market_potential += min(30, muslim * 0.4) partnership_bonus = 15 if r.get("partnership_status") == "active" else 0 halal_opp = round(min(100, halal_gap + market_potential * 0.3), 1) market_score = round(min(100, market_potential + partnership_bonus), 1) factors = { "muslim_proxy_pct": muslim, "niet_westers_pct": niet_w, "population": pop, "avg_income": income, "halal_gap": round(halal_gap, 1), "has_halal_section": bool(r.get("has_halal_section")), "halal_certified": bool(r.get("halal_certified")), "partnership_status": r.get("partnership_status"), } execute( """ INSERT INTO retail_opportunity_scores (supermarket_id, halal_opportunity_score, market_potential_score, factors, computed_at) VALUES (%s, %s, %s, %s, NOW()) ON CONFLICT (supermarket_id) DO UPDATE SET halal_opportunity_score = EXCLUDED.halal_opportunity_score, market_potential_score = EXCLUDED.market_potential_score, factors = EXCLUDED.factors, computed_at = NOW() """, (r["id"], halal_opp, market_score, json_param(factors)), ) execute( "UPDATE supermarkets SET halal_opportunity_score = %s WHERE id = %s", (halal_opp, r["id"]), ) computed += 1 return {"computed": computed} def top_opportunities( limit: int = 50, min_score: float = 30, chain: str | None = None, province: str | None = None, ) -> list[dict[str, Any]]: clauses = ["ros.halal_opportunity_score >= %s"] params: list[Any] = [min_score] if chain: clauses.append("s.chain ILIKE %s") params.append(f"%{chain}%") if province: clauses.append("s.province ILIKE %s") params.append(f"%{province}%") where = " AND ".join(clauses) return fetch_all( f""" SELECT s.id, s.name, s.chain, s.city, s.province, s.postcode, s.partnership_status, s.halal_certified, s.has_halal_section, ros.halal_opportunity_score, ros.market_potential_score, ros.factors, a.population, a.avg_income, (a.religious_composition->>'muslim_proxy_pct')::float AS muslim_proxy_pct FROM retail_opportunity_scores ros JOIN supermarkets s ON s.id = ros.supermarket_id LEFT JOIN area_analysis a ON a.postcode = s.postcode WHERE {where} ORDER BY ros.halal_opportunity_score DESC LIMIT %s """, tuple(params + [limit]), )