#!/usr/bin/env python3 """ Generate a simple text-based HTML dashboard of all tracked jobs. """ from datetime import datetime from pathlib import Path from collections import Counter from db import Database # Location grouping rules: keyword -> (group_id, display_name) # Order matters - first match wins LOCATION_RULES = [ # Canada (["canada", "toronto", "vancouver", "montreal", "ottawa", "calgary", "waterloo"], "canada", "Canada"), # Germany (["germany", "berlin", "munich", "frankfurt", "hamburg"], "germany", "Germany"), # UK (["united kingdom", " uk", "uk ", "london", "england", "manchester", "edinburgh"], "uk", "UK"), # Ireland (["ireland", "dublin"], "ireland", "Ireland"), # Netherlands (["netherlands", "amsterdam", "rotterdam"], "netherlands", "Netherlands"), # France (["france", "paris"], "france", "France"), # Spain (["spain", "madrid", "barcelona"], "spain", "Spain"), # Poland (["poland", "warsaw", "krakow", "wroclaw"], "poland", "Poland"), # Sweden (["sweden", "stockholm"], "sweden", "Sweden"), # Switzerland (["switzerland", "zurich", "geneva"], "switzerland", "Switzerland"), # Australia (["australia", "sydney", "melbourne"], "australia", "Australia"), # India (["india", "bangalore", "bengaluru", "hyderabad", "delhi", "mumbai", "pune"], "india", "India"), # Japan (["japan", "tokyo"], "japan", "Japan"), # Singapore (["singapore"], "singapore", "Singapore"), # Israel (["israel", "tel aviv"], "israel", "Israel"), # Brazil (["brazil", "sao paulo"], "brazil", "Brazil"), # US (must be after other countries to avoid false matches) (["united states", "usa", "u.s.", "san francisco", "new york", "nyc", "seattle", "austin", "boston", "chicago", "denver", "los angeles", "atlanta", "dallas", "houston", "california", "washington", "texas", "massachusetts", "colorado", "portland", "miami", "phoenix", "san diego", "san jose", "palo alto", "mountain view", "sunnyvale", "menlo park", "cupertino"], "us", "US"), # Regions (["emea"], "emea", "EMEA"), (["americas", "north america", "latam"], "americas", "Americas"), (["apac", "asia pacific", "asia-pacific"], "apac", "APAC"), (["worldwide", "global", "anywhere", "earth"], "worldwide", "Worldwide"), ] def extract_location_info(location: str, remote_type: str) -> tuple[list[str], str]: """ Extract location tags and short display text from a job's location. Returns (list of tag ids, short display location) """ tags = [] display = "" if not location: return tags, display loc_lower = location.lower() # Check for remote is_remote = remote_type == "remote" or "remote" in loc_lower if is_remote: tags.append("remote") # Check against location rules for keywords, tag_id, display_name in LOCATION_RULES: if any(kw in loc_lower for kw in keywords): if tag_id not in tags: tags.append(tag_id) if not display: display = display_name # Fallback display if not display: if is_remote: display = "Remote" elif location: display = location[:25] + "..." if len(location) > 25 else location return tags, display def generate_dashboard(output_path: str = "data/dashboard.html"): """Generate a static HTML dashboard.""" db = Database() jobs = db.get_all_active_jobs() all_company_names = db.get_all_companies() # Process all jobs and collect location data companies = {} location_counts = Counter() for company_name, job in jobs: # Extract location info tags, display = extract_location_info(job.location, job.remote_type) # Count locations for filter generation for tag in tags: location_counts[tag] += 1 # Store processed job data if company_name not in companies: companies[company_name] = [] companies[company_name].append({ "job": job, "tags": tags, "display": display, "search_text": f"{job.title.lower()} {(job.location or '').lower()} {(job.department or '').lower()} {' '.join(tags)}" }) # Ensure all companies exist (even with 0 jobs) for name in all_company_names: if name not in companies: companies[name] = [] total_jobs = sum(len(j) for j in companies.values()) sorted_companies = sorted(companies.items()) # Generate dynamic location filters (only show locations that exist in data) # Order: Remote first, then by count descending location_filters = [] if "remote" in location_counts: location_filters.append(("remote", "Remote", location_counts["remote"])) # Add other locations sorted by count other_locations = [(tag, count) for tag, count in location_counts.items() if tag != "remote"] other_locations.sort(key=lambda x: -x[1]) # Map tag_id to display name tag_display = {tag_id: display for keywords, tag_id, display in LOCATION_RULES} tag_display["remote"] = "Remote" for tag_id, count in other_locations: display = tag_display.get(tag_id, tag_id.title()) location_filters.append((tag_id, display, count)) # Generate location filter buttons HTML location_buttons = "" for tag_id, display, count in location_filters: location_buttons += f' \n' # Generate tag colors dynamically tag_colors = { "remote": ("#1a4a1a", "#4ade80"), "canada": ("#4a1a1a", "#f87171"), "germany": ("#4a4a1a", "#facc15"), "uk": ("#2a1a3a", "#a78bfa"), "us": ("#3a2a1a", "#fb923c"), "emea": ("#1a3a4a", "#60a5fa"), "americas": ("#3a1a4a", "#c084fc"), "worldwide": ("#1a4a3a", "#34d399"), "apac": ("#1a2a4a", "#38bdf8"), "ireland": ("#1a4a2a", "#4ade80"), "netherlands": ("#3a3a1a", "#fbbf24"), "france": ("#2a2a4a", "#818cf8"), "spain": ("#4a2a1a", "#fb7185"), "poland": ("#3a1a2a", "#f472b6"), "sweden": ("#1a3a3a", "#2dd4bf"), "switzerland": ("#4a1a2a", "#fb7185"), "australia": ("#2a3a1a", "#a3e635"), "india": ("#4a3a1a", "#fcd34d"), "japan": ("#4a1a3a", "#e879f9"), "singapore": ("#1a4a4a", "#22d3d1"), "israel": ("#3a2a2a", "#fca5a5"), "brazil": ("#2a4a1a", "#86efac"), } # Generate CSS for tags tag_css = "" for tag_id, (bg, fg) in tag_colors.items(): tag_css += f""" .tag-{tag_id} {{ background: {bg}; color: {fg}; }} """ html = f""" Job Board

$ job-board

Last updated: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")} | {total_jobs} jobs | {len(all_company_names)} companies
Quick:
Location: {location_buttons}
Role:
{total_jobs} jobs shown
Jump to company:
""" # Job listings for company_name, company_jobs in sorted_companies: if not company_jobs: continue anchor = company_name.lower().replace(" ", "-").replace("'", "") total = len(company_jobs) html += f"""
{company_name} {total} positions
""" for job_data in sorted(company_jobs, key=lambda j: j["job"].title): job = job_data["job"] tags = job_data["tags"] display = job_data["display"] search_text = job_data["search_text"] # Build tag HTML tag_html = "" for tag in tags: tag_html += f'{tag}' html += f"""
{job.title}{tag_html} {display}
""" html += """
""" html += """
""" # Write the file output = Path(output_path) output.parent.mkdir(parents=True, exist_ok=True) output.write_text(html) print(f"Dashboard generated: {output_path}") return output_path if __name__ == "__main__": generate_dashboard()