Files
foodlinkk-command-center/cockpit/app/routes/export_intel.py
T
Aissa 4dd1b8265f Add live CEO briefing, Wereldexport CRM, halal engine, and realtime cockpit.
Ship export intel with contact filters and CRM push, Herman live digest with Telegram briefing, halal recommendation engine, revenue cockpit, and dashboard polling/WebSocket fixes.
2026-07-19 18:25:21 +00:00

234 lines
8.0 KiB
Python

"""Export Intel Cockpit page + API proxy."""
from __future__ import annotations
import os
from typing import Any, Optional
import httpx
from fastapi import APIRouter, Query, Request
from fastapi.responses import RedirectResponse, StreamingResponse
from fastapi.templating import Jinja2Templates
from pathlib import Path
router = APIRouter()
BASE = Path(__file__).resolve().parent.parent.parent
templates = Jinja2Templates(directory=str(BASE / "templates"))
TOOLS = os.getenv("TOOLS_API_URL", "http://tools-api:8700").rstrip("/")
async def _proxy(method: str, path: str, **kwargs) -> Any:
url = f"{TOOLS}/export-intel{path}"
async with httpx.AsyncClient(timeout=60.0) as client:
r = await client.request(method, url, **kwargs)
if r.status_code >= 400:
return {"error": r.text, "status": r.status_code}
if "text/csv" in r.headers.get("content-type", ""):
return r
return r.json()
@router.get("/foodlinkk")
def foodlinkk_home():
return RedirectResponse(url="/export-intel", status_code=302)
@router.get("/export-intel")
def export_intel_page(request: Request):
return templates.TemplateResponse(
"export_intel.html",
{"request": request, "page_title": "Wereldexport"},
)
@router.get("/api/export-intel/stats")
async def api_stats(country: Optional[str] = None, region: Optional[str] = None):
params = {k: v for k, v in {"country": country, "region": region}.items() if v}
return await _proxy("GET", "/stats", params=params)
@router.get("/api/export-intel/regions")
async def api_regions():
return await _proxy("GET", "/regions")
@router.get("/api/export-intel/territories")
async def api_territories(region: Optional[str] = None):
params = {"region": region} if region else {}
return await _proxy("GET", "/territories", params=params)
@router.get("/api/export-intel/entities")
async def api_entities(
country: Optional[str] = None,
region: Optional[str] = None,
entity_type: Optional[str] = None,
entity_types: Optional[str] = None,
q: Optional[str] = None,
favorite_only: bool = False,
crm_linked: Optional[bool] = None,
limit: int = 200,
offset: int = 0,
):
params = {k: v for k, v in {
"country": country, "region": region, "entity_type": entity_type,
"entity_types": entity_types, "q": q, "limit": limit, "offset": offset,
"favorite_only": favorite_only, "crm_linked": crm_linked,
}.items() if v is not None and v is not False}
return await _proxy("GET", "/entities", params=params)
@router.get("/api/export-intel/entities/{entity_id}")
async def api_entity(entity_id: int):
return await _proxy("GET", f"/entities/{entity_id}")
@router.get("/api/export-intel/contacts")
async def api_contacts(
country: Optional[str] = None,
region: Optional[str] = None,
entity_type: Optional[str] = None,
has_email: Optional[bool] = None,
crm_linked: Optional[bool] = None,
q: Optional[str] = None,
limit: int = 200,
offset: int = 0,
):
params: dict[str, Any] = {"limit": limit, "offset": offset}
if country:
params["country"] = country
if region:
params["region"] = region
if entity_type:
params["entity_type"] = entity_type
if has_email is not None:
params["has_email"] = has_email
if crm_linked is not None:
params["crm_linked"] = crm_linked
if q:
params["q"] = q
return await _proxy("GET", "/contacts", params=params)
@router.get("/api/export-intel/contacts/export.csv")
async def api_contacts_export(
country: Optional[str] = None,
region: Optional[str] = None,
entity_type: Optional[str] = None,
has_email: Optional[bool] = None,
crm_linked: Optional[bool] = None,
q: Optional[str] = None,
):
params = {
k: v
for k, v in {
"country": country,
"region": region,
"entity_type": entity_type,
"has_email": has_email,
"crm_linked": crm_linked,
"q": q,
}.items()
if v is not None and v != ""
}
url = f"{TOOLS}/export-intel/contacts/export.csv"
async with httpx.AsyncClient(timeout=60.0) as client:
r = await client.get(url, params=params)
return StreamingResponse(
iter([r.content]),
media_type="text/csv",
headers={"Content-Disposition": "attachment; filename=export-intel-contacts.csv"},
)
@router.get("/api/export-intel/caterers/brands")
async def api_caterer_brands():
return await _proxy("GET", "/caterers/brands")
@router.get("/api/export-intel/caterers/presence")
async def api_caterer_presence(country: Optional[str] = None):
params = {"country": country} if country else {}
return await _proxy("GET", "/caterers/presence", params=params)
@router.get("/api/export-intel/gov-sources")
async def api_gov_sources(country: Optional[str] = None):
params = {"country": country} if country else {}
return await _proxy("GET", "/gov-sources", params=params)
@router.get("/api/export-intel/map/bundle")
async def api_map_bundle(
country: Optional[str] = None,
region: Optional[str] = None,
entity_type: Optional[str] = None,
entity_types: Optional[str] = None,
q: Optional[str] = None,
halal_min: Optional[float] = None,
favorite_only: bool = False,
crm_linked: Optional[bool] = None,
):
params = {k: v for k, v in {
"country": country, "region": region, "entity_type": entity_type,
"entity_types": entity_types, "q": q, "halal_min": halal_min,
"favorite_only": favorite_only, "crm_linked": crm_linked,
}.items() if v is not None and v is not False}
return await _proxy("GET", "/map/bundle", params=params)
@router.post("/api/export-intel/entities/favorites")
async def api_set_favorites(request: Request):
body = await request.json()
return await _proxy("POST", "/entities/favorites", json=body)
@router.get("/api/export-intel/entities/favorites")
async def api_list_favorites(country: Optional[str] = None, region: Optional[str] = None, limit: int = 200):
params = {k: v for k, v in {"country": country, "region": region, "limit": limit}.items() if v is not None}
return await _proxy("GET", "/entities/favorites", params=params)
@router.post("/api/export-intel/crm/push")
async def api_crm_push(request: Request):
body = await request.json()
return await _proxy("POST", "/crm/push", json=body)
@router.get("/api/export-intel/crm/pipeline")
async def api_crm_pipeline(country: Optional[str] = None, region: Optional[str] = None, limit: int = 100):
params = {k: v for k, v in {"country": country, "region": region, "limit": limit}.items() if v is not None}
return await _proxy("GET", "/crm/pipeline", params=params)
@router.get("/api/export-intel/halal/markets")
async def api_halal_markets(
region: Optional[str] = None,
country: Optional[str] = None,
limit: int = 30,
):
params = {k: v for k, v in {"region": region, "country": country, "limit": limit}.items() if v is not None}
return await _proxy("GET", "/halal/markets", params=params)
@router.get("/api/export-intel/tenders")
async def api_tenders(country: Optional[str] = None, status: Optional[str] = "open", limit: int = 100):
params = {k: v for k, v in {"country": country, "status": status, "limit": limit}.items() if v is not None}
return await _proxy("GET", "/tenders", params=params)
@router.post("/api/export-intel/sync/{kind}")
async def api_sync(kind: str, request: Request):
body = {}
if request.headers.get("content-type", "").startswith("application/json"):
try:
body = await request.json()
except Exception:
body = {}
if kind not in ("contacts", "caterers", "distributors", "customers", "tenders", "all", "world", "region"):
return {"error": "unknown sync kind"}
timeout = 7200.0 if kind in ("world", "region", "all") else 600.0
async with httpx.AsyncClient(timeout=timeout) as client:
url = f"{TOOLS}/export-intel/sync/{kind}"
r = await client.post(url, json=body)
return r.json()