Refine Doris dashboard story dedupe

This commit is contained in:
Fizzlepoof
2026-05-22 00:16:03 +00:00
parent 9dc5a9a8a3
commit f88b90b4d8

View File

@@ -1534,10 +1534,11 @@ def focus_card(title:str,kicker:str,headline:str,body:str,tone:str='',href:str='
return f"<a class='focus-card-link' href='{esc(href)}'{target_attr}>{card_html}</a>" return f"<a class='focus-card-link' href='{esc(href)}'{target_attr}>{card_html}</a>"
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'] 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'] 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() cron_summary=hermes_cron_summary()
session_window=find_usage_window(hermes.get('account_windows',[]), 'session') if hermes.get('ok') else None 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 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_headline='Hermes telemetry unavailable'
hermes_body='Could not read local Hermes usage stats right now.' hermes_body='Could not read local Hermes usage stats right now.'
local_state='attention' if local_story else 'ok' 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' 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') 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' 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( focus_card(
'Local watch', 'Local watch',
'Useful nearby', 'Useful nearby',
(local_story.get('title') if local_story else 'Local lane quiet'), local_headline,
(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_body,
f'local {local_state}'.strip(), f'local {local_state}'.strip(),
href=str(local_story.get('url') or '') if local_story else '' , href=str(local_story.get('url') or '') if local_story else '' ,
target='_blank' if local_story and local_story.get('url') 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 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: def build_feature_story(feature_item:dict[str,Any] | None)->str:
if not feature_item: if not feature_item:
return '' 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',' ')) when=(feature_item.get('published_at','')[:16].replace('T',' '))
category=str(feature_item.get('category') or 'other') category=str(feature_item.get('category') or 'other')
url=str(feature_item.get('url') or '') 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) why_text=why(feature_item)
return f""" return f"""
<section class='feature-story'> <section class='feature-story'>
@@ -1695,7 +1735,7 @@ def build_feature_story(feature_item:dict[str,Any] | None)->str:
</div> </div>
<div class='feature-actions'> <div class='feature-actions'>
<a href='{esc(url)}' target='_blank' rel='noreferrer' class='feature-open'>Open story</a> <a href='{esc(url)}' target='_blank' rel='noreferrer' class='feature-open'>Open story</a>
<span class='feature-note'>Promoted so you get one strong item instead of four equal tiles.</span> <span class='feature-note'>Secondary signal so the warrant and the next-click card are not the same story.</span>
</div> </div>
</div> </div>
</article> </article>
@@ -2105,15 +2145,17 @@ def render()->str:
pulse_metrics=''.join(metric_card(x['label'],x['value'],x['state'],x['state']) for x in pulse) pulse_metrics=''.join(metric_card(x['label'],x['value'],x['state'],x['state']) for x in pulse)
forecast_html=forecast_panel(weather) forecast_html=forecast_panel(weather)
hero_summary=build_hero_summary(groups['local'],todos,pulse,paperless,hermes) 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) focus_signature=donetick_focus_signature(todos)
hermes_html=build_hermes_section(hermes) hermes_html=build_hermes_section(hermes)
system_html=build_system_section(system) system_html=build_system_section(system)
hero_tokens=compact_number(hermes.get('total_tokens',0)) if hermes.get('ok') else '' hero_tokens=compact_number(hermes.get('total_tokens',0)) if hermes.get('ok') else ''
snapshot_html=build_operator_snapshot(todos,pulse,paperless,hermes) 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) 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 session_window=find_usage_window(hermes.get('account_windows',[]),'session') if hermes.get('ok') else None
primary_html=''.join([ primary_html=''.join([
feature_html, feature_html,