add error
This commit is contained in:
parent
4845d5d0dc
commit
d39453d688
2 changed files with 164 additions and 5 deletions
46
monitor.py
46
monitor.py
|
|
@ -288,22 +288,58 @@ When autopilot is ON, I will automatically apply to new listings."""
|
||||||
grouped['failures'] = grouped['total'] - grouped['successes']
|
grouped['failures'] = grouped['total'] - grouped['successes']
|
||||||
grouped['error_rate'] = grouped['failures'] / grouped['total']
|
grouped['error_rate'] = grouped['failures'] / grouped['total']
|
||||||
|
|
||||||
# Prepare plot
|
# Ensure index is sorted by date for plotting
|
||||||
|
grouped = grouped.sort_index()
|
||||||
|
|
||||||
|
# Prepare plot: convert dates to matplotlib numeric x-values so bars and line align
|
||||||
|
import matplotlib.dates as mdates
|
||||||
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8), sharex=True)
|
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8), sharex=True)
|
||||||
grouped[['successes','failures']].plot(kind='bar', stacked=True, ax=ax1, color=['#2E8B57','#C44A4A'])
|
|
||||||
|
dates = pd.to_datetime(grouped.index).to_pydatetime()
|
||||||
|
x = mdates.date2num(dates)
|
||||||
|
width = 0.6 # width in days for bars
|
||||||
|
|
||||||
|
successes = grouped['successes'].values
|
||||||
|
failures = grouped['failures'].values
|
||||||
|
|
||||||
|
ax1.bar(x, successes, width=width, color='#2E8B57', align='center')
|
||||||
|
ax1.bar(x, failures, bottom=successes, width=width, color='#C44A4A', align='center')
|
||||||
ax1.set_ylabel('Count')
|
ax1.set_ylabel('Count')
|
||||||
ax1.set_title('Autopilot: Successes vs Failures (by day)')
|
ax1.set_title('Autopilot: Successes vs Failures (by day)')
|
||||||
|
ax1.set_xticks(x)
|
||||||
|
ax1.set_xlim(min(x) - 1, max(x) + 1)
|
||||||
|
ax1.xaxis.set_major_locator(mdates.AutoDateLocator())
|
||||||
|
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
|
||||||
|
|
||||||
ax2.plot(grouped.index, grouped['error_rate'], marker='o', color='#3333AA')
|
# Plot error rate line on same x (date) axis
|
||||||
ax2.set_ylim(0,1)
|
ax2.plot(x, grouped['error_rate'].values, marker='o', color='#3333AA', linewidth=2)
|
||||||
|
ax2.set_ylim(-0.02, 1.02)
|
||||||
ax2.set_ylabel('Error rate')
|
ax2.set_ylabel('Error rate')
|
||||||
ax2.set_xlabel('Date')
|
ax2.set_xlabel('Date')
|
||||||
ax2.set_title('Daily Error Rate (failures / total)')
|
ax2.set_title('Daily Error Rate (failures / total)')
|
||||||
|
ax2.grid(True, alpha=0.3)
|
||||||
|
ax2.set_xticks(x)
|
||||||
|
ax2.set_xlim(min(x) - 1, max(x) + 1)
|
||||||
|
ax2.xaxis.set_major_locator(mdates.AutoDateLocator())
|
||||||
|
ax2.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
|
||||||
|
fig.autofmt_xdate()
|
||||||
|
|
||||||
plt.tight_layout()
|
plt.tight_layout()
|
||||||
plot_path = DATA_DIR / 'error_rate.png'
|
plot_path = DATA_DIR / 'error_rate.png'
|
||||||
fig.savefig(plot_path)
|
tmp_path = DATA_DIR / 'error_rate.tmp.png'
|
||||||
|
# Save to a temp file first and atomically replace to ensure overwrite
|
||||||
|
fig.savefig(tmp_path, format='png')
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
try:
|
||||||
|
tmp_path.replace(plot_path)
|
||||||
|
except Exception:
|
||||||
|
# Fallback: try removing existing and renaming
|
||||||
|
try:
|
||||||
|
if plot_path.exists():
|
||||||
|
plot_path.unlink()
|
||||||
|
tmp_path.rename(plot_path)
|
||||||
|
except Exception:
|
||||||
|
logger.exception(f"Failed to write plot to {plot_path}")
|
||||||
|
|
||||||
# Summary
|
# Summary
|
||||||
total_attempts = int(grouped['total'].sum())
|
total_attempts = int(grouped['total'].sum())
|
||||||
|
|
|
||||||
123
tests/test_errorrate_runner.py
Normal file
123
tests/test_errorrate_runner.py
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Simple test runner for the monitor's error-rate plot generator.
|
||||||
|
|
||||||
|
Run from the repository root (where `monitor.py` and `data/` live):
|
||||||
|
|
||||||
|
python3 tests/test_errorrate_runner.py
|
||||||
|
|
||||||
|
This will call `TelegramBot._generate_error_rate_plot()` and print the result.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
import matplotlib
|
||||||
|
matplotlib.use('Agg')
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import json
|
||||||
|
|
||||||
|
DATA_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data'))
|
||||||
|
APPLICATIONS_FILE = os.path.join(DATA_DIR, 'applications.json')
|
||||||
|
|
||||||
|
def generate_error_rate_plot(applications_file: str):
|
||||||
|
if not os.path.exists(applications_file):
|
||||||
|
print('No applications.json found at', applications_file)
|
||||||
|
return None, ''
|
||||||
|
try:
|
||||||
|
with open(applications_file, 'r', encoding='utf-8') as f:
|
||||||
|
apps = json.load(f)
|
||||||
|
if not apps:
|
||||||
|
return None, ''
|
||||||
|
|
||||||
|
rows = []
|
||||||
|
for _id, rec in apps.items():
|
||||||
|
ts = rec.get('timestamp')
|
||||||
|
try:
|
||||||
|
dt = pd.to_datetime(ts)
|
||||||
|
except Exception:
|
||||||
|
dt = pd.NaT
|
||||||
|
rows.append({'id': _id, 'company': rec.get('company'), 'success': bool(rec.get('success')), 'ts': dt})
|
||||||
|
df = pd.DataFrame(rows)
|
||||||
|
df = df.dropna(subset=['ts'])
|
||||||
|
if df.empty:
|
||||||
|
return None, ''
|
||||||
|
|
||||||
|
df['date'] = df['ts'].dt.floor('D')
|
||||||
|
grouped = df.groupby('date').agg(total=('id','count'), successes=('success', lambda x: x.sum()))
|
||||||
|
grouped['failures'] = grouped['total'] - grouped['successes']
|
||||||
|
grouped['error_rate'] = grouped['failures'] / grouped['total']
|
||||||
|
grouped = grouped.sort_index()
|
||||||
|
|
||||||
|
# Plot
|
||||||
|
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8), sharex=True)
|
||||||
|
grouped[['successes','failures']].plot(kind='bar', stacked=True, ax=ax1, color=['#2E8B57','#C44A4A'])
|
||||||
|
ax1.set_ylabel('Count')
|
||||||
|
ax1.set_title('Autopilot: Successes vs Failures (by day)')
|
||||||
|
|
||||||
|
import matplotlib.dates as mdates
|
||||||
|
dates = pd.to_datetime(grouped.index).to_pydatetime()
|
||||||
|
x = mdates.date2num(dates)
|
||||||
|
width = 0.6
|
||||||
|
|
||||||
|
ax1.bar(x, grouped['successes'].values, width=width, color='#2E8B57', align='center')
|
||||||
|
ax1.bar(x, grouped['failures'].values, bottom=grouped['successes'].values, width=width, color='#C44A4A', align='center')
|
||||||
|
ax1.set_xticks(x)
|
||||||
|
ax1.set_xlim(min(x) - 1, max(x) + 1)
|
||||||
|
ax1.xaxis.set_major_locator(mdates.AutoDateLocator())
|
||||||
|
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
|
||||||
|
|
||||||
|
ax2.plot(x, grouped['error_rate'].values, marker='o', color='#3333AA', linewidth=2)
|
||||||
|
ax2.set_ylim(-0.02, 1.02)
|
||||||
|
ax2.set_ylabel('Error rate')
|
||||||
|
ax2.set_xlabel('Date')
|
||||||
|
ax2.set_title('Daily Error Rate (failures / total)')
|
||||||
|
ax2.grid(True, alpha=0.3)
|
||||||
|
ax2.set_xticks(x)
|
||||||
|
ax2.set_xlim(min(x) - 1, max(x) + 1)
|
||||||
|
ax2.xaxis.set_major_locator(mdates.AutoDateLocator())
|
||||||
|
ax2.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
|
||||||
|
fig.autofmt_xdate()
|
||||||
|
|
||||||
|
plot_path = os.path.join(DATA_DIR, 'error_rate.png')
|
||||||
|
tmp_path = os.path.join(DATA_DIR, 'error_rate.tmp.png')
|
||||||
|
fig.savefig(tmp_path, format='png')
|
||||||
|
plt.close(fig)
|
||||||
|
try:
|
||||||
|
# Atomic replace where possible
|
||||||
|
os.replace(tmp_path, plot_path)
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
if os.path.exists(plot_path):
|
||||||
|
os.remove(plot_path)
|
||||||
|
os.rename(tmp_path, plot_path)
|
||||||
|
except Exception as e:
|
||||||
|
print('Failed to write plot file:', e)
|
||||||
|
return None, ''
|
||||||
|
|
||||||
|
total_attempts = int(grouped['total'].sum())
|
||||||
|
total_success = int(grouped['successes'].sum())
|
||||||
|
total_fail = int(grouped['failures'].sum())
|
||||||
|
overall_error = (total_fail / total_attempts) if total_attempts>0 else 0.0
|
||||||
|
summary = f"<b>Total attempts:</b> {total_attempts}\n<b>Successes:</b> {total_success}\n<b>Failures:</b> {total_fail}\n<b>Overall error rate:</b> {overall_error:.1%}"
|
||||||
|
return plot_path, summary
|
||||||
|
except Exception as e:
|
||||||
|
print('Error generating plot:', e)
|
||||||
|
return None, ''
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Use the local implementation to avoid importing the full monitor (Playwright heavy)
|
||||||
|
plot_path, summary = generate_error_rate_plot(APPLICATIONS_FILE)
|
||||||
|
if plot_path:
|
||||||
|
print("PLOT_PATH:", plot_path)
|
||||||
|
print("EXISTS:", os.path.exists(plot_path))
|
||||||
|
print("SUMMARY:\n", summary)
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
print("No plot generated (insufficient data or error)")
|
||||||
|
sys.exit(3)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Loading…
Add table
Add a link
Reference in a new issue