(
checkfile: Dict[str, Any],
profile: Dict[str, Any],
selected_papers: Optional[List[Dict]],
)
| 1226 | |
| 1227 | def _trigger_satisfied( |
| 1228 | checkfile: Dict[str, Any], |
| 1229 | profile: Dict[str, Any], |
| 1230 | selected_papers: Optional[List[Dict]], |
| 1231 | ) -> bool: |
| 1232 | trigger = checkfile.get("trigger") or {} |
| 1233 | trigger_type = str(trigger.get("type") or "").strip() |
| 1234 | if not trigger_type: |
| 1235 | return True |
| 1236 | |
| 1237 | min_count = max(1, int(trigger.get("min_count", 1) or 1)) |
| 1238 | |
| 1239 | if trigger_type in {"selected_paper_topic_match", "direction_exposure"}: |
| 1240 | pool = list((checkfile.get("params") or {}).get("new_directions_pool", []) or []) + list( |
| 1241 | (checkfile.get("params") or {}).get("emerging_topics_pool", []) or [] |
| 1242 | ) |
| 1243 | return _keyword_hit_count(selected_papers, pool) >= min_count |
| 1244 | |
| 1245 | if trigger_type == "keyword_frequency": |
| 1246 | pool = list((checkfile.get("params") or {}).get("new_keywords_pool", []) or []) |
| 1247 | return _keyword_hit_count(selected_papers, pool) >= min_count |
| 1248 | |
| 1249 | if trigger_type == "author_exposure": |
| 1250 | pool = list((checkfile.get("params") or {}).get("new_authors_pool", []) or []) |
| 1251 | return _keyword_hit_count(selected_papers, pool) >= min_count |
| 1252 | |
| 1253 | if trigger_type == "inactive_topic_decay": |
| 1254 | window_episodes = max(2, int(trigger.get("window_episodes", 5) or 5)) |
| 1255 | min_total_selected = max(1, int(trigger.get("min_total_selected", 3) or 3)) |
| 1256 | stale_topics_pool = _canonicalize_topics( |
| 1257 | list((checkfile.get("params") or {}).get("stale_topics_pool", []) or []) |
| 1258 | ) |
| 1259 | if not stale_topics_pool: |
| 1260 | stale_topics_pool = _canonicalize_topics( |
| 1261 | list((profile.get("drift_plan", {}) or {}).get("downweight_topics", []) or []) |
| 1262 | ) |
| 1263 | if not stale_topics_pool: |
| 1264 | stale_topics_pool = list((profile.get("core_directions", {}) or {}).keys()) |
| 1265 | |
| 1266 | staleness_payload = _compute_topic_staleness( |
| 1267 | profile, |
| 1268 | stale_topics_pool=stale_topics_pool, |
| 1269 | window_episodes=window_episodes, |
| 1270 | min_total_selected=min_total_selected, |
| 1271 | ) |
| 1272 | profile.setdefault("drift_state", {}) |
| 1273 | profile["drift_state"]["decayed_topics"] = staleness_payload["decayed_topics"] |
| 1274 | profile["drift_state"]["topic_staleness"] = staleness_payload["topic_staleness"] |
| 1275 | # Time decay alone should not start observing; it only contributes |
| 1276 | # stale-topic evidence that later ranking and combined triggers can use. |
| 1277 | return False |
| 1278 | |
| 1279 | if trigger_type == "low_engagement": |
| 1280 | min_skipped = int(trigger.get("min_skipped_papers", 5) or 5) |
| 1281 | selected_count = len(selected_papers or []) |
| 1282 | return selected_count <= max(0, min_skipped // 2) |
| 1283 | |
| 1284 | return False |
| 1285 |
no test coverage detected