DinoPLayZ commited on
Commit
6da6d3e
·
verified ·
1 Parent(s): 6620d68

Update event_state_tracker.py

Browse files
Files changed (1) hide show
  1. event_state_tracker.py +222 -222
event_state_tracker.py CHANGED
@@ -1,223 +1,223 @@
1
- import os
2
- import json
3
- import logging
4
- from datetime import datetime
5
- from main import send_email_message
6
-
7
- logger = logging.getLogger(__name__)
8
-
9
- STATE_FILE = "event_state.json"
10
-
11
- def load_state():
12
- """Loads the previous state of events from the JSON file."""
13
- if not os.path.exists(STATE_FILE):
14
- return {}
15
-
16
- try:
17
- with open(STATE_FILE, "r", encoding="utf-8") as f:
18
- return json.load(f)
19
- except Exception as e:
20
- logger.warning(f"Failed to load {STATE_FILE}, starting fresh. Error: {e}")
21
- return {}
22
-
23
- def save_state(state):
24
- """Saves the current state of events to the JSON file."""
25
- try:
26
- with open(STATE_FILE, "w", encoding="utf-8") as f:
27
- json.dump(state, f, indent=4)
28
- except Exception as e:
29
- logger.error(f"Failed to save {STATE_FILE}. Error: {e}")
30
-
31
- def extract_tracked_fields(event):
32
- """Extracts only the fields we care about tracking."""
33
- return {
34
- "status": str(event.get("status", "Unknown")).strip(),
35
- "maximum_count": event.get("maximum_count", 0),
36
- "event_code": event.get("event_code", "-"),
37
- "event_name": event.get("event_name", "-")
38
- }
39
-
40
- def send_change_alert(event, changed_field, old_value, new_value):
41
- """Sends an HTML email alert when a tracked field changes."""
42
- recipient = os.getenv("STATUS_EVENT_EMAIL_RECIPIENT", "")
43
- if not recipient:
44
- logger.warning("No STATUS_EVENT_EMAIL_RECIPIENT configured. Skipping change alert.")
45
- return
46
-
47
- subject = f"🚨 Event {changed_field} Changed: {event.get('event_code', '-')} → {new_value}"
48
-
49
- def format_date(date_str):
50
- try:
51
- dt = datetime.strptime(date_str, "%Y-%m-%d")
52
- return dt.strftime('%d-%m-%Y')
53
- except:
54
- return date_str
55
-
56
- start = format_date(event.get("start_date", "")) if event.get("start_date") else ""
57
- end = format_date(event.get("end_date", "")) if event.get("end_date") else ""
58
- date_str = f"{start} to {end}" if start and end else (start or end or "-")
59
-
60
- try:
61
- max_count = int(event.get("maximum_count", 0))
62
- except (ValueError, TypeError):
63
- max_count = 0
64
-
65
- try:
66
- applied = int(event.get("applied_count", 0))
67
- except (ValueError, TypeError):
68
- applied = 0
69
-
70
- try:
71
- balance = int(event.get("ComputedField", 0))
72
- except (ValueError, TypeError):
73
- balance = 0
74
-
75
- body = f"""
76
- <div style="font-family: Arial, sans-serif; background-color: #f4f6f9; padding: 20px;">
77
- <div style="max-width: 600px; margin: 0 auto; background: #ffffff; border-radius: 8px;
78
- box-shadow: 0 4px 6px rgba(0,0,0,0.1); overflow: hidden;">
79
-
80
- <div style="background-color: #0056b3; color: #ffffff; padding: 15px 20px;
81
- font-size: 18px; font-weight: bold; text-align: center;">
82
- 🚨 Event {changed_field} Changed
83
- </div>
84
-
85
- <div style="padding: 20px;">
86
- <div style="background-color: #f0fdf4; padding: 15px; border-radius: 6px; margin-bottom: 20px; text-align: center; border: 1px solid #bbf7d0;">
87
- <span style="font-weight: bold; color: #166534;">{changed_field} Update:</span><br>
88
- <span style="font-size: 16px;">{old_value} &rarr; <b style="color: #15803d;">{new_value}</b></span>
89
- </div>
90
-
91
- <table style="width: 100%; border-collapse: collapse; margin-bottom: 25px; font-size: 15px; color: #333333; text-align: left;">
92
- <tr>
93
- <td style="padding: 8px 0; font-weight: bold; width: 35%;">Code</td>
94
- <td style="padding: 8px 0; width: 5%;">:</td>
95
- <td style="padding: 8px 0; width: 60%; font-weight: bold;">{event.get('event_code', '-')}</td>
96
- </tr>
97
- <tr>
98
- <td style="padding: 8px 0; font-weight: bold;">Name</td>
99
- <td style="padding: 8px 0;">:</td>
100
- <td style="padding: 8px 0;">{event.get('event_name', '-')}</td>
101
- </tr>
102
- <tr>
103
- <td style="padding: 8px 0; font-weight: bold;">Organizer</td>
104
- <td style="padding: 8px 0;">:</td>
105
- <td style="padding: 8px 0;">{event.get('organizer', '-')}</td>
106
- </tr>
107
- <tr>
108
- <td style="padding: 8px 0; font-weight: bold;">Date</td>
109
- <td style="padding: 8px 0;">:</td>
110
- <td style="padding: 8px 0;">{date_str}</td>
111
- </tr>
112
- <tr>
113
- <td style="padding: 8px 0; font-weight: bold;">Category</td>
114
- <td style="padding: 8px 0;">:</td>
115
- <td style="padding: 8px 0;">{event.get('event_category', '-')}</td>
116
- </tr>
117
- <tr>
118
- <td style="padding: 8px 0; font-weight: bold;">Location</td>
119
- <td style="padding: 8px 0;">:</td>
120
- <td style="padding: 8px 0;">{event.get('location', '-')}</td>
121
- </tr>
122
- <tr>
123
- <td style="padding: 8px 0; font-weight: bold;">Status</td>
124
- <td style="padding: 8px 0;">:</td>
125
- <td style="padding: 8px 0;">{event.get('status', '-')}</td>
126
- </tr>
127
- <tr>
128
- <td style="padding: 8px 0; font-weight: bold;">Logger URL</td>
129
- <td style="padding: 8px 0;">:</td>
130
- <td style="padding: 8px 0;">
131
- <a href="https://bip.bitsathy.ac.in/nova/resources/student-achievement-loggers/new"
132
- style="color: #0056b3; text-decoration: none;">Link</a>
133
- </td>
134
- </tr>
135
- <tr>
136
- <td style="padding: 8px 0; font-weight: bold;">View Link</td>
137
- <td style="padding: 8px 0;">:</td>
138
- <td style="padding: 8px 0;">
139
- <a href="{event.get('web_url', '#')}"
140
- style="color: #0056b3; text-decoration: none;">View Event Here</a>
141
- </td>
142
- </tr>
143
- <tr>
144
- <td style="padding: 12px 0; font-weight: bold;">Count</td>
145
- <td style="padding: 12px 0;">:</td>
146
- <td style="padding: 12px 0;">
147
- Max: <b>{max_count}</b> | Balance: <b>{balance}</b> | Applied: <b>{applied}</b>
148
- </td>
149
- </tr>
150
- </table>
151
- </div>
152
- <div style="background-color: #f8f9fa; padding: 15px 20px; text-align: center; font-size: 12px; color: #777777; border-top: 1px solid #e0e0e0;">
153
- Automated Notification from BIP Tracker
154
- </div>
155
- </div>
156
- </div>
157
- """
158
-
159
- logger.info(f"🚨 STATUS TRACKER: Sending {changed_field} alert for {event.get('event_code', '-')}")
160
- send_email_message(subject, body, is_html=True, recipient=recipient)
161
-
162
- def track_event_changes(events):
163
- """
164
- Analyzes a list of events fetched from the API, compares them to the
165
- previous state, and triggers alerts for any that just became 'Active'.
166
- """
167
- if not events:
168
- return
169
-
170
- state = load_state()
171
- state_changed = False
172
-
173
- for event in events:
174
- event_id = str(event.get("id"))
175
- if not event_id:
176
- continue
177
-
178
- current_data = extract_tracked_fields(event)
179
-
180
- # Hugging Face Restart Protection:
181
- # If the event ID doesn't exist in our state file at all, this might
182
- # be the first run, or the container just restarted and lost its file.
183
- # We record it, but we DO NOT trigger an alert to prevent false positives.
184
- if event_id not in state:
185
- state[event_id] = current_data
186
- state_changed = True
187
- logger.debug(f"Tracking new state for event {event_id} (No alert)")
188
- continue
189
-
190
- previous_data = state[event_id]
191
-
192
- # Detect if status changed
193
- old_status = str(previous_data.get("status", "")).strip()
194
- new_status = str(current_data.get("status", "")).strip()
195
-
196
- if old_status != new_status:
197
- # Only trigger alert if old_status has SOME value (not completely missing) to avoid edge noise
198
- if old_status:
199
- logger.info(f"Event {event_id} ({current_data.get('event_code', '-')}) changed status to {new_status}!")
200
- send_change_alert(event, "Status", old_status, new_status)
201
-
202
- # Detect if maximum_count increased
203
- try:
204
- old_max = int(previous_data.get("maximum_count", 0))
205
- except (ValueError, TypeError):
206
- old_max = 0
207
-
208
- try:
209
- new_max = int(current_data.get("maximum_count", 0))
210
- except (ValueError, TypeError):
211
- new_max = 0
212
-
213
- if new_max > old_max:
214
- logger.info(f"Event {event_id} ({current_data.get('event_code', '-')}) increased max count from {old_max} to {new_max}!")
215
- send_change_alert(event, "Capacity", str(old_max), str(new_max))
216
-
217
- # Update state if anything changed
218
- if current_data != previous_data:
219
- state[event_id] = current_data
220
- state_changed = True
221
-
222
- if state_changed:
223
  save_state(state)
 
1
+ import os
2
+ import json
3
+ import logging
4
+ from datetime import datetime
5
+ from main import send_email_message
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+ STATE_FILE = "event_state.json"
10
+
11
+ def load_state():
12
+ """Loads the previous state of events from the JSON file."""
13
+ if not os.path.exists(STATE_FILE):
14
+ return {}
15
+
16
+ try:
17
+ with open(STATE_FILE, "r", encoding="utf-8") as f:
18
+ return json.load(f)
19
+ except Exception as e:
20
+ logger.warning(f"Failed to load {STATE_FILE}, starting fresh. Error: {e}")
21
+ return {}
22
+
23
+ def save_state(state):
24
+ """Saves the current state of events to the JSON file."""
25
+ try:
26
+ with open(STATE_FILE, "w", encoding="utf-8") as f:
27
+ json.dump(state, f, indent=4)
28
+ except Exception as e:
29
+ logger.error(f"Failed to save {STATE_FILE}. Error: {e}")
30
+
31
+ def extract_tracked_fields(event):
32
+ """Extracts only the fields we care about tracking."""
33
+ return {
34
+ "status": str(event.get("status", "Unknown")).strip(),
35
+ "maximum_count": event.get("maximum_count", 0),
36
+ "event_code": event.get("event_code", "-"),
37
+ "event_name": event.get("event_name", "-")
38
+ }
39
+
40
+ def send_change_alert(event, changed_field, old_value, new_value):
41
+ """Sends an HTML email alert when a tracked field changes."""
42
+ recipient = os.getenv("STATUS_EVENT_EMAIL_RECIPIENT", "")
43
+ if not recipient:
44
+ logger.warning("No STATUS_EVENT_EMAIL_RECIPIENT configured. Skipping change alert.")
45
+ return
46
+
47
+ subject = f"🚨 Event {changed_field} Changed: {event.get('event_code', '-')} → {new_value}"
48
+
49
+ def format_date(date_str):
50
+ try:
51
+ dt = datetime.strptime(date_str, "%Y-%m-%d")
52
+ return dt.strftime('%d-%m-%Y')
53
+ except:
54
+ return date_str
55
+
56
+ start = format_date(event.get("start_date", "")) if event.get("start_date") else ""
57
+ end = format_date(event.get("end_date", "")) if event.get("end_date") else ""
58
+ date_str = f"{start} to {end}" if start and end else (start or end or "-")
59
+
60
+ try:
61
+ max_count = int(event.get("maximum_count", 0))
62
+ except (ValueError, TypeError):
63
+ max_count = 0
64
+
65
+ try:
66
+ applied = int(event.get("applied_count", 0))
67
+ except (ValueError, TypeError):
68
+ applied = 0
69
+
70
+ try:
71
+ balance = int(event.get("ComputedField", 0))
72
+ except (ValueError, TypeError):
73
+ balance = 0
74
+
75
+ body = f"""
76
+ <div style="font-family: Arial, sans-serif; background-color: #f4f6f9; padding: 20px;">
77
+ <div style="max-width: 600px; margin: 0 auto; background: #ffffff; border-radius: 8px;
78
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1); overflow: hidden;">
79
+
80
+ <div style="background-color: #0056b3; color: #ffffff; padding: 15px 20px;
81
+ font-size: 18px; font-weight: bold; text-align: center;">
82
+ 🚨 Event {changed_field} Changed
83
+ </div>
84
+
85
+ <div style="padding: 20px;">
86
+ <div style="background-color: #f0fdf4; padding: 15px; border-radius: 6px; margin-bottom: 20px; text-align: center; border: 1px solid #bbf7d0;">
87
+ <span style="font-weight: bold; color: #166534;">{changed_field} Update:</span><br>
88
+ <span style="font-size: 16px;">{old_value} &rarr; <b style="color: #15803d;">{new_value}</b></span>
89
+ </div>
90
+
91
+ <table style="width: 100%; border-collapse: collapse; margin-bottom: 25px; font-size: 15px; color: #333333; text-align: left;">
92
+ <tr>
93
+ <td style="padding: 8px 0; font-weight: bold; width: 35%;">Code</td>
94
+ <td style="padding: 8px 0; width: 5%;">:</td>
95
+ <td style="padding: 8px 0; width: 60%; font-weight: bold;">{event.get('event_code', '-')}</td>
96
+ </tr>
97
+ <tr>
98
+ <td style="padding: 8px 0; font-weight: bold;">Name</td>
99
+ <td style="padding: 8px 0;">:</td>
100
+ <td style="padding: 8px 0;">{event.get('event_name', '-')}</td>
101
+ </tr>
102
+ <tr>
103
+ <td style="padding: 8px 0; font-weight: bold;">Organizer</td>
104
+ <td style="padding: 8px 0;">:</td>
105
+ <td style="padding: 8px 0;">{event.get('organizer', '-')}</td>
106
+ </tr>
107
+ <tr>
108
+ <td style="padding: 8px 0; font-weight: bold;">Date</td>
109
+ <td style="padding: 8px 0;">:</td>
110
+ <td style="padding: 8px 0;">{date_str}</td>
111
+ </tr>
112
+ <tr>
113
+ <td style="padding: 8px 0; font-weight: bold;">Category</td>
114
+ <td style="padding: 8px 0;">:</td>
115
+ <td style="padding: 8px 0;">{event.get('event_category', '-')}</td>
116
+ </tr>
117
+ <tr>
118
+ <td style="padding: 8px 0; font-weight: bold;">Location</td>
119
+ <td style="padding: 8px 0;">:</td>
120
+ <td style="padding: 8px 0;">{event.get('location', '-')}</td>
121
+ </tr>
122
+ <tr>
123
+ <td style="padding: 8px 0; font-weight: bold;">Status</td>
124
+ <td style="padding: 8px 0;">:</td>
125
+ <td style="padding: 8px 0;">{event.get('status', '-')}</td>
126
+ </tr>
127
+ <tr>
128
+ <td style="padding: 8px 0; font-weight: bold;">Logger URL</td>
129
+ <td style="padding: 8px 0;">:</td>
130
+ <td style="padding: 8px 0;">
131
+ <a href="https://bip.bitsathy.ac.in/nova/resources/student-achievement-loggers/new"
132
+ style="color: #0056b3; text-decoration: none;">Link</a>
133
+ </td>
134
+ </tr>
135
+ <tr>
136
+ <td style="padding: 8px 0; font-weight: bold;">View Link</td>
137
+ <td style="padding: 8px 0;">:</td>
138
+ <td style="padding: 8px 0;">
139
+ <a href="{event.get('web_url', '#')}"
140
+ style="color: #0056b3; text-decoration: none;">View Event Here</a>
141
+ </td>
142
+ </tr>
143
+ <tr>
144
+ <td style="padding: 12px 0; font-weight: bold;">Count</td>
145
+ <td style="padding: 12px 0;">:</td>
146
+ <td style="padding: 12px 0;">
147
+ Max: <b>{max_count}</b> | Balance: <b>{balance}</b> | Applied: <b>{applied}</b>
148
+ </td>
149
+ </tr>
150
+ </table>
151
+ </div>
152
+ <div style="background-color: #f8f9fa; padding: 15px 20px; text-align: center; font-size: 12px; color: #777777; border-top: 1px solid #e0e0e0;">
153
+ Automated Notification from BIP Tracker
154
+ </div>
155
+ </div>
156
+ </div>
157
+ """
158
+
159
+ logger.info(f"🚨 STATUS TRACKER: Sending {changed_field} alert for {event.get('event_code', '-')}")
160
+ send_email_message(subject, body, is_html=True, recipient=recipient)
161
+
162
+ def track_event_changes(events):
163
+ """
164
+ Analyzes a list of events fetched from the API, compares them to the
165
+ previous state, and triggers alerts for any that just became 'Active'.
166
+ """
167
+ if not events:
168
+ return
169
+
170
+ state = load_state()
171
+ state_changed = False
172
+
173
+ for event in events:
174
+ event_id = str(event.get("id"))
175
+ if not event_id:
176
+ continue
177
+
178
+ current_data = extract_tracked_fields(event)
179
+
180
+ # Hugging Face Restart Protection:
181
+ # If the event ID doesn't exist in our state file at all, this might
182
+ # be the first run, or the container just restarted and lost its file.
183
+ # We record it, but we DO NOT trigger an alert to prevent false positives.
184
+ if event_id not in state:
185
+ state[event_id] = current_data
186
+ state_changed = True
187
+ logger.debug(f"Tracking new state for event {event_id} (No alert)")
188
+ continue
189
+
190
+ previous_data = state[event_id]
191
+
192
+ # Detect if status changed
193
+ old_status = str(previous_data.get("status", "")).strip()
194
+ new_status = str(current_data.get("status", "")).strip()
195
+
196
+ if old_status != new_status:
197
+ # Only trigger alert if status changes to 'active'
198
+ if old_status and new_status.lower() == "active":
199
+ logger.info(f"Event {event_id} ({current_data.get('event_code', '-')}) changed status to {new_status}!")
200
+ send_change_alert(event, "Status", old_status, new_status)
201
+
202
+ # Detect if maximum_count increased
203
+ try:
204
+ old_max = int(previous_data.get("maximum_count", 0))
205
+ except (ValueError, TypeError):
206
+ old_max = 0
207
+
208
+ try:
209
+ new_max = int(current_data.get("maximum_count", 0))
210
+ except (ValueError, TypeError):
211
+ new_max = 0
212
+
213
+ if new_max > old_max:
214
+ logger.info(f"Event {event_id} ({current_data.get('event_code', '-')}) increased max count from {old_max} to {new_max}!")
215
+ send_change_alert(event, "Capacity", str(old_max), str(new_max))
216
+
217
+ # Update state if anything changed
218
+ if current_data != previous_data:
219
+ state[event_id] = current_data
220
+ state_changed = True
221
+
222
+ if state_changed:
223
  save_state(state)