import os, json, re from datetime import date import pandas as pd import streamlit as st st.set_page_config(page_title="Culturally Creative & Relevant Rater", page_icon="đī¸", layout="wide") # --------- Constants --------- DATA_DIR = "data" RATINGS_CSV = os.path.join(DATA_DIR, "CCR_ratings.csv") RESULTS_CSV = os.path.join(DATA_DIR, "CCR_results.csv") DEFAULT_WEIGHTS = { "CR_cultural_resonance": 0.22, "OR_originality": 0.18, "TI_timeliness": 0.12, "IE_inclusivity_ethics": 0.10, "SH_shareability": 0.15, "BF_brand_channel_fit": 0.13, "CQ_craft_quality": 0.10 } DIMENSIONS = list(DEFAULT_WEIGHTS.keys()) AUDIENCE_OPTIONS = [ "BE urban 16-24", "BE Gen Z 18-24", "BE mainstream 25-44", "NL mainstream 25-44", "EU mainstream 25-44", "FR urban 18-34", "DE mainstream 18-49", "Other...", ] CHANNEL_OPTIONS = ["TikTok","Instagram","YouTube","OOH","TV","Radio","Integrated","Other"] RATER_OPTIONS = ["Lode","Maarten"] os.makedirs(DATA_DIR, exist_ok=True) # --------- Helpers --------- def ensure_csv(path): if not os.path.exists(path): cols = ["campaign_id","rater_id","rater_notes","campaign_name","brand","channel","scene_audience","country","submit_date_iso","asset_youtube_url",*DIMENSIONS,"flag_stereotype","flag_misappropriation","flag_sensitive_timing","flag_other_risk","neg_sentiment_ratio_estimate"] pd.DataFrame(columns=cols).to_csv(path, index=False) def load_ratings(): ensure_csv(RATINGS_CSV) try: return pd.read_csv(RATINGS_CSV) except Exception: return pd.DataFrame(columns=["campaign_id","rater_id","rater_notes","campaign_name","brand","channel","scene_audience","country","submit_date_iso","asset_youtube_url",*DIMENSIONS,"flag_stereotype","flag_misappropriation","flag_sensitive_timing","flag_other_risk","neg_sentiment_ratio_estimate"]) def save_rating(row: dict): df = load_ratings() df = pd.concat([df, pd.DataFrame([row])], ignore_index=True) df.to_csv(RATINGS_CSV, index=False) def to_0_100(x): import pandas as pd return (pd.to_numeric(x, errors="coerce") - 1.0)/4.0*100.0 def compute_results(weights: dict) -> pd.DataFrame: df = load_ratings() if df.empty: return pd.DataFrame() for d in DIMENSIONS: df[f"{d}_100"] = to_0_100(df[d]) df["CCR_rater"] = 0.0 for d, w in weights.items(): df["CCR_rater"] += w * df[f"{d}_100"] agg = {**{d: "mean" for d in DIMENSIONS}, **{f"{d}_100": "mean" for d in DIMENSIONS}, **{c: "mean" for c in ["flag_stereotype","flag_misappropriation","flag_sensitive_timing","flag_other_risk","neg_sentiment_ratio_estimate"] if c in df.columns}, "CCR_rater": "mean", "campaign_name":"first", "brand":"first", "channel":"first", "scene_audience":"first", "country":"first", "asset_youtube_url":"first"} grouped = df.groupby("campaign_id", dropna=False).agg(agg).reset_index() grouped["CCR_mean"] = grouped["CCR_rater"] grouped.to_csv(RESULTS_CSV, index=False) return grouped def live_ccr_preview(scores: dict, weights: dict) -> float: total = 0.0 for d,w in weights.items(): s100 = (scores.get(d, 3.0) - 1.0)/4.0*100.0 total += w * s100 return max(0.0, min(100.0, total)) def next_campaign_id(df: pd.DataFrame) -> str: if df.empty or "campaign_id" not in df.columns or df["campaign_id"].dropna().empty: return "CMP001" last = str(df["campaign_id"].dropna().iloc[-1]) m = re.match(r"([A-Za-z]*)(\d+)$", last) if m: prefix, num = m.group(1) or "CMP", m.group(2) nxt = int(num) + 1 return f"{prefix}{nxt:03d}" return f"CMP{len(df)+1:03d}" # --------- Session State Defaults --------- if "step" not in st.session_state: st.session_state.step = "Campagne-info" if "info" not in st.session_state: df0 = load_ratings() st.session_state.info = { "campaign_id": next_campaign_id(df0), "campaign_name": "", "brand": "", "channel": CHANNEL_OPTIONS[0], "scene_audience_choice": AUDIENCE_OPTIONS[0], "scene_audience_custom": "", "country": "BE", "submit_date_iso": str(date.today()), "asset_youtube_url": "", "rater_id": "Lode", "rater_notes": "", } if "scores" not in st.session_state: st.session_state.scores = {d: 3.0 for d in DIMENSIONS} if "risks" not in st.session_state: st.session_state.risks = {"flag_stereotype":0,"flag_misappropriation":0,"flag_sensitive_timing":0,"flag_other_risk":0,"neg_sentiment_ratio_estimate":0.0} # --------- Header --------- st.title("Culturally Creative & Relevant Rater") # --------- Stepper (segmented control) --------- try: st.session_state.step = st.segmented_control( "Stap", options=["Campagne-info","Score","Output"], default=st.session_state.step, ) except Exception: # fallback to radio if segmented_control not available st.session_state.step = st.radio("Stap", ["Campagne-info","Score","Output"], index=["Campagne-info","Score","Output"].index(st.session_state.step), horizontal=True) # --------- RENDER: Campagne-info --------- if st.session_state.step == "Campagne-info": st.subheader("Campagne-info") c_left, c_right = st.columns(2) with c_left: st.session_state.info["campaign_id"] = st.text_input("Campaign ID *", value=st.session_state.info["campaign_id"]) bn1, bn2 = st.columns(2) with bn1: st.session_state.info["brand"] = st.text_input("Brand", value=st.session_state.info["brand"]) with bn2: st.session_state.info["campaign_name"] = st.text_input("Naam", value=st.session_state.info["campaign_name"]) st.session_state.info["channel"] = st.selectbox("Channel", CHANNEL_OPTIONS, index=CHANNEL_OPTIONS.index(st.session_state.info["channel"]) if st.session_state.info["channel"] in CHANNEL_OPTIONS else 0) with c_right: st.session_state.info["scene_audience_choice"] = st.selectbox("Scene / Audience", AUDIENCE_OPTIONS, index=AUDIENCE_OPTIONS.index(st.session_state.info["scene_audience_choice"]) if st.session_state.info["scene_audience_choice"] in AUDIENCE_OPTIONS else 0) if st.session_state.info["scene_audience_choice"] == "Other...": st.session_state.info["scene_audience_custom"] = st.text_input("Custom audience", value=st.session_state.info["scene_audience_custom"]) st.session_state.info["country"] = st.text_input("Country", value=st.session_state.info["country"]) st.session_state.info["submit_date_iso"] = st.text_input("Date (YYYY-MM-DD)", value=st.session_state.info["submit_date_iso"]) st.session_state.info["rater_id"] = st.selectbox("Rater", ["Lode","Maarten"], index=["Lode","Maarten"].index(st.session_state.info["rater_id"]) if st.session_state.info["rater_id"] in ["Lode","Maarten"] else 0) st.session_state.info["asset_youtube_url"] = st.text_input("YouTube URL (optioneel)", value=st.session_state.info["asset_youtube_url"], placeholder="https://www.youtube.com/watch?v=...") st.session_state.info["rater_notes"] = st.text_area("Rater notes", value=st.session_state.info["rater_notes"], height=90, placeholder="Kernobservaties, insider cues, etc.") if st.button("Volgende â", type="primary"): st.session_state.step = "Score" st.rerun() # --------- RENDER: Score --------- elif st.session_state.step == "Score": st.subheader("Score") DESCRIPTIONS = { "CR_cultural_resonance": {"title":"Cultural Resonance", "desc":"Raakt de campagne de cultuur/scene van de doelgroep? Insider cues, taal, symbolen."}, "OR_originality": {"title":"Originality", "desc":"Is het concept verrassend en vernieuwend vs. wat al bestaat?"}, "TI_timeliness": {"title":"Timeliness", "desc":"Sluit dit aan bij het momentum: trends, events, seizoen?"}, "IE_inclusivity_ethics": {"title":"Inclusivity & Ethics", "desc":"Respectvol en inclusief, zonder stereotypes of toe-eigening?"}, "SH_shareability": {"title":"Shareability", "desc":"Hoe deelbaar is het? Hook, quotables, remixedbaarheid."}, "BF_brand_channel_fit": {"title":"Brand & Channel Fit", "desc":"Matcht met merkcodes en benut het kanaal optimaal?"}, "CQ_craft_quality": {"title":"Craft Quality", "desc":"Sterke uitvoering: beeld/copy/sound/edit/montage."}, } left, right = st.columns([1.6, 1]) with left: for d in DIMENSIONS: st.markdown(f"**{DESCRIPTIONS[d]['title']}**") st.caption(DESCRIPTIONS[d]['desc']) st.session_state.scores[d] = st.slider("", 1.0, 5.0, st.session_state.scores[d], 0.5, key=f"score_{d}") r1, r2, r3, r4 = st.columns(4) with r1: st.session_state.risks["flag_stereotype"] = int(st.checkbox("Stereotype", value=bool(st.session_state.risks["flag_stereotype"]))) with r2: st.session_state.risks["flag_misappropriation"] = int(st.checkbox("Misappropriation", value=bool(st.session_state.risks["flag_misappropriation"]))) with r3: st.session_state.risks["flag_sensitive_timing"] = int(st.checkbox("Sensitive timing", value=bool(st.session_state.risks["flag_sensitive_timing"]))) with r4: st.session_state.risks["flag_other_risk"] = int(st.checkbox("Other risk", value=bool(st.session_state.risks["flag_other_risk"]))) st.session_state.risks["neg_sentiment_ratio_estimate"] = st.slider("Neg sentiment ratio", 0.0, 1.0, float(st.session_state.risks["neg_sentiment_ratio_estimate"]), 0.01) with right: live_ccr = live_ccr_preview(st.session_state.scores, DEFAULT_WEIGHTS) st.subheader("đ Live Score") st.markdown(f"