#!/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.")