the-main-method commited on
Commit
77d1323
·
1 Parent(s): fc2a790

Fix HF debate arena fallbacks, score focus 0 in LLM mode, and typewriter concurrent race condition

Browse files
Files changed (2) hide show
  1. app/engine.py +124 -0
  2. app/static/app.js +13 -3
app/engine.py CHANGED
@@ -575,6 +575,29 @@ def extract_aspect_argument(persona, item, aspect, query, memory_pool=None):
575
  text = pattern.sub(item["title"], text)
576
  text = re.sub(r'\s+', ' ', text).strip()
577
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
578
  return text
579
 
580
 
@@ -829,6 +852,93 @@ def _calibrate_agent_score(raw_score_0_100, item_avg_rating=4.0):
829
  calibrated = item_avg_rating + deviation * 2.0
830
  return max(1.0, min(5.0, round(calibrated, 2)))
831
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
832
  # ----------------------------------------------------------------------
833
  # 5. LOSS & WEIGHTS PARAMETER TRAINING (COORDINATE DESCENT)
834
  # ----------------------------------------------------------------------
@@ -2017,6 +2127,7 @@ class TasteTwinEngine:
2017
  parsed_debate = []
2018
  if debate_match:
2019
  parsed_debate = parse_debate_transcript(debate_match.group(1))
 
2020
 
2021
  return {
2022
  "rating": rating,
@@ -2177,6 +2288,8 @@ class TasteTwinEngine:
2177
  r["price"] = real_item["price"]
2178
  r["currency"] = real_item["currency"]
2179
  r["category"] = real_item["category"]
 
 
2180
  valid_recs.append(r)
2181
  else:
2182
  # Fallback matching by title
@@ -2190,12 +2303,23 @@ class TasteTwinEngine:
2190
  r["price"] = real_item["price"]
2191
  r["currency"] = real_item["currency"]
2192
  r["category"] = real_item["category"]
 
 
2193
  valid_recs.append(r)
2194
  else:
2195
  # Default values so UI doesn't crash or show N/A
2196
  r["price"] = r.get("price") or 15000.0
2197
  r["currency"] = r.get("currency") or "NGN"
2198
  r["category"] = r.get("category") or "electronics"
 
 
 
 
 
 
 
 
 
2199
  valid_recs.append(r)
2200
 
2201
  valid_recs.sort(key=lambda x: x.get("predicted_rating", 0.0), reverse=True)
 
575
  text = pattern.sub(item["title"], text)
576
  text = re.sub(r'\s+', ' ', text).strip()
577
 
578
+ # Beautifully synthesize natural fallbacks when RAG matches raw metadata context strings
579
+ text_lower = text.lower()
580
+ if "target product features:" in text_lower or "category affinities:" in text_lower or "historical aspect sentiments:" in text_lower or "no specific historical traces for taste" in text_lower:
581
+ features_list = ", ".join(item.get("features", []))
582
+ text = f"Looking closely at the specifications for {item['title']}, the build features are quite compelling: {features_list}. For a user with {persona['name']}'s distinct taste DNA, this matches extremely well omo!"
583
+ elif "target product price:" in text_lower or "budget sensitivity dna:" in text_lower or "historical prices paid:" in text_lower or "average historical price:" in text_lower or "no specific historical traces for budget" in text_lower:
584
+ text = f"Let's talk about the pricing for {item['title']}. It is listed at {item['price']} {item['currency']}. Considering {persona['name']}'s budget sensitivity DNA of {persona['dna']['budget']}%, this price point requires careful value assessment to ensure complete alignment sha."
585
+ elif "is this category new to the user" in text_lower or "novelty exploration dna:" in text_lower or "historically purchased categories:" in text_lower or "target product category:" in text_lower or "no specific historical traces for novelty" in text_lower:
586
+ user_cats = set(r["category"] for r in persona.get("history", []))
587
+ is_new = item["category"] not in user_cats
588
+ if is_new:
589
+ text = f"This category is actually completely new to {persona['name']}! Since their novelty exploration DNA is set to {persona['dna']['novelty']}%, this is an exciting opportunity to try something completely fresh, abeg!"
590
+ else:
591
+ text = f"This category is familiar to {persona['name']}. But even within a familiar domain, the unique qualities of {item['title']} should offer a nice, satisfying twist to their routine."
592
+ elif "persona recent mood state:" in text_lower or "strictness dna:" in text_lower or "historical rating variance:" in text_lower or "exclamations across" in text_lower or "no specific historical traces for mood" in text_lower:
593
+ mood = persona.get("recent_mood", "standard")
594
+ text = f"Looking at {persona['name']}'s current mood, they are feeling {mood}. With a strictness level of {persona['dna']['strictness']}%, their expectations will be quite high omo, so there is no room for subpar performance."
595
+ elif "nigerian behavioral cues to prioritize:" in text_lower or "nigerian affinity scale dna:" in text_lower or "no specific historical traces for cultural" in text_lower:
596
+ text = f"For the Nigerian context, durability is key. We are looking at durability under NEPA grid fluctuations, resale value, and Lagos traffic/heavy rain delivery logistics. {item['title']} needs to show it can survive the local elements sha."
597
+ elif "target product complaints:" in text_lower or "no specific historical traces for post_consumption" in text_lower:
598
+ complaints_list = ", ".join(item.get("complaints", [])) if item.get("complaints") else "no major complaints reported"
599
+ text = f"I noticed some minor complaints about {item['title']}: {complaints_list}. While these shouldn't be dealbreakers, they are worth keeping in mind for the overall user experience."
600
+
601
  return text
602
 
603
 
 
852
  calibrated = item_avg_rating + deviation * 2.0
853
  return max(1.0, min(5.0, round(calibrated, 2)))
854
 
855
+
856
+ def inject_mathematical_agent_scores(persona, item, debate_script, final_rating=None):
857
+ """
858
+ Dynamically overrides/injects actual mathematically optimized DNA focus scores
859
+ into each debate agent's turn to prevent flat 0.0 scores in LLM outputs.
860
+ """
861
+ if not debate_script:
862
+ return debate_script
863
+
864
+ item_avg = item.get("avg_rating", 4.0) if item else 4.0
865
+
866
+ # 1. Taste Score
867
+ taste_score = item_avg * 20.0
868
+ if item and item["category"] == persona["domain"]:
869
+ taste_score += 10.0
870
+ cal_taste = _calibrate_agent_score(taste_score, item_avg)
871
+
872
+ # 2. Budget Score
873
+ price_ngn = 15000.0
874
+ if item:
875
+ price_ngn = item["price"] if item["currency"] == "NGN" else item["price"] * 1500.0
876
+ budget_sensitivity = persona["dna"]["budget"]
877
+ if price_ngn > 100000:
878
+ budget_score = max(10.0, 100.0 - (budget_sensitivity * 0.9))
879
+ elif price_ngn > 30000:
880
+ budget_score = max(30.0, 100.0 - (budget_sensitivity * 0.5))
881
+ else:
882
+ budget_score = min(100.0, 50.0 + (budget_sensitivity * 0.5))
883
+ cal_budget = _calibrate_agent_score(budget_score, item_avg)
884
+
885
+ # 3. Novelty Score
886
+ user_cats = set(rec["category"] for rec in persona.get("history", []))
887
+ novelty_sensitivity = persona["dna"]["novelty"]
888
+ if item and item["category"] not in user_cats:
889
+ novelty_score = novelty_sensitivity
890
+ else:
891
+ novelty_score = 100.0 - (novelty_sensitivity * 0.5)
892
+ cal_novelty = _calibrate_agent_score(novelty_score, item_avg)
893
+
894
+ # 4. Mood Score
895
+ mood = persona.get("recent_mood", "standard")
896
+ mood_score = 70.0
897
+ if mood == "frustrated":
898
+ mood_score = 40.0
899
+ elif mood == "happy":
900
+ mood_score = 90.0
901
+ cal_mood = _calibrate_agent_score(mood_score, item_avg)
902
+
903
+ # 5. Cultural Score
904
+ naija_scale = persona["dna"]["naija_scale"]
905
+ naija_score = 70.0
906
+ if item:
907
+ has_delivery_issue = any("delivery" in c.lower() or "wait" in c.lower() or "delay" in c.lower() for c in item.get("complaints", []))
908
+ has_power_issue = any("charge" in c.lower() or "nepa" in c.lower() or "battery" in c.lower() or "power" in c.lower() for c in item.get("complaints", []))
909
+ has_repair_issue = any("broken" in c.lower() or "fragile" in c.lower() or "break" in c.lower() for c in item.get("complaints", []))
910
+
911
+ if naija_scale > 60:
912
+ if has_delivery_issue:
913
+ naija_score -= 25.0
914
+ if has_power_issue:
915
+ naija_score -= 20.0
916
+ if has_repair_issue:
917
+ naija_score -= 15.0
918
+ cal_cultural = _calibrate_agent_score(naija_score, item_avg)
919
+
920
+ for step in debate_script:
921
+ if not isinstance(step, dict):
922
+ continue
923
+ agent_name = step.get("agent", "")
924
+ if "Taste" in agent_name:
925
+ step["score"] = cal_taste
926
+ elif "Budget" in agent_name:
927
+ step["score"] = cal_budget
928
+ elif "Novelty" in agent_name:
929
+ step["score"] = cal_novelty
930
+ elif "Mood" in agent_name:
931
+ step["score"] = cal_mood
932
+ elif "Cultural" in agent_name:
933
+ step["score"] = cal_cultural
934
+ elif "Judge" in agent_name:
935
+ if final_rating is not None:
936
+ step["score"] = final_rating
937
+ elif step.get("score") == 0.0 or not step.get("score"):
938
+ step["score"] = item_avg
939
+
940
+ return debate_script
941
+
942
  # ----------------------------------------------------------------------
943
  # 5. LOSS & WEIGHTS PARAMETER TRAINING (COORDINATE DESCENT)
944
  # ----------------------------------------------------------------------
 
2127
  parsed_debate = []
2128
  if debate_match:
2129
  parsed_debate = parse_debate_transcript(debate_match.group(1))
2130
+ parsed_debate = inject_mathematical_agent_scores(persona, item, parsed_debate, final_rating=rating)
2131
 
2132
  return {
2133
  "rating": rating,
 
2288
  r["price"] = real_item["price"]
2289
  r["currency"] = real_item["currency"]
2290
  r["category"] = real_item["category"]
2291
+ if "debate" in r and isinstance(r["debate"], list):
2292
+ r["debate"] = inject_mathematical_agent_scores(persona, real_item, r["debate"], final_rating=r.get("predicted_rating"))
2293
  valid_recs.append(r)
2294
  else:
2295
  # Fallback matching by title
 
2303
  r["price"] = real_item["price"]
2304
  r["currency"] = real_item["currency"]
2305
  r["category"] = real_item["category"]
2306
+ if "debate" in r and isinstance(r["debate"], list):
2307
+ r["debate"] = inject_mathematical_agent_scores(persona, real_item, r["debate"], final_rating=r.get("predicted_rating"))
2308
  valid_recs.append(r)
2309
  else:
2310
  # Default values so UI doesn't crash or show N/A
2311
  r["price"] = r.get("price") or 15000.0
2312
  r["currency"] = r.get("currency") or "NGN"
2313
  r["category"] = r.get("category") or "electronics"
2314
+ dummy_item = {
2315
+ "id": r.get("item_id", "dummy"),
2316
+ "title": r.get("title", "Product"),
2317
+ "category": r["category"],
2318
+ "price": r["price"],
2319
+ "currency": r["currency"]
2320
+ }
2321
+ if "debate" in r and isinstance(r["debate"], list):
2322
+ r["debate"] = inject_mathematical_agent_scores(persona, dummy_item, r["debate"], final_rating=r.get("predicted_rating"))
2323
  valid_recs.append(r)
2324
 
2325
  valid_recs.sort(key=lambda x: x.get("predicted_rating", 0.0), reverse=True)
app/static/app.js CHANGED
@@ -553,12 +553,13 @@ function playSimulationResult(monologue, review, rating) {
553
  });
554
  }
555
 
556
- function typeWriter(element, text, speed = 20, callback = null) {
557
  let index = 0;
558
  // Fast typing: do word by word or chunk by chunk if long text to avoid user frustration
559
  let words = text.split(" ");
560
 
561
  function type() {
 
562
  if (index < words.length) {
563
  element.textContent += (index === 0 ? "" : " ") + words[index];
564
  index++;
@@ -848,13 +849,17 @@ function selectDebateItem(itemId) {
848
  }
849
  }
850
 
 
 
851
  function playAgentDebateSequence(debate) {
852
  const arena = document.getElementById("debate-arena");
853
  arena.innerHTML = "";
854
 
 
855
  let index = 0;
856
 
857
  function playNextBubble() {
 
858
  if (index < debate.length) {
859
  const step = debate[index];
860
  const bubble = document.createElement("div");
@@ -877,9 +882,14 @@ function playAgentDebateSequence(debate) {
877
  // Type message
878
  const msgContainer = document.getElementById(`debate-msg-${index}`);
879
  typeWriter(msgContainer, step.text, 15, () => {
 
880
  index++;
881
- setTimeout(playNextBubble, 600); // delay before next agent enters
882
- });
 
 
 
 
883
  }
884
  }
885
 
 
553
  });
554
  }
555
 
556
+ function typeWriter(element, text, speed = 20, callback = null, playIdToCheck = null) {
557
  let index = 0;
558
  // Fast typing: do word by word or chunk by chunk if long text to avoid user frustration
559
  let words = text.split(" ");
560
 
561
  function type() {
562
+ if (playIdToCheck !== null && playIdToCheck !== currentDebatePlayId) return;
563
  if (index < words.length) {
564
  element.textContent += (index === 0 ? "" : " ") + words[index];
565
  index++;
 
849
  }
850
  }
851
 
852
+ let currentDebatePlayId = 0;
853
+
854
  function playAgentDebateSequence(debate) {
855
  const arena = document.getElementById("debate-arena");
856
  arena.innerHTML = "";
857
 
858
+ const playId = ++currentDebatePlayId;
859
  let index = 0;
860
 
861
  function playNextBubble() {
862
+ if (playId !== currentDebatePlayId) return;
863
  if (index < debate.length) {
864
  const step = debate[index];
865
  const bubble = document.createElement("div");
 
882
  // Type message
883
  const msgContainer = document.getElementById(`debate-msg-${index}`);
884
  typeWriter(msgContainer, step.text, 15, () => {
885
+ if (playId !== currentDebatePlayId) return;
886
  index++;
887
+ setTimeout(() => {
888
+ if (playId === currentDebatePlayId) {
889
+ playNextBubble();
890
+ }
891
+ }, 600); // delay before next agent enters
892
+ }, playId);
893
  }
894
  }
895