Britzzy commited on
Commit
b4f9df5
·
1 Parent(s): fb1c672

feat: incrementing counter file naming (01_name_company) + fix API key overwrite bug + better LLM logging

Browse files
Files changed (3) hide show
  1. backend/database.py +22 -4
  2. backend/llm.py +10 -4
  3. backend/main.py +11 -15
backend/database.py CHANGED
@@ -114,7 +114,8 @@ CREATE TABLE IF NOT EXISTS global_jobs (
114
 
115
  CREATE TABLE IF NOT EXISTS user_settings (
116
  user_id INTEGER PRIMARY KEY,
117
- use_default_api INTEGER DEFAULT 1
 
118
  );
119
 
120
  CREATE TABLE IF NOT EXISTS user_job_links (
@@ -194,7 +195,8 @@ CREATE TABLE IF NOT EXISTS global_jobs (
194
 
195
  CREATE TABLE IF NOT EXISTS user_settings (
196
  user_id INTEGER PRIMARY KEY,
197
- use_default_api INTEGER DEFAULT 1
 
198
  );
199
 
200
  CREATE TABLE IF NOT EXISTS user_job_links (
@@ -290,6 +292,7 @@ def init_db():
290
  _safe_alter(conn, "ALTER TABLE global_jobs ALTER COLUMN date_found TYPE TIMESTAMP USING date_found::timestamp")
291
  _safe_alter(conn, "ALTER TABLE user_cv ADD COLUMN IF NOT EXISTS raw_text TEXT DEFAULT ''")
292
  _safe_alter(conn, "ALTER TABLE users ADD COLUMN IF NOT EXISTS is_admin INTEGER DEFAULT 0")
 
293
  _seed_categories(conn)
294
  _seed_admin_user(conn)
295
  else:
@@ -303,6 +306,10 @@ def init_db():
303
  conn.execute("ALTER TABLE user_cv ADD COLUMN raw_text TEXT DEFAULT ''")
304
  except _sqlite3.OperationalError:
305
  pass
 
 
 
 
306
  _seed_categories(conn)
307
  _seed_admin_user(conn)
308
 
@@ -454,6 +461,17 @@ def save_user_settings(user_id: int, settings: dict) -> None:
454
  )
455
 
456
 
 
 
 
 
 
 
 
 
 
 
 
457
  def get_api_keys(user_id: int) -> dict:
458
  with get_db() as conn:
459
  rows = _exec(conn, "SELECT provider, api_key FROM api_keys WHERE user_id = ?", (user_id,)).fetchall()
@@ -464,8 +482,8 @@ def get_effective_api_keys(user_id: int) -> dict:
464
  from backend.config import DEFAULT_NVIDIA_KEY
465
  keys = get_api_keys(user_id)
466
  settings = get_user_settings(user_id)
467
- if settings.get("use_default_api", True):
468
- keys["nvidia"] = DEFAULT_NVIDIA_KEY
469
  return keys
470
 
471
 
 
114
 
115
  CREATE TABLE IF NOT EXISTS user_settings (
116
  user_id INTEGER PRIMARY KEY,
117
+ use_default_api INTEGER DEFAULT 1,
118
+ cv_gen_count INTEGER DEFAULT 0
119
  );
120
 
121
  CREATE TABLE IF NOT EXISTS user_job_links (
 
195
 
196
  CREATE TABLE IF NOT EXISTS user_settings (
197
  user_id INTEGER PRIMARY KEY,
198
+ use_default_api INTEGER DEFAULT 1,
199
+ cv_gen_count INTEGER DEFAULT 0
200
  );
201
 
202
  CREATE TABLE IF NOT EXISTS user_job_links (
 
292
  _safe_alter(conn, "ALTER TABLE global_jobs ALTER COLUMN date_found TYPE TIMESTAMP USING date_found::timestamp")
293
  _safe_alter(conn, "ALTER TABLE user_cv ADD COLUMN IF NOT EXISTS raw_text TEXT DEFAULT ''")
294
  _safe_alter(conn, "ALTER TABLE users ADD COLUMN IF NOT EXISTS is_admin INTEGER DEFAULT 0")
295
+ _safe_alter(conn, "ALTER TABLE user_settings ADD COLUMN IF NOT EXISTS cv_gen_count INTEGER DEFAULT 0")
296
  _seed_categories(conn)
297
  _seed_admin_user(conn)
298
  else:
 
306
  conn.execute("ALTER TABLE user_cv ADD COLUMN raw_text TEXT DEFAULT ''")
307
  except _sqlite3.OperationalError:
308
  pass
309
+ try:
310
+ conn.execute("ALTER TABLE user_settings ADD COLUMN cv_gen_count INTEGER DEFAULT 0")
311
+ except _sqlite3.OperationalError:
312
+ pass
313
  _seed_categories(conn)
314
  _seed_admin_user(conn)
315
 
 
461
  )
462
 
463
 
464
+ def get_and_increment_cv_gen_count(user_id: int) -> int:
465
+ with get_db() as conn:
466
+ rows = _exec(conn, "SELECT cv_gen_count FROM user_settings WHERE user_id = ?", (user_id,)).fetchall()
467
+ if not rows:
468
+ _exec(conn, "INSERT INTO user_settings (user_id, use_default_api, cv_gen_count) VALUES (?, 1, 1)")
469
+ return 1
470
+ current = int(rows[0]["cv_gen_count"] or 0)
471
+ _exec(conn, "UPDATE user_settings SET cv_gen_count = ? WHERE user_id = ?", (current + 1, user_id))
472
+ return current + 1
473
+
474
+
475
  def get_api_keys(user_id: int) -> dict:
476
  with get_db() as conn:
477
  rows = _exec(conn, "SELECT provider, api_key FROM api_keys WHERE user_id = ?", (user_id,)).fetchall()
 
482
  from backend.config import DEFAULT_NVIDIA_KEY
483
  keys = get_api_keys(user_id)
484
  settings = get_user_settings(user_id)
485
+ if settings.get("use_default_api", True) and DEFAULT_NVIDIA_KEY:
486
+ keys.setdefault("nvidia", DEFAULT_NVIDIA_KEY)
487
  return keys
488
 
489
 
backend/llm.py CHANGED
@@ -166,10 +166,16 @@ def _call_gemma(prompt: str, api_key: str, max_tokens: int = 512) -> str | None:
166
 
167
 
168
  def _call_any(prompt: str, api_keys: dict, max_tokens: int = 512) -> str | None:
169
- return _call_nim(prompt, api_keys.get("nvidia", ""), max_tokens) or \
170
- _call_groq(prompt, api_keys.get("groq", ""), max_tokens) or \
171
- _call_gemini(prompt, api_keys.get("gemini", ""), max_tokens) or \
172
- _call_gemma(prompt, api_keys.get("gemini", ""), max_tokens)
 
 
 
 
 
 
173
 
174
 
175
  SKILL_TAXONOMY = {
 
166
 
167
 
168
  def _call_any(prompt: str, api_keys: dict, max_tokens: int = 512) -> str | None:
169
+ if not any(api_keys.values()):
170
+ print("[llm] No API keys available — will use rule-based fallback")
171
+ result = _call_nim(prompt, api_keys.get("nvidia", ""), max_tokens)
172
+ if result: return result
173
+ result = _call_groq(prompt, api_keys.get("groq", ""), max_tokens)
174
+ if result: return result
175
+ result = _call_gemini(prompt, api_keys.get("gemini", ""), max_tokens)
176
+ if result: return result
177
+ result = _call_gemma(prompt, api_keys.get("gemini", ""), max_tokens)
178
+ return result
179
 
180
 
181
  SKILL_TAXONOMY = {
backend/main.py CHANGED
@@ -13,7 +13,7 @@ from backend.cron import run_nightly_scrape
13
  from backend.database import (
14
  init_db, create_user, get_user_by_email, get_user_by_id,
15
  save_api_keys, get_api_keys, get_effective_api_keys,
16
- save_user_settings, get_user_settings,
17
  save_cv, get_cv, get_cv_default, save_cv_raw_text, get_cv_raw_text,
18
  save_global_jobs, get_global_jobs, get_global_job, get_global_stats,
19
  get_categories, classify_job_title,
@@ -27,20 +27,15 @@ from backend.cv_quality import score_cv_quality
27
  from backend.docx_generator import generate_cv_docx, generate_cover_docx, generate_cv_pdf, generate_cover_pdf, generate_cv_preview_text, get_cv_profile
28
 
29
 
30
- def _file_slug(name: str, title: str, company: str = "", number: str = "00") -> str:
31
- parts = [number]
32
  if name:
33
  parts.append(re.sub(r"[^a-zA-Z0-9]", "", name)[:20])
34
- if title:
35
- stitle = re.sub(r"[^a-zA-Z0-9 ]", "", title).strip()[:20]
36
- if stitle:
37
- words = stitle.split()
38
- parts.append("".join(w.capitalize() for w in words[:3]))
39
  if company:
40
  scompany = re.sub(r"[^a-zA-Z0-9]", "", company)[:10]
41
  if scompany:
42
  parts.append(scompany)
43
- return "_".join(parts) if parts else f"{number}_cv"
44
  from backend.cv_diversity import randomize_tailored_cv
45
  from backend.excel_export import export_jobs_to_excel
46
 
@@ -238,7 +233,8 @@ def make_cv(req: MakeCVRequest, current_user: dict = Depends(get_current_user)):
238
  preview = generate_cv_preview_text(cv_data)
239
 
240
  name = (cv_data.get("personal_info") or {}).get("name", "")
241
- slug = _file_slug(name, req.target_jobs[0] if req.target_jobs else "CV", "", number="00")
 
242
  cv_path = str(GENERATED_DIR / f"{slug}_cv.docx")
243
  cv_pdf_path = str(GENERATED_DIR / f"{slug}_cv.pdf")
244
  profile = get_cv_profile(str(user_id))
@@ -659,8 +655,8 @@ def tailor_from_job_data(
659
  tailored_cv = randomize_tailored_cv(tailored_cv, job.get("title", ""), seed=user_id)
660
 
661
  name = (tailored_cv.get("personal_info") or {}).get("name", "")
662
- score = result.get("match_score", 0)
663
- slug = _file_slug(name, job.get("title", ""), job.get("company", ""), number=str(score).zfill(2))
664
  cv_path = str(GENERATED_DIR / f"{slug}_cv.docx")
665
  cover_path = str(GENERATED_DIR / f"{slug}_cover.docx")
666
  cv_pdf_path = str(GENERATED_DIR / f"{slug}_cv.pdf")
@@ -673,7 +669,7 @@ def tailor_from_job_data(
673
 
674
  return {
675
  "status": "ok",
676
- "match_score": score,
677
  "provider": result.get("provider", "rule-based"),
678
  "keywords_hit": result.get("keywords_hit", []),
679
  "professional_summary": result.get("professional_summary", ""),
@@ -725,8 +721,8 @@ def tailor_job(job_id: int, current_user: dict = Depends(get_current_user)):
725
  tailored_cv = randomize_tailored_cv(tailored_cv, job.get("title", ""), seed=user_id)
726
 
727
  name = (tailored_cv.get("personal_info") or {}).get("name", "")
728
- score = result.get("match_score", 0)
729
- slug = _file_slug(name, job.get("title", ""), job.get("company", ""), number=str(score).zfill(2))
730
  cv_path = str(GENERATED_DIR / f"{slug}_cv.docx")
731
  cover_path = str(GENERATED_DIR / f"{slug}_cover.docx")
732
  cv_pdf_path = str(GENERATED_DIR / f"{slug}_cv.pdf")
 
13
  from backend.database import (
14
  init_db, create_user, get_user_by_email, get_user_by_id,
15
  save_api_keys, get_api_keys, get_effective_api_keys,
16
+ save_user_settings, get_user_settings, get_and_increment_cv_gen_count,
17
  save_cv, get_cv, get_cv_default, save_cv_raw_text, get_cv_raw_text,
18
  save_global_jobs, get_global_jobs, get_global_job, get_global_stats,
19
  get_categories, classify_job_title,
 
27
  from backend.docx_generator import generate_cv_docx, generate_cover_docx, generate_cv_pdf, generate_cover_pdf, generate_cv_preview_text, get_cv_profile
28
 
29
 
30
+ def _file_slug(name: str, company: str = "", counter: int = 0) -> str:
31
+ parts = [str(counter).zfill(2)]
32
  if name:
33
  parts.append(re.sub(r"[^a-zA-Z0-9]", "", name)[:20])
 
 
 
 
 
34
  if company:
35
  scompany = re.sub(r"[^a-zA-Z0-9]", "", company)[:10]
36
  if scompany:
37
  parts.append(scompany)
38
+ return "_".join(parts) if parts else f"{str(counter).zfill(2)}_cv"
39
  from backend.cv_diversity import randomize_tailored_cv
40
  from backend.excel_export import export_jobs_to_excel
41
 
 
233
  preview = generate_cv_preview_text(cv_data)
234
 
235
  name = (cv_data.get("personal_info") or {}).get("name", "")
236
+ counter = get_and_increment_cv_gen_count(user_id)
237
+ slug = _file_slug(name, counter=counter)
238
  cv_path = str(GENERATED_DIR / f"{slug}_cv.docx")
239
  cv_pdf_path = str(GENERATED_DIR / f"{slug}_cv.pdf")
240
  profile = get_cv_profile(str(user_id))
 
655
  tailored_cv = randomize_tailored_cv(tailored_cv, job.get("title", ""), seed=user_id)
656
 
657
  name = (tailored_cv.get("personal_info") or {}).get("name", "")
658
+ counter = get_and_increment_cv_gen_count(user_id)
659
+ slug = _file_slug(name, job.get("company", ""), counter=counter)
660
  cv_path = str(GENERATED_DIR / f"{slug}_cv.docx")
661
  cover_path = str(GENERATED_DIR / f"{slug}_cover.docx")
662
  cv_pdf_path = str(GENERATED_DIR / f"{slug}_cv.pdf")
 
669
 
670
  return {
671
  "status": "ok",
672
+ "match_score": result.get("match_score", 0),
673
  "provider": result.get("provider", "rule-based"),
674
  "keywords_hit": result.get("keywords_hit", []),
675
  "professional_summary": result.get("professional_summary", ""),
 
721
  tailored_cv = randomize_tailored_cv(tailored_cv, job.get("title", ""), seed=user_id)
722
 
723
  name = (tailored_cv.get("personal_info") or {}).get("name", "")
724
+ counter = get_and_increment_cv_gen_count(user_id)
725
+ slug = _file_slug(name, job.get("company", ""), counter=counter)
726
  cv_path = str(GENERATED_DIR / f"{slug}_cv.docx")
727
  cover_path = str(GENERATED_DIR / f"{slug}_cover.docx")
728
  cv_pdf_path = str(GENERATED_DIR / f"{slug}_cv.pdf")