diff --git a/home/doris-dashboard/bin/generate_dashboard.py b/home/doris-dashboard/bin/generate_dashboard.py index f215c2b..d8ebc10 100755 --- a/home/doris-dashboard/bin/generate_dashboard.py +++ b/home/doris-dashboard/bin/generate_dashboard.py @@ -1534,10 +1534,11 @@ def focus_card(title:str,kicker:str,headline:str,body:str,tone:str='',href:str=' return f"{card_html}" -def build_focus_cards(local_items:list[dict[str,Any]], todos:list[dict[str,Any]], pulse:list[dict[str,str]], paperless:dict[str,Any], hermes:dict[str,Any])->tuple[str,str]: +def build_focus_cards(local_items:list[dict[str,Any]], todos:list[dict[str,Any]], pulse:list[dict[str,str]], paperless:dict[str,Any], hermes:dict[str,Any], excluded_story_keys:set[str] | None=None)->tuple[str,str]: urgent_todos=[item for item in todos if (item.get('state') or '') in {'overdue','today'} and (item.get('source') or '') != 'Dashboard'] warn_items=[item for item in pulse if item.get('state')=='warn'] - local_story=local_items[0] if local_items else None + excluded_story_keys=excluded_story_keys or set() + local_story=next((item for item in local_items if story_key(item) not in excluded_story_keys), None) cron_summary=hermes_cron_summary() session_window=find_usage_window(hermes.get('account_windows',[]), 'session') if hermes.get('ok') else None session_remaining=session_window.get('remaining_percent') if session_window else None @@ -1564,6 +1565,12 @@ def build_focus_cards(local_items:list[dict[str,Any]], todos:list[dict[str,Any]] hermes_headline='Hermes telemetry unavailable' hermes_body='Could not read local Hermes usage stats right now.' local_state='attention' if local_story else 'ok' + local_headline=str(local_story.get('title') or '') if local_story else ('Local lane already covered' if local_items else 'Local lane quiet') + local_body=( + f"{local_story.get('source','Local feed')} • {label(local_story.get('category','local'))}" + if local_story else + ('The strongest local item is already promoted above, so this slot stays out of the way.' if local_items else 'Nothing local survived filtering hard enough to earn the homepage.') + ) chores_state='warn' if urgent_todos else 'ok' paperless_state='warn' if paperless.get('flagged_count',0) else ('ok' if not paperless.get('active_count',0) else 'attention') homelab_state='warn' if warn_items else 'ok' @@ -1573,8 +1580,8 @@ def build_focus_cards(local_items:list[dict[str,Any]], todos:list[dict[str,Any]] focus_card( 'Local watch', 'Useful nearby', - (local_story.get('title') if local_story else 'Local lane quiet'), - (f"{local_story.get('source','Local feed')} • {label(local_story.get('category','local'))}" if local_story else 'Nothing local survived filtering hard enough to earn the homepage.'), + local_headline, + local_body, f'local {local_state}'.strip(), href=str(local_story.get('url') or '') if local_story else '' , target='_blank' if local_story and local_story.get('url') else '' @@ -1671,6 +1678,39 @@ def choose_feature_story(preferred_items:list[dict[str,Any]], fallback_items:lis return None +def choose_secondary_feature_story(preferred_items:list[dict[str,Any]], fallback_items:list[dict[str,Any]], primary_story:dict[str,Any] | None, prefs:dict[str,Any])->dict[str,Any] | None: + primary_key=story_key(primary_story) if primary_story else '' + state=load_lead_warrant_state() + feedback=load_dashboard_feedback() + acknowledged=set(state.get('acknowledged',[])) + ignored_sources=set(state.get('ignored_sources',[])) + ignored_topics=set(state.get('ignored_topics',[])) + candidates=[] + fallback=[] + seen=set() + for pool_name,pool in (('preferred',preferred_items),('fallback',fallback_items)): + for item in pool: + key=story_key(item) + dedupe=key or f"{str(item.get('source') or '').strip().lower()}::{str(item.get('title') or '').strip().lower()}" + if dedupe in seen or (primary_key and key == primary_key): + continue + seen.add(dedupe) + fallback.append(item) + if good_lead_candidate(item,prefs,feedback,ignored_sources,ignored_topics): + candidates.append(item) + for item in candidates: + if story_key(item) not in acknowledged: + return item + if candidates: + return candidates[0] + for item in fallback: + if story_key(item) not in acknowledged: + return item + if fallback: + return fallback[0] + return None + + def build_feature_story(feature_item:dict[str,Any] | None)->str: if not feature_item: return '' @@ -1680,7 +1720,7 @@ def build_feature_story(feature_item:dict[str,Any] | None)->str: when=(feature_item.get('published_at','')[:16].replace('T',' ')) category=str(feature_item.get('category') or 'other') url=str(feature_item.get('url') or '') - feature_label='Lead local item' if category=='local' else ('Lead homelab item' if category in {'homelab','smart_home','radio'} else 'Lead signal') + feature_label='Next local signal' if category=='local' else ('Next homelab signal' if category in {'homelab','smart_home','radio'} else 'Next signal') why_text=why(feature_item) return f"""
@@ -1695,7 +1735,7 @@ def build_feature_story(feature_item:dict[str,Any] | None)->str:
Open story - Promoted so you get one strong item instead of four equal tiles. + Secondary signal so the warrant and the next-click card are not the same story.
@@ -2105,15 +2145,17 @@ def render()->str: pulse_metrics=''.join(metric_card(x['label'],x['value'],x['state'],x['state']) for x in pulse) forecast_html=forecast_panel(weather) hero_summary=build_hero_summary(groups['local'],todos,pulse,paperless,hermes) - focus_html,focus_state=build_focus_cards(groups['local'],todos,pulse,paperless,hermes) + feature_item=choose_feature_story(top_briefing, selected, prefs) + secondary_feature_item=choose_secondary_feature_story(top_briefing, selected, feature_item, prefs) + excluded_focus_story_keys={key for key in [story_key(feature_item), story_key(secondary_feature_item)] if key} + focus_html,focus_state=build_focus_cards(groups['local'],todos,pulse,paperless,hermes,excluded_story_keys=excluded_focus_story_keys) focus_signature=donetick_focus_signature(todos) hermes_html=build_hermes_section(hermes) system_html=build_system_section(system) hero_tokens=compact_number(hermes.get('total_tokens',0)) if hermes.get('ok') else '—' snapshot_html=build_operator_snapshot(todos,pulse,paperless,hermes) - feature_item=choose_feature_story(top_briefing, selected, prefs) hero_caseboard_html=build_hero_caseboard(feature_item,todos,paperless) - feature_html=build_feature_story(feature_item) + feature_html=build_feature_story(secondary_feature_item) session_window=find_usage_window(hermes.get('account_windows',[]),'session') if hermes.get('ok') else None primary_html=''.join([ feature_html,