working app

This commit is contained in:
Aron Petau 2025-12-29 22:46:10 +01:00
parent 8e69e30387
commit 3057cda8d3
12 changed files with 708 additions and 232 deletions

View file

@ -45,7 +45,9 @@ class ApplicationHandler:
Handles browser automation, listing extraction, application delegation, and Telegram notifications.
"""
def __init__(self, browser_context, state_manager, applications_file: Path = None):
def __init__(self, browser_context, state_manager, applications_file: Optional[Path] = None):
if browser_context is None:
raise ValueError("browser_context must not be None. ApplicationHandler requires a valid Playwright context.")
self.context = browser_context
self.state_manager = state_manager
self.applications_file = applications_file or APPLICATIONS_FILE
@ -72,6 +74,7 @@ class ApplicationHandler:
company = self._detect_company(link)
if company == "wgcompany":
continue # skip WGCompany listings for main handler
company_label = company.capitalize() if company != "unknown" else "Wohnung"
message = (
f"\ud83c\udfe0 <b>[{company_label}] Neue Wohnung!</b>\n\n"
@ -107,10 +110,7 @@ class ApplicationHandler:
logger.info(f"Notifying Telegram: {listing['address']} ({listing['rooms']}, {listing['size']}, {listing['price']})")
self.telegram_bot._send_message(message)
else:
logger.info(f"[TELEGRAM] Would send message for: {listing['address']} ({listing['rooms']}, {listing['size']}, {listing['price']})")
self.telegram_bot._send_message(message)
else:
logger.info(f"[TELEGRAM] Would send message for: {listing['address']} ({listing['rooms']}, {listing['size']}, {listing['price']})")
logger.info(f"[TELEGRAM] Would send message for: {listing['address']} ({listing['rooms']}, {listing['size']}, {listing['price']})")
async def apply_to_listings(self, listings: list[dict]) -> dict:
"""
@ -118,6 +118,9 @@ class ApplicationHandler:
Returns a dict of application results keyed by listing ID.
"""
results = {}
# Fail fast if context is ever None (should never happen)
if self.context is None:
raise RuntimeError("browser_context is None in apply_to_listings. This should never happen.")
for listing in listings:
if self.has_applied(listing["id"]):
logger.info(f"Already applied to {listing['id']} ({listing['address']}), skipping.")
@ -131,6 +134,7 @@ class ApplicationHandler:
return results
def log_listing_times(self, new_listings: list[dict]):
"""
Log new listing appearance times to CSV for later analysis and pattern mining.
@ -164,31 +168,7 @@ class ApplicationHandler:
logger.info(f"Logged {len(new_listings)} new listing times to CSV.")
def __init__(self, browser_context, state_manager):
self.context = browser_context
self.state_manager = state_manager
self.handlers = {
"howoge": HowogeHandler(browser_context),
"gewobag": GewobagHandler(browser_context),
"degewo": DegewoHandler(browser_context),
"gesobau": GesobauHandler(browser_context),
"stadtundland": StadtUndLandHandler(browser_context),
"wbm": WBMHandler(browser_context),
}
self.applications_file = applications_file or APPLICATIONS_FILE
def __init__(self, browser_context, state_manager, applications_file: Path = None):
self.context = browser_context
self.state_manager = state_manager
self.applications_file = applications_file or APPLICATIONS_FILE
self.handlers = {
"howoge": HowogeHandler(browser_context),
"gewobag": GewobagHandler(browser_context),
"degewo": DegewoHandler(browser_context),
"gesobau": GesobauHandler(browser_context),
"stadtundland": StadtUndLandHandler(browser_context),
"wbm": WBMHandler(browser_context),
}
# ...existing code...
async def init_browser(self):
@ -333,16 +313,39 @@ class ApplicationHandler:
def _generate_weekly_plot(self) -> str:
"""Generate a heatmap of listings by day of week and hour"""
if not TIMING_FILE.exists():
logger.warning("No timing file found for weekly plot")
return ""
"""Generate a heatmap of listings by day of week and hour. Always returns a plot path, even if no data."""
plot_path = DATA_DIR / "weekly_plot.png"
try:
if not TIMING_FILE.exists():
logger.warning("No timing file found for weekly plot. Generating empty plot.")
# Generate empty plot
fig, ax = plt.subplots(figsize=(10, 6))
ax.set_xticks(range(24))
ax.set_yticks(range(7))
ax.set_xticklabels([f"{h}:00" for h in range(24)], rotation=90)
ax.set_yticklabels(["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"])
ax.set_title("Listings Heatmap (No Data)")
ax.text(0.5, 0.5, "No data available", fontsize=18, ha='center', va='center', transform=ax.transAxes, color='gray')
plt.savefig(plot_path)
plt.close(fig)
return str(plot_path)
df = pd.read_csv(TIMING_FILE, parse_dates=["timestamp"])
if df.empty:
logger.warning("Timing file is empty. Generating empty plot.")
fig, ax = plt.subplots(figsize=(10, 6))
ax.set_xticks(range(24))
ax.set_yticks(range(7))
ax.set_xticklabels([f"{h}:00" for h in range(24)], rotation=90)
ax.set_yticklabels(["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"])
ax.set_title("Listings Heatmap (No Data)")
ax.text(0.5, 0.5, "No data available", fontsize=18, ha='center', va='center', transform=ax.transAxes, color='gray')
plt.savefig(plot_path)
plt.close(fig)
return str(plot_path)
df["day_of_week"] = df["timestamp"].dt.dayofweek
df["hour"] = df["timestamp"].dt.hour
heatmap_data = df.groupby(["day_of_week", "hour"]).size().unstack(fill_value=0)
fig, ax = plt.subplots(figsize=(10, 6))
@ -356,15 +359,23 @@ class ApplicationHandler:
ax.set_title("Listings Heatmap (Day of Week vs Hour)")
plot_path = DATA_DIR / "weekly_plot.png"
plt.savefig(plot_path)
plt.close(fig)
logger.info(f"Weekly plot saved to {plot_path}")
return str(plot_path)
except Exception as e:
logger.error(f"Failed to generate weekly plot: {e}")
return ""
# Always generate a fallback empty plot
fig, ax = plt.subplots(figsize=(10, 6))
ax.set_xticks(range(24))
ax.set_yticks(range(7))
ax.set_xticklabels([f"{h}:00" for h in range(24)], rotation=90)
ax.set_yticklabels(["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"])
ax.set_title("Listings Heatmap (Error)")
ax.text(0.5, 0.5, "Plot error", fontsize=18, ha='center', va='center', transform=ax.transAxes, color='red')
plt.savefig(plot_path)
plt.close(fig)
return str(plot_path)
def _generate_error_rate_plot(self):