Initial commit: job dashboard server with evening stand-down, Notion sync, cover letter generation

This commit is contained in:
2026-07-20 21:06:44 +08:00
commit c6476462b7
7 changed files with 2123 additions and 0 deletions

137
backfill_cover_letters.py Executable file
View File

@@ -0,0 +1,137 @@
#!/usr/bin/env python3
"""Backfill HTML/DOCX from existing .txt files using canonical parser.
This script uses the canonical parse_cover_letter() and render_cover_letter_html()
functions from regenerate_cover_letter.py to ensure consistent parsing across all
cover letter generation paths.
Usage:
python3 backfill_cover_letters.py # Batch mode: all .txt files
python3 backfill_cover_letters.py path/to/file.txt # Single file mode
"""
import os, sys, json, subprocess, glob
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from regenerate_cover_letter import parse_cover_letter, render_cover_letter_html
DRAFTS = os.path.expanduser("~/.hermes/radar-ref/drafts")
DOCX_SCRIPT = os.path.expanduser("~/.hermes/scripts/build_cover_letter_docx.py")
def backfill_txt(txt_path):
"""Regenerate HTML/DOCX from an existing .txt file using canonical parser."""
errors = []
try:
with open(txt_path, "r", encoding="utf-8") as f:
text = f.read()
except Exception as e:
return False, [f"Read failed: {e}"]
# Extract company/role from filename for better parsing
fname = os.path.basename(txt_path)
# Simple heuristic: cl-company-role-date.txt
parts = fname.replace("cl-", "").replace(".txt", "").split("-")
company = parts[0].replace("-", " ").title() if parts else ""
role = parts[1].replace("-", " ").title() if len(parts) > 1 else ""
# Parse using canonical function
try:
structured = parse_cover_letter(text, company=company, role=role, location="Perth")
except Exception as e:
return False, [f"Parse failed: {e}"]
# Validate parsed output
if not structured.get("opening") and not structured.get("bullets") and not structured.get("closing"):
return False, ["Parsed output is empty — check .txt file format"]
base = txt_path[:-4]
json_path = base + ".json"
html_path = base + ".html"
docx_path = base + ".docx"
# Add URLs
txt_url = f"/jobs/drafts/{os.path.basename(txt_path)}"
structured["txt_url"] = txt_url
structured["html_url"] = txt_url.replace(".txt", ".html")
structured["docx_url"] = txt_url.replace(".txt", ".docx")
# Save JSON
try:
with open(json_path, "w", encoding="utf-8") as f:
json.dump(structured, f, indent=2)
except Exception as e:
return False, [f"JSON save failed: {e}"]
# Render HTML
try:
html = render_cover_letter_html(structured, txt_url)
if html:
with open(html_path, "w", encoding="utf-8") as f:
f.write(html)
else:
return False, ["HTML render returned None"]
except Exception as e:
return False, [f"HTML render failed: {e}"]
# Generate DOCX
try:
result = subprocess.run(
["python3", DOCX_SCRIPT, json_path],
capture_output=True, text=True, timeout=60
)
if result.returncode != 0:
errors.append(f"DOCX script error: {result.stderr[:200]}")
except Exception as e:
errors.append(f"DOCX subprocess failed: {e}")
docx_ok = os.path.exists(docx_path)
html_ok = os.path.exists(html_path)
return html_ok and docx_ok, errors
if __name__ == "__main__":
if len(sys.argv) > 1:
# Single file mode
txt_file = sys.argv[1]
print(f"Backfilling {os.path.basename(txt_file)}...")
success, errors = backfill_txt(txt_file)
if success:
print(f"{os.path.basename(txt_file)}")
else:
print(f"{os.path.basename(txt_file)}")
for err in errors:
print(f" - {err}")
sys.exit(0 if success else 1)
# Batch mode: process all .txt files
print("Backfilling all cover letters from .txt files using canonical parser...")
print("=" * 70)
results = {"ok": 0, "fail": 0, "errors": []}
for txt_path in sorted(glob.glob(os.path.join(DRAFTS, "cl-*.txt"))):
base = os.path.basename(txt_path)
success, errors = backfill_txt(txt_path)
if success:
results["ok"] += 1
print(f"{base[:55]}")
else:
results["fail"] += 1
print(f"{base[:55]}")
for err in errors:
results["errors"].append((base, err))
print("=" * 70)
print(f"Results: {results['ok']} OK, {results['fail']} failed")
if results["errors"]:
print("\nErrors:")
for base, err in results["errors"]:
print(f" {base}: {err}")
print("\nDone.")