diff --git a/scrapers/companyScraper.py b/scrapers/companyScraper.py
index 1a01d83..61add58 100755
--- a/scrapers/companyScraper.py
+++ b/scrapers/companyScraper.py
@@ -14,7 +14,6 @@ Enhanced features:
import argparse
import csv
-import json
import os
import random
import re
diff --git a/scripts/benchmark_classifier.py b/scripts/benchmark_classifier.py
index 2eec77d..34ce405 100644
--- a/scripts/benchmark_classifier.py
+++ b/scripts/benchmark_classifier.py
@@ -31,7 +31,6 @@ sys.path.insert(0, str(Path(__file__).parent.parent))
from scripts.classifier_adapters import (
LABELS,
- LABEL_DESCRIPTIONS,
ClassifierAdapter,
GLiClassAdapter,
RerankerAdapter,
diff --git a/scripts/calendar_push.py b/scripts/calendar_push.py
index 69b50b9..25ab067 100644
--- a/scripts/calendar_push.py
+++ b/scripts/calendar_push.py
@@ -5,7 +5,6 @@ push updates the existing event rather than creating a duplicate.
"""
from __future__ import annotations
-import uuid
import yaml
from datetime import datetime, timedelta, timezone
from pathlib import Path
diff --git a/scripts/db.py b/scripts/db.py
index e015a2b..6ccd2f7 100644
--- a/scripts/db.py
+++ b/scripts/db.py
@@ -121,6 +121,17 @@ CREATE TABLE IF NOT EXISTS survey_responses (
);
"""
+CREATE_RESUME_CORRECTIONS = """
+CREATE TABLE IF NOT EXISTS resume_optimizer_corrections (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ job_id INTEGER NOT NULL REFERENCES jobs(id),
+ section TEXT NOT NULL,
+ proposed_json TEXT NOT NULL,
+ accepted_json TEXT NOT NULL,
+ created_at TEXT DEFAULT (datetime('now'))
+);
+"""
+
CREATE_DIGEST_QUEUE = """
CREATE TABLE IF NOT EXISTS digest_queue (
id INTEGER PRIMARY KEY,
@@ -205,9 +216,10 @@ def _migrate_db(db_path: Path) -> None:
conn.execute("ALTER TABLE background_tasks ADD COLUMN params TEXT")
except sqlite3.OperationalError:
pass # column already exists
- # Ensure references tables exist (CREATE IF NOT EXISTS is idempotent)
+ # Ensure tables that can't be added via ALTER TABLE exist (all idempotent).
conn.execute(CREATE_REFERENCES)
conn.execute(CREATE_JOB_REFERENCES)
+ conn.execute(CREATE_RESUME_CORRECTIONS)
conn.commit()
conn.close()
@@ -223,6 +235,7 @@ def init_db(db_path: Path = DEFAULT_DB) -> None:
conn.execute(CREATE_DIGEST_QUEUE)
conn.execute(CREATE_REFERENCES)
conn.execute(CREATE_JOB_REFERENCES)
+ conn.execute(CREATE_RESUME_CORRECTIONS)
conn.commit()
conn.close()
_migrate_db(db_path)
@@ -1241,3 +1254,76 @@ def set_training_exclusion(db_path: Path, job_id: int, excluded: bool) -> None:
conn.commit()
finally:
conn.close()
+
+
+# ── Resume optimizer corrections ──────────────────────────────────────────────
+
+def save_resume_correction(
+ db_path: Path,
+ job_id: int,
+ section: str,
+ proposed: object,
+ accepted: object,
+) -> None:
+ """Persist a (proposed, accepted) correction pair from the resume review UI.
+
+ Called when a user edits an LLM-proposed value and accepts it. The pair
+ becomes a supervised fine-tuning (SFT) candidate routed through Avocet.
+
+ Args:
+ section: 'summary' or 'experience:
|'
+ proposed: Original LLM output (string for summary, list for bullets).
+ accepted: User-edited value (same type as proposed).
+ """
+ import json as _json
+ conn = sqlite3.connect(db_path)
+ try:
+ conn.execute(
+ """INSERT INTO resume_optimizer_corrections
+ (job_id, section, proposed_json, accepted_json)
+ VALUES (?, ?, ?, ?)""",
+ (job_id, section, _json.dumps(proposed), _json.dumps(accepted)),
+ )
+ conn.commit()
+ finally:
+ conn.close()
+
+
+def get_resume_corrections(
+ db_path: Path,
+ limit: int = 200,
+ job_id: int | None = None,
+) -> list[dict]:
+ """Return pending resume corrections for Avocet export.
+
+ Args:
+ limit: Maximum rows to return.
+ job_id: If set, filter to corrections for a specific job.
+ """
+ import json as _json
+ conn = sqlite3.connect(db_path)
+ conn.row_factory = sqlite3.Row
+ try:
+ if job_id is not None:
+ rows = conn.execute(
+ "SELECT * FROM resume_optimizer_corrections WHERE job_id=? ORDER BY created_at DESC LIMIT ?",
+ (job_id, limit),
+ ).fetchall()
+ else:
+ rows = conn.execute(
+ "SELECT * FROM resume_optimizer_corrections ORDER BY created_at DESC LIMIT ?",
+ (limit,),
+ ).fetchall()
+ finally:
+ conn.close()
+ return [
+ {
+ "id": r["id"],
+ "job_id": r["job_id"],
+ "section": r["section"],
+ "proposed": _json.loads(r["proposed_json"]),
+ "accepted": _json.loads(r["accepted_json"]),
+ "created_at": r["created_at"],
+ }
+ for r in rows
+ ]
diff --git a/scripts/generate_cover_letter.py b/scripts/generate_cover_letter.py
index 87f4dce..e2ff93b 100644
--- a/scripts/generate_cover_letter.py
+++ b/scripts/generate_cover_letter.py
@@ -186,7 +186,7 @@ def build_prompt(
)
parts.append(f"{recruiter_note}\n")
- parts.append(f"Now write a new cover letter for:")
+ parts.append("Now write a new cover letter for:")
parts.append(f" Role: {title}")
parts.append(f" Company: {company}")
if description:
diff --git a/scripts/integrations/apple_calendar.py b/scripts/integrations/apple_calendar.py
index 3da9b57..9554323 100644
--- a/scripts/integrations/apple_calendar.py
+++ b/scripts/integrations/apple_calendar.py
@@ -1,5 +1,5 @@
from __future__ import annotations
-from datetime import datetime, timedelta, timezone
+from datetime import datetime
from scripts.integrations.base import IntegrationBase
diff --git a/scripts/migrate.py b/scripts/migrate.py
index edf97cf..b1d888a 100644
--- a/scripts/migrate.py
+++ b/scripts/migrate.py
@@ -25,7 +25,6 @@ import argparse
import shutil
import sys
from pathlib import Path
-from textwrap import dedent
import yaml
diff --git a/scripts/preflight.py b/scripts/preflight.py
index 34d7907..46ddd83 100644
--- a/scripts/preflight.py
+++ b/scripts/preflight.py
@@ -348,14 +348,14 @@ def write_compose_override(ports: dict[str, dict]) -> None:
for name, info in to_disable.items():
lines += [
f" {name}: # adopted — host service on :{info['resolved']}",
- f" entrypoint: [\"/bin/sh\", \"-c\", \"sleep infinity\"]",
- f" ports: []",
- f" healthcheck:",
- f" test: [\"CMD\", \"true\"]",
- f" interval: 1s",
- f" timeout: 1s",
- f" start_period: 0s",
- f" retries: 1",
+ " entrypoint: [\"/bin/sh\", \"-c\", \"sleep infinity\"]",
+ " ports: []",
+ " healthcheck:",
+ " test: [\"CMD\", \"true\"]",
+ " interval: 1s",
+ " timeout: 1s",
+ " start_period: 0s",
+ " retries: 1",
]
OVERRIDE_YML.write_text("\n".join(lines) + "\n")
diff --git a/scripts/resume_optimizer.py b/scripts/resume_optimizer.py
index 7b13a20..ff853be 100644
--- a/scripts/resume_optimizer.py
+++ b/scripts/resume_optimizer.py
@@ -19,7 +19,6 @@ from __future__ import annotations
import json
import logging
import re
-from pathlib import Path
from typing import Any
log = logging.getLogger(__name__)
diff --git a/scripts/resume_parser.py b/scripts/resume_parser.py
index ed9f74b..aa7e67e 100644
--- a/scripts/resume_parser.py
+++ b/scripts/resume_parser.py
@@ -9,11 +9,9 @@ Falls back to empty dict on unrecoverable errors — caller shows the form build
from __future__ import annotations
import io
-import json
import logging
import re
import zipfile
-from pathlib import Path
from xml.etree import ElementTree as ET
import pdfplumber
diff --git a/scripts/survey_assistant.py b/scripts/survey_assistant.py
index 9fb4380..f7e9773 100644
--- a/scripts/survey_assistant.py
+++ b/scripts/survey_assistant.py
@@ -7,7 +7,6 @@ FastAPI application. Callable directly or via the survey_analyze background task
from __future__ import annotations
-import json
import logging
from pathlib import Path
from typing import Optional
diff --git a/scripts/task_runner.py b/scripts/task_runner.py
index c66298c..0795176 100644
--- a/scripts/task_runner.py
+++ b/scripts/task_runner.py
@@ -341,7 +341,6 @@ def _run_task(db_path: Path, task_id: int, task_type: str, job_id: int,
prioritize_gaps,
rewrite_for_ats,
hallucination_check,
- render_resume_text,
)
from scripts.user_profile import load_user_profile
diff --git a/scripts/task_scheduler.py b/scripts/task_scheduler.py
index c1be4db..261e5a8 100644
--- a/scripts/task_scheduler.py
+++ b/scripts/task_scheduler.py
@@ -15,13 +15,11 @@ Public API (unchanged — callers do not need to change):
from __future__ import annotations
import logging
-import os
import threading
from pathlib import Path
from typing import Callable, Optional
from circuitforge_core.tasks.scheduler import (
- TaskSpec, # re-export unchanged
LocalScheduler as _CoreTaskScheduler,
)