working app
This commit is contained in:
parent
8e69e30387
commit
3057cda8d3
12 changed files with 708 additions and 232 deletions
|
|
@ -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):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue