Files

168 lines
6.1 KiB
Python
Raw Permalink Normal View History

"""Halal vlees trends, top gerechten en food concept seeds."""
from __future__ import annotations
import re
from datetime import datetime, timezone
from typing import Any
from app.db import fetch_all
HALAL_MEAT_KEYWORDS = (
"halal vlees", "halal meat", "halal kip", "halal lam", "halal rund",
"halal gehakt", "halal chicken", "halal beef", "halal slacht",
"halal certific", "vleesvervanger halal",
)
TOP_DISH_KEYWORDS = (
"kant-en-klaar", "kant en klaar", "ready meal", "maaltijd", "gerecht",
"curry", "stamppot", "biryani", "tagine", "lasagne", "schotel",
"meal prep", "microwave meal", "diepvries maaltijd",
)
CONCEPT_SEEDS = [
"Halal {dish} single-serve voor {chain} schappen in regio's met score >{score}",
"Premium halal {meat} maaltijdlijn — inspelen op trend: {trend}",
"Seizoens {dish} tray (4-portions) voor Plus/Jumbo non-listed partnership pitch",
"AH {dish} variant — benchmark tegen {competitor} koers momentum ({pct}%)",
"Halal-gap fill: {dish} + {meat} combo voor filialen zonder halal schap",
]
def _match_keywords(text: str, keywords: tuple[str, ...]) -> bool:
blob = (text or "").lower()
return any(k in blob for k in keywords)
def fetch_halal_meat_trends(limit: int = 15) -> list[dict[str, Any]]:
rows = fetch_all(
"""SELECT i.title, i.link, i.summary, i.published_at, f.name AS feed_name, f.url AS feed_url
FROM rss_items i JOIN rss_feeds f ON f.id = i.feed_id AND f.is_active = TRUE
ORDER BY i.published_at DESC NULLS LAST LIMIT 200"""
)
out = []
for r in rows:
title = r.get("title") or ""
summary = r.get("summary") or ""
if not _match_keywords(f"{title} {summary}", HALAL_MEAT_KEYWORDS):
continue
out.append({
"title": title,
"link": r.get("link"),
"summary": (summary or "")[:280],
"feed_name": r.get("feed_name"),
"feed_url": r.get("feed_url"),
"published_at": r.get("published_at").isoformat() if r.get("published_at") else None,
"category": "halal_vlees",
"source_url": r.get("link"),
})
if len(out) >= limit:
break
return out
def fetch_top_dishes(limit: int = 12) -> list[dict[str, Any]]:
rows = fetch_all(
"""SELECT i.title, i.link, i.summary, i.published_at, f.name AS feed_name, f.url AS feed_url
FROM rss_items i JOIN rss_feeds f ON f.id = i.feed_id AND f.is_active = TRUE
ORDER BY i.published_at DESC NULLS LAST LIMIT 250"""
)
scored: dict[str, dict[str, Any]] = {}
for r in rows:
title = (r.get("title") or "").lower()
summary = (r.get("summary") or "").lower()
blob = f"{title} {summary}"
if not _match_keywords(blob, TOP_DISH_KEYWORDS):
continue
for kw in TOP_DISH_KEYWORDS:
if kw in blob:
key = kw.strip()
if key not in scored:
scored[key] = {
"dish_keyword": key,
"mentions": 0,
"latest_title": r.get("title"),
"latest_link": r.get("link"),
"feed_name": r.get("feed_name"),
"source_url": r.get("link"),
}
scored[key]["mentions"] += 1
break
items = sorted(scored.values(), key=lambda x: x["mentions"], reverse=True)[:limit]
return items
def fetch_market_trend_rows(limit: int = 8) -> list[dict[str, Any]]:
rows = fetch_all(
"""SELECT trend_name, description, opportunity_score, source, category, updated_at
FROM market_trends ORDER BY updated_at DESC NULLS LAST LIMIT %s""",
(limit,),
)
out = []
for r in rows:
out.append({
"trend_name": r.get("trend_name"),
"description": r.get("description"),
"opportunity_score": float(r.get("opportunity_score") or 0),
"source": r.get("source") or "Foodlinkk trends",
"source_url": "/retail",
"category": r.get("category") or "markt",
})
return out
def generate_concepts(
dishes: list[dict[str, Any]] | None = None,
halal_items: list[dict[str, Any]] | None = None,
market_best: dict[str, Any] | None = None,
limit: int = 6,
) -> list[dict[str, Any]]:
dishes = dishes or fetch_top_dishes(5)
halal_items = halal_items or fetch_halal_meat_trends(5)
best_chain = (market_best or {}).get("chains", ["Albert Heijn"])[0]
pct = (market_best or {}).get("change_pct", 0)
competitor = (market_best or {}).get("name", "Ahold Delhaize")
concepts = []
for i, tmpl in enumerate(CONCEPT_SEEDS[:limit]):
dish = dishes[i % len(dishes)]["dish_keyword"] if dishes else "kant-en-klaar maaltijd"
meat = "halal kip" if halal_items else "halal vlees"
trend = halal_items[i % len(halal_items)]["title"][:60] if halal_items else "groei halal convenience"
text = tmpl.format(
dish=dish,
meat=meat,
chain=best_chain,
score=75,
trend=trend,
competitor=competitor,
pct=pct,
)
concepts.append({
"id": i + 1,
"concept": text,
"based_on": {
"dish": dish,
"halal_trend": trend,
"market_signal": f"{competitor} {pct:+.1f}%" if market_best else "retail DB",
},
"source_urls": [
u for u in [
dishes[i % len(dishes)].get("source_url") if dishes else None,
halal_items[i % len(halal_items)].get("source_url") if halal_items else None,
(market_best or {}).get("source_url"),
] if u
],
})
return concepts
def food_trends_dashboard() -> dict[str, Any]:
halal = fetch_halal_meat_trends()
dishes = fetch_top_dishes()
trends = fetch_market_trend_rows()
return {
"halal_meat_trends": halal,
"top_dishes": dishes,
"market_trends": trends,
"updated_at": datetime.now(timezone.utc).isoformat(),
}