diff --git a/.python-version b/.python-version
index 24ee5b1..e4fba21 100644
--- a/.python-version
+++ b/.python-version
@@ -1 +1 @@
-3.13
+3.12
diff --git a/monitor.py b/monitor.py
index 74d19ee..27d6d57 100644
--- a/monitor.py
+++ b/monitor.py
@@ -261,13 +261,13 @@ When autopilot is ON, I will automatically apply to new listings."""
"""
if not APPLICATIONS_FILE.exists():
logger.warning("No applications.json found for errorrate plot")
- return None, ""
+ return "", "" # Return empty strings
try:
with open(APPLICATIONS_FILE, 'r', encoding='utf-8') as f:
apps = json.load(f)
if not apps:
- return None, ""
+ return "", ""
# Convert to DataFrame
rows = []
@@ -281,7 +281,7 @@ When autopilot is ON, I will automatically apply to new listings."""
df = pd.DataFrame(rows)
df = df.dropna(subset=['ts'])
if df.empty:
- return None, ""
+ return "", ""
df['date'] = df['ts'].dt.floor('D')
grouped = df.groupby('date').agg(total=('id','count'), successes=('success', lambda x: x.sum()))
@@ -293,7 +293,7 @@ When autopilot is ON, I will automatically apply to new listings."""
# 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, ax3) = plt.subplots(3, 1, figsize=(12, 12), sharex=True)
dates = pd.to_datetime(grouped.index).to_pydatetime()
x = mdates.date2num(dates)
@@ -322,6 +322,27 @@ When autopilot is ON, I will automatically apply to new listings."""
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'))
+
+ # New: Error rate by company (line plot)
+ company_grouped = df.groupby(['date', 'company']).agg(total=('id','count'), successes=('success', lambda x: x.sum()))
+ company_grouped['failures'] = company_grouped['total'] - company_grouped['successes']
+ company_grouped['error_rate'] = company_grouped['failures'] / company_grouped['total']
+ company_grouped = company_grouped.reset_index()
+ error_rate_pivot = company_grouped.pivot(index='date', columns='company', values='error_rate')
+ for company in error_rate_pivot.columns:
+ y = error_rate_pivot[company].values
+ ax3.plot(x, y, marker='o', label=str(company))
+ ax3.set_ylim(-0.02, 1.02)
+ ax3.set_ylabel('Error rate')
+ ax3.set_xlabel('Date')
+ ax3.set_title('Daily Error Rate by Company')
+ ax3.grid(True, alpha=0.3)
+ ax3.set_xticks(x)
+ ax3.set_xlim(min(x) - 1, max(x) + 1)
+ ax3.xaxis.set_major_locator(mdates.AutoDateLocator())
+ ax3.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
+ ax3.legend(title='Company', loc='upper right', fontsize='small')
+
fig.autofmt_xdate()
plt.tight_layout()
@@ -351,7 +372,7 @@ When autopilot is ON, I will automatically apply to new listings."""
return str(plot_path), summary
except Exception as e:
logger.exception(f"Failed to generate error rate plot: {e}")
- return None, ""
+ return "", ""
self._send_message(f"❓ Unknown command: {cmd}\n\nUse /help to see available commands.")
def _handle_plot_command(self):
@@ -373,13 +394,13 @@ When autopilot is ON, I will automatically apply to new listings."""
"""Generate a heatmap of listings by day of week and hour"""
if not TIMING_FILE.exists():
logger.warning("No timing data file found")
- return None
+ return ""
try:
df = pd.read_csv(TIMING_FILE)
if len(df) < 1:
logger.warning("Timing file is empty")
- return None
+ return ""
logger.info(f"Loaded {len(df)} listing records for plot")
@@ -393,7 +414,11 @@ When autopilot is ON, I will automatically apply to new listings."""
day = row['weekday']
hour = int(row['hour'])
if day in days_order:
- heatmap_data.loc[day, hour] += 1
+ # Fix: Ensure the value is numeric before incrementing
+ if pd.api.types.is_numeric_dtype(heatmap_data.loc[day, hour]):
+ heatmap_data.loc[day, hour] += 1
+ else:
+ heatmap_data.loc[day, hour] = 1 # Initialize if not numeric
# Create figure with two subplots
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
@@ -414,7 +439,7 @@ When autopilot is ON, I will automatically apply to new listings."""
# 2. Bar chart - By day of week
ax2 = axes[0, 1]
day_counts = df['weekday'].value_counts().reindex(days_order, fill_value=0)
- colors = plt.cm.Blues(day_counts / day_counts.max() if day_counts.max() > 0 else day_counts)
+ colors = plt.cm.get_cmap('Blues')(day_counts / day_counts.max() if day_counts.max() > 0 else day_counts)
bars = ax2.bar(range(7), day_counts.values, color=colors)
ax2.set_xticks(range(7))
ax2.set_xticklabels(['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'])
@@ -448,13 +473,19 @@ When autopilot is ON, I will automatically apply to new listings."""
# Find peak combinations
peak_combo = heatmap_data.stack().idxmax() if heatmap_data.values.max() > 0 else ("N/A", "N/A")
+ # Fix: Ensure peak_combo is iterable
+ if isinstance(peak_combo, tuple) and len(peak_combo) == 2:
+ stats_text = f"🎯 Peak time: {peak_combo[0]} at {peak_combo[1]}:00"
+ else:
+ stats_text = "🎯 Peak time: N/A"
+
stats_text = f"""📊 Summary Statistics
Total listings tracked: {total_listings}
🏆 Best day: {best_day}
⏰ Best hour: {best_hour}:00
-🎯 Peak time: {peak_combo[0]} at {peak_combo[1]}:00
+{stats_text}
📈 Average per day: {total_listings/7:.1f}
📅 Data collection period:
@@ -479,7 +510,8 @@ Total listings tracked: {total_listings}
logger.error(f"Error creating plot: {e}")
import traceback
logger.error(traceback.format_exc())
- return None
+ return ""
+ self._send_message(f"❓ Unknown command: {cmd}\n\nUse /help to see available commands.")
def _send_message(self, text):
try:
@@ -1720,7 +1752,7 @@ class InBerlinMonitor:
# Extract listings from button elements with aria-label
# Format: @click="open !== 12345 ..." aria-label="Wohnungsangebot - 2,0 Zimmer, 53,01 m², 494,38 € Kaltmiete | Adresse"
- button_pattern = r'@click="open !== (\d+)[^"]*"[^>]*aria-label="Wohnungsangebot - ([^"]+)"'
+ button_pattern = r'@click="open !== (\d+)[^"]*"[^>]*aria-label="Wohnungsangebot - ([^"]+)'
button_matches = re.findall(button_pattern, content_decoded)
logger.info(f"Found {len(button_matches)} listing buttons")
@@ -2238,54 +2270,3 @@ class WGCompanyMonitor:
async def _async_fetch(self):
await self.init_browser()
return await self.fetch_listings()
-
-
-def main():
- """Main entry point"""
-
- # Ensure data directory exists
- DATA_DIR.mkdir(parents=True, exist_ok=True)
-
- # Initialize monitors
- inberlin_monitor = InBerlinMonitor()
- wgcompany_monitor = WGCompanyMonitor() if WGCOMPANY_ENABLED else None
-
- # Start Telegram command listener
- telegram_bot = TelegramBot(inberlin_monitor)
- telegram_bot.start()
-
- logger.info(f"Monitor started (interval: {CHECK_INTERVAL}s)")
- logger.info(f"InBerlin Autopilot: {'ENABLED' if inberlin_monitor.is_autopilot_enabled() else 'DISABLED'}")
- logger.info(f"WGcompany: {'ENABLED' if WGCOMPANY_ENABLED else 'DISABLED'}")
-
- # Run periodic cleanup hourly
- last_cleanup = 0
-
- while True:
- # Check InBerlinWohnen
- try:
- inberlin_monitor.check()
- except Exception as e:
- logger.error(f"InBerlin check failed: {e}")
-
- # Periodic cleanup: remove PNGs older than 24h and prune logs older than 7 days
- try:
- if time.time() - last_cleanup > 3600: # every hour
- logger.info("Running periodic cleanup (old PNGs, prune logs)")
- _cleanup_old_files(png_hours=24, log_days=7)
- last_cleanup = time.time()
- except Exception:
- logger.exception("Cleanup failed")
-
- # Check WGcompany
- if wgcompany_monitor:
- try:
- wgcompany_monitor.check()
- except Exception as e:
- logger.error(f"WGcompany check failed: {e}")
-
- time.sleep(CHECK_INTERVAL)
-
-
-if __name__ == "__main__":
- main()
diff --git a/tests/test_errorrate_runner.py b/tests/test_errorrate_runner.py
index 8806520..d2ba77d 100644
--- a/tests/test_errorrate_runner.py
+++ b/tests/test_errorrate_runner.py
@@ -52,12 +52,15 @@ def generate_error_rate_plot(applications_file: str):
grouped = grouped.sort_index()
# Plot
- fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8), sharex=True)
+ import matplotlib.dates as mdates
+ # Add a third subplot for error rate by company
+ fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(12, 12), sharex=True)
+
+ # Stacked bar: successes vs failures (all companies)
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
@@ -69,6 +72,7 @@ def generate_error_rate_plot(applications_file: str):
ax1.xaxis.set_major_locator(mdates.AutoDateLocator())
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
+ # Line: overall error rate
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')
@@ -79,6 +83,30 @@ def generate_error_rate_plot(applications_file: str):
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'))
+
+ # New: Error rate by company (line plot)
+ # Group by date and company
+ company_grouped = df.groupby(['date', 'company']).agg(total=('id','count'), successes=('success', lambda x: x.sum()))
+ company_grouped['failures'] = company_grouped['total'] - company_grouped['successes']
+ company_grouped['error_rate'] = company_grouped['failures'] / company_grouped['total']
+ company_grouped = company_grouped.reset_index()
+ # Pivot for plotting: index=date, columns=company, values=error_rate
+ error_rate_pivot = company_grouped.pivot(index='date', columns='company', values='error_rate')
+ # Plot each company as a line
+ for company in error_rate_pivot.columns:
+ y = error_rate_pivot[company].values
+ ax3.plot(x, y, marker='o', label=str(company))
+ ax3.set_ylim(-0.02, 1.02)
+ ax3.set_ylabel('Error rate')
+ ax3.set_xlabel('Date')
+ ax3.set_title('Daily Error Rate by Company')
+ ax3.grid(True, alpha=0.3)
+ ax3.set_xticks(x)
+ ax3.set_xlim(min(x) - 1, max(x) + 1)
+ ax3.xaxis.set_major_locator(mdates.AutoDateLocator())
+ ax3.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
+ ax3.legend(title='Company', loc='upper right', fontsize='small')
+
fig.autofmt_xdate()
plot_path = os.path.join(DATA_DIR, 'error_rate.png')