| """Digest renderer — turns top-N ranked items into a readable briefing.""" |
| from __future__ import annotations |
|
|
| import json |
|
|
| from config import DIGEST_TOP_N |
| from rank import RankerConfig, _chat |
|
|
|
|
| _DIGEST_SYSTEM = "You write tight, useful AI-news briefings. No fluff." |
|
|
|
|
| _DIGEST_PROMPT = """Write a 2-hour AI-news briefing from the items below. |
| |
| RULES: |
| - Group by theme if obvious (Models / Research / Tools / Industry); otherwise a flat list. |
| - Each item: 1-2 lines in plain English. End the item with the URL on its own line. |
| - Lead with WHAT CHANGED and WHY IT MATTERS — not the source name. |
| - No markdown headers, no bold asterisks. Optional bullet (•). |
| - Skip items that are obvious duplicates or hype with no concrete new info. |
| - Close with a one-line meta note ("3 from labs, 2 from research, 1 from tools" style). |
| - Target ~1500 chars total. Stay short. Skip filler. |
| |
| Items (ranked by importance, highest first): |
| {items_json} |
| """ |
|
|
|
|
| def make_digest(ranked: list[dict], cfg: RankerConfig | None = None) -> str: |
| """Render the top-N ranked items as a readable briefing.""" |
| if not ranked: |
| return "_(no high-signal items in window)_" |
| cfg = cfg or RankerConfig() |
| top = ranked[:DIGEST_TOP_N] |
| indexed = [ |
| { |
| "source": it.get("source", ""), |
| "title": (it.get("title") or "")[:200], |
| "url": it.get("url", ""), |
| "summary": (it.get("summary") or "")[:300], |
| "score": it.get("score", 5), |
| "reason": it.get("reason", ""), |
| } |
| for it in top |
| ] |
| return _chat( |
| cfg, |
| _DIGEST_SYSTEM, |
| _DIGEST_PROMPT.format(items_json=json.dumps(indexed, ensure_ascii=False, indent=2)), |
| json_mode=False, |
| temperature=0.3, |
| max_tokens=2000, |
| ).strip() |
|
|