138 lines
4.6 KiB
Python
Executable File
138 lines
4.6 KiB
Python
Executable File
#!/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.")
|