This reverts commit bad1d312ef.
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
This commit is contained in:
@@ -950,7 +950,7 @@ Read the research output carefully. Pay attention to:
|
|||||||
|
|
||||||
**ANTI-PATTERN TO AVOID**: If user asks about "clawdbot skills" and research returns ClawdBot content (self-hosted AI agent), do NOT synthesize this as "Claude Code skills" just because both involve "skills". Read what the research actually says.
|
**ANTI-PATTERN TO AVOID**: If user asks about "clawdbot skills" and research returns ClawdBot content (self-hosted AI agent), do NOT synthesize this as "Claude Code skills" just because both involve "skills". Read what the research actually says.
|
||||||
|
|
||||||
**FUN CONTENT: If the research output includes a "## Best Takes" section at the top of the compact output, quote at least TWO of those items verbatim in your synthesis.** These are pre-ranked comedy and wit lines, pulled from both candidate titles and individual top_comments across every source. A comment entry looks like `"WHAT?! I reached my monthly limit just reading this post" -- r/Anthropic in "Claude Opus 4.7 is a regression" (2,304 upvotes) (fun:88)`. Use the full body, attribute to the container (r/subreddit, @handle on platform), and keep the upvote number so readers can gauge community agreement. Reddit comments and X posts with high fun scores are the voice of the people. Don't put fun content in a separate section - mix it into the narrative where it fits naturally. If no Best Takes section appears, scan items tagged with `fun:` scores in the cluster listings and weave 2-3 of those instead.
|
**FUN CONTENT: If the research output includes a "## Best Takes" section or items tagged with `fun:` scores, weave at least 2-3 of the funniest/cleverest quotes into your synthesis.** Reddit comments and X posts with high fun scores are the voice of the people. A 1,338-upvote comment that says "Where's the limewire link" tells you more about the cultural moment than a news article. Quote the actual text. Don't put fun content in a separate section - mix it into the narrative where it fits naturally. This is what makes the report feel alive rather than like a news summary.
|
||||||
|
|
||||||
**ELI5 MODE: If ELI5_MODE is true for this run, apply these writing guidelines to your ENTIRE synthesis. If ELI5_MODE is false, skip this block completely and write normally.**
|
**ELI5 MODE: If ELI5_MODE is true for this run, apply these writing guidelines to your ENTIRE synthesis. If ELI5_MODE is false, skip this block completely and write normally.**
|
||||||
|
|
||||||
|
|||||||
+30
-75
@@ -18,9 +18,9 @@ SOURCE_LABELS = {
|
|||||||
|
|
||||||
|
|
||||||
_FUN_LEVELS = {
|
_FUN_LEVELS = {
|
||||||
"low": {"threshold": 75.0, "limit": 2},
|
"low": {"threshold": 80.0, "limit": 2},
|
||||||
"medium": {"threshold": 55.0, "limit": 5},
|
"medium": {"threshold": 70.0, "limit": 5},
|
||||||
"high": {"threshold": 40.0, "limit": 8},
|
"high": {"threshold": 55.0, "limit": 8},
|
||||||
}
|
}
|
||||||
|
|
||||||
_AI_SAFETY_NOTE = (
|
_AI_SAFETY_NOTE = (
|
||||||
@@ -60,11 +60,6 @@ def render_compact(report: schema.Report, cluster_limit: int = 8, fun_level: str
|
|||||||
lines.extend(f"- {warning}" for warning in report.warnings)
|
lines.extend(f"- {warning}" for warning in report.warnings)
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
fun_params = _FUN_LEVELS.get(fun_level, _FUN_LEVELS["medium"])
|
|
||||||
best_takes = _render_best_takes(report.ranked_candidates, limit=fun_params["limit"], threshold=fun_params["threshold"])
|
|
||||||
if best_takes:
|
|
||||||
lines.extend(best_takes + [""])
|
|
||||||
|
|
||||||
lines.append("## Ranked Evidence Clusters")
|
lines.append("## Ranked Evidence Clusters")
|
||||||
lines.append("")
|
lines.append("")
|
||||||
candidate_by_id = {candidate.candidate_id: candidate for candidate in report.ranked_candidates}
|
candidate_by_id = {candidate.candidate_id: candidate for candidate in report.ranked_candidates}
|
||||||
@@ -85,6 +80,11 @@ def render_compact(report: schema.Report, cluster_limit: int = 8, fun_level: str
|
|||||||
|
|
||||||
lines.extend(_render_stats(report))
|
lines.extend(_render_stats(report))
|
||||||
|
|
||||||
|
fun_params = _FUN_LEVELS.get(fun_level, _FUN_LEVELS["medium"])
|
||||||
|
best_takes = _render_best_takes(report.ranked_candidates, limit=fun_params["limit"], threshold=fun_params["threshold"])
|
||||||
|
if best_takes:
|
||||||
|
lines.extend([""] + best_takes)
|
||||||
|
|
||||||
lines.extend(_render_source_coverage(report))
|
lines.extend(_render_source_coverage(report))
|
||||||
return "\n".join(lines).strip() + "\n"
|
return "\n".join(lines).strip() + "\n"
|
||||||
|
|
||||||
@@ -654,78 +654,33 @@ def _source_label(source: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def _render_best_takes(candidates, limit=5, threshold=70.0):
|
def _render_best_takes(candidates, limit=5, threshold=70.0):
|
||||||
# Build a unified list of gems: candidate-level entries and comment-level entries,
|
gems = sorted(
|
||||||
# each tagged by type so they can share the sort but render with different attribution.
|
(c for c in candidates if c.fun_score is not None and c.fun_score >= threshold),
|
||||||
gems: list[tuple[str, float, object]] = []
|
key=lambda c: -(c.fun_score or 0),
|
||||||
for candidate in candidates:
|
)
|
||||||
if candidate.fun_score is not None and candidate.fun_score >= threshold:
|
if len(gems) < 2:
|
||||||
gems.append(("candidate", candidate.fun_score, candidate))
|
|
||||||
for item in candidate.source_items:
|
|
||||||
for comment in item.metadata.get("top_comments", []) or []:
|
|
||||||
if not isinstance(comment, dict):
|
|
||||||
continue
|
|
||||||
comment_score = comment.get("fun_score")
|
|
||||||
if comment_score is None or comment_score < threshold:
|
|
||||||
continue
|
|
||||||
gems.append(("comment", float(comment_score), (candidate, item, comment)))
|
|
||||||
if not gems:
|
|
||||||
return []
|
return []
|
||||||
gems.sort(key=lambda g: -g[1])
|
|
||||||
lines = ["## Best Takes", ""]
|
lines = ["## Best Takes", ""]
|
||||||
for kind, score, payload in gems[:limit]:
|
for candidate in gems[:limit]:
|
||||||
if kind == "candidate":
|
text = candidate.title.strip()
|
||||||
lines.append(_render_candidate_gem(payload))
|
for item in candidate.source_items:
|
||||||
else:
|
for comment in item.metadata.get("top_comments", [])[:3]:
|
||||||
candidate, item, comment = payload
|
body = (comment.get("body") or comment.get("text") or "") if isinstance(comment, dict) else str(comment)
|
||||||
lines.append(_render_comment_gem(candidate, item, comment))
|
body = body.strip()
|
||||||
|
if body and len(body) < len(text) and len(body) > 10:
|
||||||
|
text = body
|
||||||
|
source_label = _source_label(candidate.source)
|
||||||
|
author = candidate.source_items[0].author if candidate.source_items else None
|
||||||
|
attribution = f"@{author} on {source_label}" if author and candidate.source in ("x", "tiktok", "instagram", "threads") else f"{source_label}"
|
||||||
|
if author and candidate.source == "reddit":
|
||||||
|
container = candidate.source_items[0].container if candidate.source_items else None
|
||||||
|
attribution = f"r/{container} comment" if container else "Reddit"
|
||||||
|
score_tag = f"(fun:{candidate.fun_score:.0f})"
|
||||||
|
reason = f" -- {candidate.fun_explanation}" if candidate.fun_explanation and candidate.fun_explanation != "heuristic-fallback" else ""
|
||||||
|
lines.append(f'- "{_truncate(text, 280)}" -- {attribution} {score_tag}{reason}')
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
|
||||||
def _render_candidate_gem(candidate) -> str:
|
|
||||||
text = candidate.title.strip()
|
|
||||||
for item in candidate.source_items:
|
|
||||||
for comment in item.metadata.get("top_comments", [])[:3]:
|
|
||||||
body = (comment.get("body") or comment.get("excerpt") or comment.get("text") or "") if isinstance(comment, dict) else str(comment)
|
|
||||||
body = body.strip()
|
|
||||||
if body and len(body) < len(text) and len(body) > 10:
|
|
||||||
text = body
|
|
||||||
source_label = _source_label(candidate.source)
|
|
||||||
author = candidate.source_items[0].author if candidate.source_items else None
|
|
||||||
attribution = f"@{author} on {source_label}" if author and candidate.source in ("x", "tiktok", "instagram", "threads") else f"{source_label}"
|
|
||||||
if author and candidate.source == "reddit":
|
|
||||||
container = candidate.source_items[0].container if candidate.source_items else None
|
|
||||||
attribution = f"r/{container} comment" if container else "Reddit"
|
|
||||||
score_tag = f"(fun:{candidate.fun_score:.0f})"
|
|
||||||
reason = f" -- {candidate.fun_explanation}" if candidate.fun_explanation and candidate.fun_explanation != "heuristic-fallback" else ""
|
|
||||||
return f'- "{_truncate(text, 280)}" -- {attribution} {score_tag}{reason}'
|
|
||||||
|
|
||||||
|
|
||||||
def _render_comment_gem(candidate, item, comment) -> str:
|
|
||||||
body = (comment.get("body") or comment.get("excerpt") or comment.get("text") or "").strip()
|
|
||||||
upvotes = comment.get("score") or comment.get("ups") or comment.get("upvotes") or comment.get("likes") or 0
|
|
||||||
source_label = _source_label(candidate.source)
|
|
||||||
parent_title = (candidate.title or "").strip()
|
|
||||||
comment_author = comment.get("author")
|
|
||||||
vote_label = _vote_label_for(candidate.source)
|
|
||||||
fun_tag = f"(fun:{comment.get('fun_score', 0):.0f})"
|
|
||||||
|
|
||||||
if candidate.source == "reddit":
|
|
||||||
container = item.container if item else None
|
|
||||||
source_attribution = f"r/{container}" if container else source_label
|
|
||||||
elif comment_author:
|
|
||||||
source_attribution = f"@{comment_author} on {source_label}"
|
|
||||||
else:
|
|
||||||
source_attribution = source_label
|
|
||||||
|
|
||||||
vote_suffix = ""
|
|
||||||
if isinstance(upvotes, int) and upvotes:
|
|
||||||
vote_suffix = f" ({upvotes:,} {vote_label})"
|
|
||||||
|
|
||||||
in_clause = f' in "{_truncate(parent_title, 100)}"' if parent_title else ""
|
|
||||||
|
|
||||||
return f'- "{_truncate(body, 280)}" -- {source_attribution}{in_clause}{vote_suffix} {fun_tag}'
|
|
||||||
|
|
||||||
|
|
||||||
def _truncate(text: str, limit: int) -> str:
|
def _truncate(text: str, limit: int) -> str:
|
||||||
text = text.strip()
|
text = text.strip()
|
||||||
if len(text) <= limit:
|
if len(text) <= limit:
|
||||||
|
|||||||
@@ -285,13 +285,11 @@ def _apply_fun_scores(candidates: list[schema.Candidate], payload: dict) -> None
|
|||||||
c.fun_score, c.fun_explanation = scores[c.candidate_id]
|
c.fun_score, c.fun_explanation = scores[c.candidate_id]
|
||||||
else:
|
else:
|
||||||
_apply_single_fun_fallback(c)
|
_apply_single_fun_fallback(c)
|
||||||
_score_comments_per_candidate(c)
|
|
||||||
|
|
||||||
|
|
||||||
def _apply_fun_fallback(candidates: list[schema.Candidate]) -> None:
|
def _apply_fun_fallback(candidates: list[schema.Candidate]) -> None:
|
||||||
for c in candidates:
|
for c in candidates:
|
||||||
_apply_single_fun_fallback(c)
|
_apply_single_fun_fallback(c)
|
||||||
_score_comments_per_candidate(c)
|
|
||||||
|
|
||||||
|
|
||||||
def _apply_single_fun_fallback(candidate: schema.Candidate) -> None:
|
def _apply_single_fun_fallback(candidate: schema.Candidate) -> None:
|
||||||
@@ -306,72 +304,6 @@ def _apply_single_fun_fallback(candidate: schema.Candidate) -> None:
|
|||||||
candidate.fun_explanation = "heuristic-fallback"
|
candidate.fun_explanation = "heuristic-fallback"
|
||||||
|
|
||||||
|
|
||||||
_FUN_MARKERS = ("lol", "lmao", "dead", "hilarious", "funny", "bruh", "ratio",
|
|
||||||
"nah", "bro", "ain't no way", "i'm crying", "rent free")
|
|
||||||
|
|
||||||
|
|
||||||
def _comment_body(comment: dict) -> str:
|
|
||||||
for key in ("body", "excerpt", "text"):
|
|
||||||
value = comment.get(key) if isinstance(comment, dict) else None
|
|
||||||
if value:
|
|
||||||
return str(value).strip()
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def _comment_upvotes(comment: dict) -> int:
|
|
||||||
for key in ("score", "ups", "upvotes", "likes"):
|
|
||||||
value = comment.get(key) if isinstance(comment, dict) else None
|
|
||||||
if value is not None:
|
|
||||||
try:
|
|
||||||
return int(value)
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
continue
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def _parent_raw_upvotes(candidate: schema.Candidate) -> int:
|
|
||||||
for item in candidate.source_items:
|
|
||||||
eng = item.engagement
|
|
||||||
if isinstance(eng, dict):
|
|
||||||
for key in ("score", "ups", "upvotes", "likes"):
|
|
||||||
value = eng.get(key)
|
|
||||||
if value is not None:
|
|
||||||
try:
|
|
||||||
return int(value)
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
continue
|
|
||||||
elif isinstance(eng, (int, float)) and eng:
|
|
||||||
return int(eng)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def _score_comments_per_candidate(candidate: schema.Candidate) -> None:
|
|
||||||
"""Annotate each of the top 3 comments on this candidate with its own fun_score.
|
|
||||||
|
|
||||||
Scoring: (ratio-to-parent bonus, capped 50) + shortness bonus (0-30) + marker bonus (0-20).
|
|
||||||
A comment with high upvotes relative to its parent thread dominates an absolute-high
|
|
||||||
comment on a dominant parent thread, which is the viral-wit signal.
|
|
||||||
"""
|
|
||||||
parent_upvotes = _parent_raw_upvotes(candidate)
|
|
||||||
for item in candidate.source_items:
|
|
||||||
comments = item.metadata.get("top_comments") or []
|
|
||||||
if not isinstance(comments, list):
|
|
||||||
continue
|
|
||||||
for comment in comments[:3]:
|
|
||||||
if not isinstance(comment, dict):
|
|
||||||
continue
|
|
||||||
body = _comment_body(comment)
|
|
||||||
if not body:
|
|
||||||
continue
|
|
||||||
upvotes = _comment_upvotes(comment)
|
|
||||||
ratio = upvotes / max(parent_upvotes, 1) if parent_upvotes else min(upvotes / 100.0, 2.5)
|
|
||||||
ratio_bonus = min(ratio * 20.0, 50.0)
|
|
||||||
body_len = len(body)
|
|
||||||
shortness_bonus = max(0.0, (200 - body_len) / 200.0) * 30.0
|
|
||||||
marker_bonus = 20.0 if any(m in body.lower() for m in _FUN_MARKERS) else 0.0
|
|
||||||
comment["fun_score"] = max(0.0, min(100.0, ratio_bonus + shortness_bonus + marker_bonus))
|
|
||||||
|
|
||||||
|
|
||||||
def _normalized_rrf(rrf_score: float) -> float:
|
def _normalized_rrf(rrf_score: float) -> float:
|
||||||
# Empirical ceiling for normalized RRF scores at the pool sizes we use.
|
# Empirical ceiling for normalized RRF scores at the pool sizes we use.
|
||||||
# Max single-stream RRF at rank 1 is 1/(K+1) ~ 0.016; multi-stream
|
# Max single-stream RRF at rank 1 is 1/(K+1) ~ 0.016; multi-stream
|
||||||
|
|||||||
+4
-113
@@ -387,124 +387,15 @@ class RenderBestTakesCompactTests(unittest.TestCase):
|
|||||||
text = render.render_compact(report)
|
text = render.render_compact(report)
|
||||||
self.assertNotIn("## Best Takes", text)
|
self.assertNotIn("## Best Takes", text)
|
||||||
|
|
||||||
def test_best_takes_with_1_high_fun_candidate(self):
|
def test_no_best_takes_with_1_high_fun_candidate(self):
|
||||||
"""Best Takes section appears even with only 1 candidate above threshold.
|
"""No Best Takes section when only 1 candidate above threshold."""
|
||||||
|
|
||||||
The single-gem floor means a single viral quote is enough to show the block,
|
|
||||||
which is essential for the default medium level on most research topics.
|
|
||||||
"""
|
|
||||||
candidates = [
|
candidates = [
|
||||||
self._make_candidate("c1", fun_score=80),
|
self._make_candidate("c1", fun_score=80),
|
||||||
self._make_candidate("c2", fun_score=40),
|
self._make_candidate("c2", fun_score=50),
|
||||||
]
|
]
|
||||||
report = self._make_report_with_candidates(candidates)
|
report = self._make_report_with_candidates(candidates)
|
||||||
text = render.render_compact(report)
|
text = render.render_compact(report)
|
||||||
self.assertIn("## Best Takes", text)
|
self.assertNotIn("## Best Takes", text)
|
||||||
self.assertIn("(fun:80)", text)
|
|
||||||
|
|
||||||
def test_best_takes_renders_above_clusters(self):
|
|
||||||
"""Best Takes section appears before the Ranked Evidence Clusters header."""
|
|
||||||
candidates = [
|
|
||||||
self._make_candidate("c1", fun_score=85),
|
|
||||||
self._make_candidate("c2", fun_score=75),
|
|
||||||
]
|
|
||||||
report = self._make_report_with_candidates(candidates)
|
|
||||||
text = render.render_compact(report)
|
|
||||||
self.assertLess(text.index("## Best Takes"), text.index("## Ranked Evidence Clusters"))
|
|
||||||
|
|
||||||
def test_comment_level_gem_appears_in_best_takes(self):
|
|
||||||
"""A high-fun top_comment on a low-fun parent candidate still qualifies."""
|
|
||||||
item = schema.SourceItem(
|
|
||||||
item_id="item-c1",
|
|
||||||
source="reddit",
|
|
||||||
title="Post c1",
|
|
||||||
body="Body.",
|
|
||||||
url="https://reddit.com/r/test/comments/c1/",
|
|
||||||
container="test",
|
|
||||||
published_at="2026-03-15",
|
|
||||||
date_confidence="high",
|
|
||||||
engagement={"score": 300, "num_comments": 30},
|
|
||||||
metadata={
|
|
||||||
"top_comments": [{
|
|
||||||
"body": "WHAT?! I reached my monthly limit just reading this post",
|
|
||||||
"excerpt": "WHAT?! I reached my monthly limit just reading this post",
|
|
||||||
"score": 2304,
|
|
||||||
"fun_score": 88.0,
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
candidate = schema.Candidate(
|
|
||||||
candidate_id="c1",
|
|
||||||
item_id="item-c1",
|
|
||||||
source="reddit",
|
|
||||||
title="Post c1",
|
|
||||||
url="https://reddit.com/r/test/comments/c1/",
|
|
||||||
snippet="",
|
|
||||||
subquery_labels=["primary"],
|
|
||||||
native_ranks={"primary:reddit": 1},
|
|
||||||
local_relevance=0.9,
|
|
||||||
freshness=90,
|
|
||||||
engagement=88,
|
|
||||||
source_quality=1.0,
|
|
||||||
rrf_score=0.02,
|
|
||||||
rerank_score=92,
|
|
||||||
final_score=90,
|
|
||||||
sources=["reddit"],
|
|
||||||
source_items=[item],
|
|
||||||
fun_score=35.0, # parent below threshold
|
|
||||||
)
|
|
||||||
report = self._make_report_with_candidates([candidate])
|
|
||||||
text = render.render_compact(report)
|
|
||||||
self.assertIn("## Best Takes", text)
|
|
||||||
self.assertIn("WHAT?! I reached my monthly limit", text)
|
|
||||||
self.assertIn("2,304", text)
|
|
||||||
self.assertIn("r/test", text)
|
|
||||||
self.assertIn('in "Post c1"', text)
|
|
||||||
|
|
||||||
def test_comment_and_candidate_gems_sorted_by_fun_score(self):
|
|
||||||
"""Higher fun score comes first, regardless of whether it's a candidate or a comment."""
|
|
||||||
item = schema.SourceItem(
|
|
||||||
item_id="item-c1",
|
|
||||||
source="reddit",
|
|
||||||
title="Post c1",
|
|
||||||
body="Body.",
|
|
||||||
url="https://reddit.com/r/test/comments/c1/",
|
|
||||||
container="test",
|
|
||||||
engagement={"score": 100},
|
|
||||||
metadata={
|
|
||||||
"top_comments": [{
|
|
||||||
"body": "higher-ranked comment",
|
|
||||||
"score": 800,
|
|
||||||
"fun_score": 90.0,
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
comment_candidate = schema.Candidate(
|
|
||||||
candidate_id="cc",
|
|
||||||
item_id="item-c1",
|
|
||||||
source="reddit",
|
|
||||||
title="Parent thread title",
|
|
||||||
url="https://reddit.com/r/test/comments/c1/",
|
|
||||||
snippet="",
|
|
||||||
subquery_labels=["primary"],
|
|
||||||
native_ranks={"primary:reddit": 1},
|
|
||||||
local_relevance=0.9,
|
|
||||||
freshness=90,
|
|
||||||
engagement=10,
|
|
||||||
source_quality=1.0,
|
|
||||||
rrf_score=0.02,
|
|
||||||
sources=["reddit"],
|
|
||||||
source_items=[item],
|
|
||||||
fun_score=30.0,
|
|
||||||
)
|
|
||||||
lower_candidate = self._make_candidate("cl", fun_score=70)
|
|
||||||
report = self._make_report_with_candidates([comment_candidate, lower_candidate])
|
|
||||||
text = render.render_compact(report)
|
|
||||||
comment_pos = text.find("higher-ranked comment")
|
|
||||||
lower_pos = text.find("(fun:70)")
|
|
||||||
self.assertNotEqual(comment_pos, -1)
|
|
||||||
self.assertNotEqual(lower_pos, -1)
|
|
||||||
self.assertLess(comment_pos, lower_pos)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -9,12 +9,7 @@ SCRIPTS_DIR = Path(__file__).parent.parent / "scripts"
|
|||||||
sys.path.insert(0, str(SCRIPTS_DIR))
|
sys.path.insert(0, str(SCRIPTS_DIR))
|
||||||
|
|
||||||
from lib import schema
|
from lib import schema
|
||||||
from lib.rerank import (
|
from lib.rerank import _apply_single_fun_fallback, _extract_comment_text
|
||||||
_apply_fun_fallback,
|
|
||||||
_apply_single_fun_fallback,
|
|
||||||
_extract_comment_text,
|
|
||||||
_score_comments_per_candidate,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _make_candidate(
|
def _make_candidate(
|
||||||
@@ -22,12 +17,10 @@ def _make_candidate(
|
|||||||
snippet: str = "",
|
snippet: str = "",
|
||||||
engagement: float | None = 0.0,
|
engagement: float | None = 0.0,
|
||||||
top_comments: list[dict] | None = None,
|
top_comments: list[dict] | None = None,
|
||||||
parent_raw_engagement: int | None = None,
|
|
||||||
) -> schema.Candidate:
|
) -> schema.Candidate:
|
||||||
"""Build a minimal Candidate with optional source_items carrying top_comments."""
|
"""Build a minimal Candidate with optional source_items carrying top_comments."""
|
||||||
source_items = []
|
source_items = []
|
||||||
if top_comments is not None:
|
if top_comments is not None:
|
||||||
item_engagement = {"score": parent_raw_engagement} if parent_raw_engagement is not None else {}
|
|
||||||
source_items.append(
|
source_items.append(
|
||||||
schema.SourceItem(
|
schema.SourceItem(
|
||||||
item_id="si-1",
|
item_id="si-1",
|
||||||
@@ -35,7 +28,6 @@ def _make_candidate(
|
|||||||
title=title,
|
title=title,
|
||||||
body="",
|
body="",
|
||||||
url="https://reddit.com/r/test/1",
|
url="https://reddit.com/r/test/1",
|
||||||
engagement=item_engagement,
|
|
||||||
metadata={"top_comments": top_comments},
|
metadata={"top_comments": top_comments},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -120,89 +112,6 @@ class TestFunFallbackCommentText:
|
|||||||
assert candidate.fun_explanation == "heuristic-fallback"
|
assert candidate.fun_explanation == "heuristic-fallback"
|
||||||
|
|
||||||
|
|
||||||
class TestScoreCommentsPerCandidate:
|
|
||||||
"""Per-comment fun scoring: high-ratio viral comments outrank absolute-high comments on dominant threads."""
|
|
||||||
|
|
||||||
def test_high_ratio_comment_outranks_low_ratio(self):
|
|
||||||
"""A 2304-upvote comment on a 300-upvote parent should score higher than
|
|
||||||
a 400-upvote comment on a 3400-upvote parent (high ratio = viral wit)."""
|
|
||||||
viral = _make_candidate(
|
|
||||||
parent_raw_engagement=300,
|
|
||||||
top_comments=[{"body": "WHAT?! I reached my monthly limit just reading this post", "score": 2304}],
|
|
||||||
)
|
|
||||||
average = _make_candidate(
|
|
||||||
parent_raw_engagement=3400,
|
|
||||||
top_comments=[{"body": "we can't trust benchmarks anymore and need to re-run them", "score": 400}],
|
|
||||||
)
|
|
||||||
_score_comments_per_candidate(viral)
|
|
||||||
_score_comments_per_candidate(average)
|
|
||||||
viral_score = viral.source_items[0].metadata["top_comments"][0]["fun_score"]
|
|
||||||
average_score = average.source_items[0].metadata["top_comments"][0]["fun_score"]
|
|
||||||
assert viral_score > average_score
|
|
||||||
|
|
||||||
def test_2304_upvote_comment_on_small_parent_crosses_medium_threshold(self):
|
|
||||||
"""The exact Opus 4.7 case: the comment should score >= 55 (medium threshold)."""
|
|
||||||
candidate = _make_candidate(
|
|
||||||
parent_raw_engagement=300,
|
|
||||||
top_comments=[{
|
|
||||||
"body": "WHAT?! I reached my monthly limit just reading this post",
|
|
||||||
"score": 2304,
|
|
||||||
}],
|
|
||||||
)
|
|
||||||
_score_comments_per_candidate(candidate)
|
|
||||||
comment = candidate.source_items[0].metadata["top_comments"][0]
|
|
||||||
assert comment["fun_score"] >= 55.0
|
|
||||||
|
|
||||||
def test_reddit_excerpt_field_also_works(self):
|
|
||||||
"""Reddit uses 'excerpt' not 'body'. The scorer must handle that."""
|
|
||||||
candidate = _make_candidate(
|
|
||||||
parent_raw_engagement=100,
|
|
||||||
top_comments=[{"excerpt": "bruh this is gold", "score": 500}],
|
|
||||||
)
|
|
||||||
_score_comments_per_candidate(candidate)
|
|
||||||
comment = candidate.source_items[0].metadata["top_comments"][0]
|
|
||||||
assert "fun_score" in comment
|
|
||||||
assert comment["fun_score"] > 0
|
|
||||||
|
|
||||||
def test_no_parent_upvotes_does_not_crash(self):
|
|
||||||
"""A candidate without parent engagement still scores comments via the absolute-upvote fallback."""
|
|
||||||
candidate = _make_candidate(
|
|
||||||
parent_raw_engagement=None,
|
|
||||||
top_comments=[{"body": "lmao", "score": 200}],
|
|
||||||
)
|
|
||||||
_score_comments_per_candidate(candidate)
|
|
||||||
comment = candidate.source_items[0].metadata["top_comments"][0]
|
|
||||||
assert "fun_score" in comment
|
|
||||||
|
|
||||||
def test_malformed_comments_skipped(self):
|
|
||||||
"""Non-dict entries and missing-body entries are skipped without raising."""
|
|
||||||
candidate = _make_candidate(
|
|
||||||
parent_raw_engagement=500,
|
|
||||||
top_comments=[
|
|
||||||
"not a dict", # malformed, still counts toward the top-3 window
|
|
||||||
{"body": "", "score": 10}, # empty body within window
|
|
||||||
{"body": "valid", "score": 50}, # valid within window
|
|
||||||
],
|
|
||||||
)
|
|
||||||
_score_comments_per_candidate(candidate)
|
|
||||||
comments = candidate.source_items[0].metadata["top_comments"]
|
|
||||||
# Only the valid one gets a fun_score
|
|
||||||
valid = [c for c in comments if isinstance(c, dict) and c.get("fun_score") is not None]
|
|
||||||
assert len(valid) == 1
|
|
||||||
assert valid[0]["body"] == "valid"
|
|
||||||
|
|
||||||
def test_score_comments_runs_after_fallback(self):
|
|
||||||
"""_apply_fun_fallback wires in comment scoring automatically."""
|
|
||||||
candidate = _make_candidate(
|
|
||||||
parent_raw_engagement=300,
|
|
||||||
top_comments=[{"body": "bro what 😭", "score": 1500}],
|
|
||||||
)
|
|
||||||
_apply_fun_fallback([candidate])
|
|
||||||
comment = candidate.source_items[0].metadata["top_comments"][0]
|
|
||||||
assert "fun_score" in comment
|
|
||||||
assert candidate.fun_score is not None
|
|
||||||
|
|
||||||
|
|
||||||
class TestExtractCommentText:
|
class TestExtractCommentText:
|
||||||
"""Verify _extract_comment_text handles edge cases."""
|
"""Verify _extract_comment_text handles edge cases."""
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user