feat: wizard fields in UserProfile + params column in background_tasks
- Add tier, dev_tier_override, wizard_complete, wizard_step, dismissed_banners fields to UserProfile with defaults and effective_tier property - Add params TEXT column to background_tasks table (CREATE + migration) - Update insert_task() to accept params with params-aware dedup logic - Update submit_task() and _run_task() to thread params through - Add test_wizard_defaults, test_effective_tier_override, test_effective_tier_no_override, and test_insert_task_with_params
This commit is contained in:
parent
1c39af564d
commit
450bfe1913
6 changed files with 100 additions and 20 deletions
|
|
@ -30,6 +30,12 @@ candidate_accessibility_focus: false
|
|||
# Adds an LGBTQIA+ inclusion section (ERGs, non-discrimination policies, culture signals).
|
||||
candidate_lgbtq_focus: false
|
||||
|
||||
tier: free # free | paid | premium
|
||||
dev_tier_override: null # overrides tier locally (for testing only)
|
||||
wizard_complete: false
|
||||
wizard_step: 0
|
||||
dismissed_banners: []
|
||||
|
||||
docs_dir: "~/Documents/JobSearch"
|
||||
ollama_models_dir: "~/models/ollama"
|
||||
vllm_models_dir: "~/models/vllm"
|
||||
|
|
|
|||
|
|
@ -84,7 +84,8 @@ CREATE_BACKGROUND_TASKS = """
|
|||
CREATE TABLE IF NOT EXISTS background_tasks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
task_type TEXT NOT NULL,
|
||||
job_id INTEGER NOT NULL,
|
||||
job_id INTEGER DEFAULT 0,
|
||||
params TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'queued',
|
||||
error TEXT,
|
||||
created_at DATETIME DEFAULT (datetime('now')),
|
||||
|
|
@ -150,6 +151,10 @@ def _migrate_db(db_path: Path) -> None:
|
|||
conn.execute("ALTER TABLE background_tasks ADD COLUMN updated_at TEXT")
|
||||
except sqlite3.OperationalError:
|
||||
pass
|
||||
try:
|
||||
conn.execute("ALTER TABLE background_tasks ADD COLUMN params TEXT")
|
||||
except sqlite3.OperationalError:
|
||||
pass # column already exists
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
|
@ -641,28 +646,40 @@ def get_survey_responses(db_path: Path = DEFAULT_DB, job_id: int = None) -> list
|
|||
# ── Background task helpers ───────────────────────────────────────────────────
|
||||
|
||||
def insert_task(db_path: Path = DEFAULT_DB, task_type: str = "",
|
||||
job_id: int = None) -> tuple[int, bool]:
|
||||
job_id: int = None,
|
||||
params: Optional[str] = None) -> tuple[int, bool]:
|
||||
"""Insert a new background task.
|
||||
|
||||
Returns (task_id, True) if inserted, or (existing_id, False) if a
|
||||
queued/running task for the same (task_type, job_id) already exists.
|
||||
|
||||
Dedup key: (task_type, job_id) when params is None;
|
||||
(task_type, job_id, params) when params is provided.
|
||||
"""
|
||||
conn = sqlite3.connect(db_path)
|
||||
existing = conn.execute(
|
||||
"SELECT id FROM background_tasks WHERE task_type=? AND job_id=? AND status IN ('queued','running')",
|
||||
(task_type, job_id),
|
||||
).fetchone()
|
||||
if existing:
|
||||
try:
|
||||
if params is not None:
|
||||
existing = conn.execute(
|
||||
"SELECT id FROM background_tasks WHERE task_type=? AND job_id=? "
|
||||
"AND params=? AND status IN ('queued','running')",
|
||||
(task_type, job_id, params),
|
||||
).fetchone()
|
||||
else:
|
||||
existing = conn.execute(
|
||||
"SELECT id FROM background_tasks WHERE task_type=? AND job_id=? "
|
||||
"AND status IN ('queued','running')",
|
||||
(task_type, job_id),
|
||||
).fetchone()
|
||||
if existing:
|
||||
return existing[0], False
|
||||
cur = conn.execute(
|
||||
"INSERT INTO background_tasks (task_type, job_id, params) VALUES (?,?,?)",
|
||||
(task_type, job_id, params),
|
||||
)
|
||||
conn.commit()
|
||||
return cur.lastrowid, True
|
||||
finally:
|
||||
conn.close()
|
||||
return existing[0], False
|
||||
cur = conn.execute(
|
||||
"INSERT INTO background_tasks (task_type, job_id, status) VALUES (?, ?, 'queued')",
|
||||
(task_type, job_id),
|
||||
)
|
||||
task_id = cur.lastrowid
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return task_id, True
|
||||
|
||||
|
||||
def update_task_status(db_path: Path = DEFAULT_DB, task_id: int = None,
|
||||
|
|
|
|||
|
|
@ -24,24 +24,26 @@ from scripts.db import (
|
|||
|
||||
|
||||
def submit_task(db_path: Path = DEFAULT_DB, task_type: str = "",
|
||||
job_id: int = None) -> tuple[int, bool]:
|
||||
job_id: int = None,
|
||||
params: str | None = None) -> tuple[int, bool]:
|
||||
"""Submit a background LLM task.
|
||||
|
||||
Returns (task_id, True) if a new task was queued and a thread spawned.
|
||||
Returns (existing_id, False) if an identical task is already in-flight.
|
||||
"""
|
||||
task_id, is_new = insert_task(db_path, task_type, job_id)
|
||||
task_id, is_new = insert_task(db_path, task_type, job_id or 0, params=params)
|
||||
if is_new:
|
||||
t = threading.Thread(
|
||||
target=_run_task,
|
||||
args=(db_path, task_id, task_type, job_id),
|
||||
args=(db_path, task_id, task_type, job_id or 0, params),
|
||||
daemon=True,
|
||||
)
|
||||
t.start()
|
||||
return task_id, is_new
|
||||
|
||||
|
||||
def _run_task(db_path: Path, task_id: int, task_type: str, job_id: int) -> None:
|
||||
def _run_task(db_path: Path, task_id: int, task_type: str, job_id: int,
|
||||
params: str | None = None) -> None:
|
||||
"""Thread body: run the generator and persist the result."""
|
||||
# job_id == 0 means a global task (e.g. discovery) with no associated job row.
|
||||
job: dict = {}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,11 @@ _DEFAULTS = {
|
|||
"mission_preferences": {},
|
||||
"candidate_accessibility_focus": False,
|
||||
"candidate_lgbtq_focus": False,
|
||||
"tier": "free",
|
||||
"dev_tier_override": None,
|
||||
"wizard_complete": False,
|
||||
"wizard_step": 0,
|
||||
"dismissed_banners": [],
|
||||
"services": {
|
||||
"streamlit_port": 8501,
|
||||
"ollama_host": "localhost",
|
||||
|
|
@ -64,6 +69,11 @@ class UserProfile:
|
|||
self.mission_preferences: dict[str, str] = data.get("mission_preferences", {})
|
||||
self.candidate_accessibility_focus: bool = bool(data.get("candidate_accessibility_focus", False))
|
||||
self.candidate_lgbtq_focus: bool = bool(data.get("candidate_lgbtq_focus", False))
|
||||
self.tier: str = data.get("tier", "free")
|
||||
self.dev_tier_override: str | None = data.get("dev_tier_override") or None
|
||||
self.wizard_complete: bool = bool(data.get("wizard_complete", False))
|
||||
self.wizard_step: int = int(data.get("wizard_step", 0))
|
||||
self.dismissed_banners: list[str] = list(data.get("dismissed_banners", []))
|
||||
self._svc = data["services"]
|
||||
|
||||
# ── Service URLs ──────────────────────────────────────────────────────────
|
||||
|
|
@ -90,6 +100,11 @@ class UserProfile:
|
|||
"""Return ssl_verify flag for a named service (ollama/vllm/searxng)."""
|
||||
return bool(self._svc.get(f"{service}_ssl_verify", True))
|
||||
|
||||
@property
|
||||
def effective_tier(self) -> str:
|
||||
"""Returns dev_tier_override if set, otherwise tier."""
|
||||
return self.dev_tier_override or self.tier
|
||||
|
||||
# ── NDA helpers ───────────────────────────────────────────────────────────
|
||||
def is_nda(self, company: str) -> bool:
|
||||
return company.lower() in self.nda_companies
|
||||
|
|
|
|||
|
|
@ -558,3 +558,21 @@ def test_update_job_fields_ignores_unknown_columns(tmp_path):
|
|||
row = dict(conn.execute("SELECT * FROM jobs WHERE id=?", (job_id,)).fetchone())
|
||||
conn.close()
|
||||
assert row["title"] == "Real Title"
|
||||
|
||||
|
||||
def test_insert_task_with_params(tmp_path):
|
||||
from scripts.db import init_db, insert_task
|
||||
db = tmp_path / "t.db"
|
||||
init_db(db)
|
||||
import json
|
||||
params = json.dumps({"section": "career_summary"})
|
||||
task_id, is_new = insert_task(db, "wizard_generate", 0, params=params)
|
||||
assert is_new is True
|
||||
# Second call with same params = dedup
|
||||
task_id2, is_new2 = insert_task(db, "wizard_generate", 0, params=params)
|
||||
assert is_new2 is False
|
||||
assert task_id == task_id2
|
||||
# Different section = new task
|
||||
params2 = json.dumps({"section": "job_titles"})
|
||||
task_id3, is_new3 = insert_task(db, "wizard_generate", 0, params=params2)
|
||||
assert is_new3 is True
|
||||
|
|
|
|||
|
|
@ -84,3 +84,25 @@ def test_docs_dir_expanded(profile_yaml):
|
|||
p = UserProfile(profile_yaml)
|
||||
assert not str(p.docs_dir).startswith("~")
|
||||
assert p.docs_dir.is_absolute()
|
||||
|
||||
def test_wizard_defaults(tmp_path):
|
||||
p = tmp_path / "user.yaml"
|
||||
p.write_text("name: Test\nemail: t@t.com\ncareer_summary: x\n")
|
||||
u = UserProfile(p)
|
||||
assert u.wizard_complete is False
|
||||
assert u.wizard_step == 0
|
||||
assert u.tier == "free"
|
||||
assert u.dev_tier_override is None
|
||||
assert u.dismissed_banners == []
|
||||
|
||||
def test_effective_tier_override(tmp_path):
|
||||
p = tmp_path / "user.yaml"
|
||||
p.write_text("name: T\nemail: t@t.com\ncareer_summary: x\ntier: free\ndev_tier_override: premium\n")
|
||||
u = UserProfile(p)
|
||||
assert u.effective_tier == "premium"
|
||||
|
||||
def test_effective_tier_no_override(tmp_path):
|
||||
p = tmp_path / "user.yaml"
|
||||
p.write_text("name: T\nemail: t@t.com\ncareer_summary: x\ntier: paid\n")
|
||||
u = UserProfile(p)
|
||||
assert u.effective_tier == "paid"
|
||||
|
|
|
|||
Loading…
Reference in a new issue