Initial commit: job dashboard server with evening stand-down, Notion sync, cover letter generation
This commit is contained in:
137
backfill_cover_letters.py
Executable file
137
backfill_cover_letters.py
Executable 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.")
|
||||
Reference in New Issue
Block a user