| 43 |
43 |
PER_JOB_EVENT_MAX = 5 # NEW_JOB per job only when ≤ 5 jobs added |
| 44 |
44 |
PER_PERSON_EVENT_MAX = 10 |
| 45 |
45 |
NEWS_ITEMS_MAX = 20 |
|
46 |
+NEWS_ITEMS_PER_EVENT_MAX = 3 # more items than this in one observation → one aggregate communication event |
| 46 |
47 |
LEADERSHIP_AGGREGATE_MIN = 3 |
| 47 |
48 |
SURGE_MIN_JOBS = 10 # fallback thresholds when no baseline is available yet |
| 48 |
49 |
SURGE_MIN_RATIO = 0.5 |
| 525 |
526 |
prefix = {"NEWS_RELEASE": "News release", "BLOG_POST": "Blog post", "CHANGELOG_ENTRY": "Changelog entry", "INVESTOR_UPDATE": "Investor update", |
| 526 |
527 |
"EARNINGS_RELEASE": "Earnings release"} |
| 527 |
528 |
out: list[EventDraft] = [] |
| 528 |
|
− for item in list(news.get("added") or [])[:NEWS_ITEMS_MAX]: |
|
529 |
+ added = [i for i in list(news.get("added") or []) if (i.get("title") or "").strip()] |
|
530 |
+ if len(added) > NEWS_ITEMS_PER_EVENT_MAX: |
|
531 |
+ # A burst of items in one observation (catalogue re-listing, archive page, many posts at once) is one communication event, |
|
532 |
+ # not a flood: individual titles stay in `entities.news` for the evidence drawer. |
|
533 |
+ subtypes = [_news_subtype(i, surface) for i in added] |
|
534 |
+ subtype = max(set(subtypes), key=subtypes.count) |
|
535 |
+ label = {"NEWS_RELEASE": "news releases", "BLOG_POST": "blog posts", "CHANGELOG_ENTRY": "changelog entries", "INVESTOR_UPDATE": "investor updates", |
|
536 |
+ "EARNINGS_RELEASE": "earnings releases"}[subtype] |
|
537 |
+ titles = [(i.get("title") or "").strip() for i in added] |
|
538 |
+ tags = ["news", subtype.lower()] |
|
539 |
+ if any(_is_ai(t) for t in titles): |
|
540 |
+ tags.append("ai") |
|
541 |
+ if any(_LAUNCH_RE.search(t) for t in titles): |
|
542 |
+ tags.append("launch") |
|
543 |
+ out.append(EventDraft( |
|
544 |
+ subtype=subtype, title=f"{len(added)} new {label} published on {_label(surface)}", |
|
545 |
+ entity_key=f"news_batch:{normalize_entity_key(titles[0])}:{len(added)}", evidence=evidence, |
|
546 |
+ summary="Latest: " + " · ".join(t[:80] for t in titles[:3]) + (" …" if len(titles) > 3 else ""), |
|
547 |
+ entities={"news": [{"title": i.get("title"), "url": i.get("url"), "published_at": i.get("published_at")} for i in added[:NEWS_ITEMS_MAX]]}, |
|
548 |
+ payload={"count": len(added), "category": added[0].get("category")}, tags=tags, magnitude=min(1.0, len(added) / 20))) |
|
549 |
+ return out |
|
550 |
+ for item in added[:NEWS_ITEMS_MAX]: |
| 529 |
551 |
title = (item.get("title") or "").strip() |
| 530 |
|
− if not title: |
| 531 |
|
− continue |
| 532 |
552 |
subtype = _news_subtype(item, surface) |
| 533 |
553 |
tags = ["news", subtype.lower()] |
| 534 |
554 |
if _is_ai(title): |