Skip to content

Commit c29cfc9

Browse files
committed
Update feed to best-of-3
1 parent 86a1556 commit c29cfc9

File tree

2 files changed

+62
-33
lines changed

2 files changed

+62
-33
lines changed

api/check_status.py

Lines changed: 20 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
# Add parent directory to path for lib imports
2222
sys.path.insert(0, str(PROJECT_ROOT))
23-
from lib.muni_lib import download_muni_image, detect_muni_status, read_cache, write_cache, write_cached_image
23+
from lib.muni_lib import download_muni_image, detect_muni_status, read_cache, write_cache, write_cached_image, calculate_best_status
2424
from lib.notifiers import notify_status_change
2525

2626
# Configuration
@@ -111,41 +111,26 @@ def check_status(should_write_cache=False):
111111
'timestamp': datetime.now().isoformat()
112112
}
113113

114-
# Read existing cache to get previous statuses
114+
# Read existing cache to get previous statuses and best_status
115115
statuses = []
116-
previous_status = None
116+
previous_best_status = None
117117
cache_data = read_cache()
118118
if cache_data:
119-
# Get previous statuses from cache
120-
if 'statuses' in cache_data and len(cache_data['statuses']) > 0:
121-
previous_status = cache_data['statuses'][0]['status']
122-
# Keep existing statuses (will be trimmed below)
119+
if 'statuses' in cache_data:
123120
statuses = cache_data['statuses'][:]
121+
if 'best_status' in cache_data:
122+
previous_best_status = cache_data['best_status']
124123

125124
# Add new status at the front
126125
statuses.insert(0, new_status)
127126

127+
# Calculate best status using shared function
128+
# This ensures webapp, RSS, and Bluesky all show the same status
129+
best_status = calculate_best_status(statuses, window_size=3)
130+
128131
# Keep only last 3 statuses (~1.5 min window at 30s intervals)
129-
# This filters out brief transient issues
130132
statuses = statuses[:3]
131133

132-
# Determine best status (most optimistic)
133-
# Priority: green > yellow > red
134-
status_priority = {'green': 3, 'yellow': 2, 'red': 1}
135-
best_status_value = max([s['status'] for s in statuses], key=lambda x: status_priority.get(x, 0))
136-
137-
# Find the most recent entry with the best status
138-
# This ensures we use the most recent delay info if status is yellow
139-
best_status = None
140-
for s in statuses: # statuses[0] is most recent
141-
if s['status'] == best_status_value:
142-
best_status = s
143-
break
144-
145-
# Fallback to most recent if somehow not found
146-
if best_status is None:
147-
best_status = statuses[0]
148-
149134
# Write cache with status history
150135
cache_data = {
151136
'statuses': statuses,
@@ -166,16 +151,18 @@ def check_status(should_write_cache=False):
166151
else:
167152
print(f"\nCache write failed")
168153

169-
# Notify all channels if status changed
170-
current_status = detection['status']
171-
if previous_status is not None and current_status != previous_status:
172-
print(f"\nStatus changed: {previous_status} -> {current_status}")
173-
delay_summaries = detection.get('detection', {}).get('delay_summaries', [])
154+
# Notify all channels if BEST status changed
155+
# This ensures notifications match what the webapp shows
156+
current_best = best_status['status']
157+
previous_best = previous_best_status['status'] if previous_best_status else None
158+
if previous_best is not None and current_best != previous_best:
159+
print(f"\nBest status changed: {previous_best} -> {current_best}")
160+
delay_summaries = best_status.get('detection', {}).get('delay_summaries', [])
174161
notify_results = notify_status_change(
175-
status=current_status,
176-
previous_status=previous_status,
162+
status=current_best,
163+
previous_status=previous_best,
177164
delay_summaries=delay_summaries,
178-
timestamp=new_status['timestamp']
165+
timestamp=best_status['timestamp']
179166
)
180167
for channel, result in notify_results.items():
181168
if result['success']:

lib/muni_lib.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,48 @@ def read_cached_image():
216216
return None
217217

218218

219+
# Status priority for "best of N" calculation
220+
# Higher number = more optimistic status
221+
STATUS_PRIORITY = {'green': 3, 'yellow': 2, 'red': 1}
222+
223+
224+
def calculate_best_status(statuses, window_size=3):
225+
"""
226+
Calculate the best (most optimistic) status from a list of recent statuses.
227+
228+
This implements the "best of N" smoothing to filter out brief transient issues.
229+
The webapp, RSS feed, and Bluesky all use this to ensure consistent status reporting.
230+
231+
Args:
232+
statuses: List of status dicts, most recent first. Each must have 'status' key.
233+
window_size: Number of recent statuses to consider (default 3)
234+
235+
Returns:
236+
dict: The status entry with the most optimistic status within the window,
237+
preferring the most recent if there are ties. Returns None if empty.
238+
"""
239+
if not statuses:
240+
return None
241+
242+
# Only consider the most recent N statuses
243+
recent = statuses[:window_size]
244+
245+
# Find the best (most optimistic) status value
246+
best_value = max(
247+
[s['status'] for s in recent],
248+
key=lambda x: STATUS_PRIORITY.get(x, 0)
249+
)
250+
251+
# Return the most recent entry with that status
252+
# This ensures we use the most recent delay info if status is yellow
253+
for s in recent:
254+
if s['status'] == best_value:
255+
return s
256+
257+
# Fallback (shouldn't happen)
258+
return recent[0]
259+
260+
219261
# Backward compatibility: import post_to_bluesky from notifiers module
220262
# This allows existing code to continue using: from lib.muni_lib import post_to_bluesky
221263
from lib.notifiers import post_to_bluesky

0 commit comments

Comments
 (0)