Skip to content

Commit 803a813

Browse files
committed
v2.2: updated filters, updated style for frontend
1 parent bfd38ad commit 803a813

38 files changed

+4589
-3035
lines changed

backend/.env.example

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,18 @@ EMBEDDING_MODEL=text-embedding-3-small
3333
# -----------------------------------------------------------------------------
3434
# Redis Configuration
3535
# -----------------------------------------------------------------------------
36+
REDIS_HOST=redis
3637
REDIS_PORT=6379
3738
REDIS_PASSWORD=
3839
REDIS_JOB_PREFIX=cv:job:
39-
REDIS_JOB_QUEUE=cv_processing_queue
4040
REDIS_CLEANUP_QUEUE=cv_cleanup_queue
4141
REDIS_JOB_TIMEOUT=1800
42-
REDIS_WORKER_TIMEOUT=3
4342
REDIS_MAX_RETRIES=3
4443

4544
# -----------------------------------------------------------------------------
4645
# Neo4j Graph Database
4746
# -----------------------------------------------------------------------------
47+
NEO4J_HOST=neo4j
4848
NEO4J_PORT=7687
4949
NEO4J_USERNAME=neo4j
5050
NEO4J_PASSWORD=secure_default_password
@@ -56,6 +56,7 @@ NEO4J_CONNECTION_TIMEOUT=30
5656
# -----------------------------------------------------------------------------
5757
# Qdrant Vector Database
5858
# -----------------------------------------------------------------------------
59+
QDRANT_HOST=qdrant
5960
QDRANT_PORT=6333
6061
QDRANT_COLLECTION=cv_key_points
6162
VECTOR_SIZE=1536

backend/backend/settings.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
import sys
3+
import uuid
34
from pathlib import Path
45

56
from dotenv import load_dotenv
@@ -184,10 +185,8 @@
184185
REDIS_PORT = int(os.getenv("REDIS_PORT", "6379"))
185186
REDIS_PASSWORD = os.getenv("REDIS_PASSWORD", "")
186187
REDIS_JOB_PREFIX = os.getenv("REDIS_JOB_PREFIX", "cv:job:")
187-
REDIS_JOB_QUEUE = os.getenv("REDIS_JOB_QUEUE", "cv_processing_queue")
188188
REDIS_CLEANUP_QUEUE = os.getenv("REDIS_CLEANUP_QUEUE", "cv_cleanup_queue")
189189
REDIS_JOB_TIMEOUT = int(os.getenv("REDIS_JOB_TIMEOUT", "1800"))
190-
REDIS_WORKER_TIMEOUT = int(os.getenv("REDIS_WORKER_TIMEOUT", "30"))
191190
REDIS_MAX_RETRIES = int(os.getenv("REDIS_MAX_RETRIES", "3"))
192191

193192
# =============================================================================
@@ -255,6 +254,7 @@
255254
# WORKER
256255
# =============================================================================
257256

257+
WORKER_ID = os.getenv("WORKER_ID", f"worker-{uuid.uuid4().hex[:8]}")
258258
WORKER_STORE_IN_DB = os.getenv("WORKER_STORE_IN_DB", "true").lower() == "true"
259259
WORKER_GENERATE_REVIEW = os.getenv("WORKER_GENERATE_REVIEW", "true").lower() == "true"
260260
WORKER_CONCURRENT_JOBS = int(os.getenv("WORKER_CONCURRENT_JOBS", "3"))

backend/backend/urls.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88
from processor.utils.redis_queue import RedisJobQueue
99

1010

11-
def metrics_view(_request):
11+
async def metrics_view(_request):
1212
"""Collect current metrics and return Prometheus format."""
1313
# Update queue metrics before generating output
1414
try:
1515
redis_queue = RedisJobQueue()
16-
stats = redis_queue.get_queue_stats()
16+
stats = await redis_queue.get_queue_stats()
1717
update_queue_metrics(stats)
1818
except Exception:
1919
# If Redis is down, metrics endpoint should still work

backend/core/domain/resume.py

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import re
12
from enum import StrEnum
23

34
from pydantic import BaseModel, Field, model_validator
@@ -118,11 +119,36 @@ class CompanyInfo(BaseModel):
118119

119120

120121
class EmploymentDuration(BaseModel):
121-
date_format: str
122-
start: str
123-
end: str
122+
start: str # Format: YYYY.MM
123+
end: str | None = None # Format: YYYY.MM or None for ongoing
124124
duration_months: int
125125

126+
@model_validator(mode="before")
127+
@classmethod
128+
def normalize_dates(cls, v: dict):
129+
"""Convert MM.YYYY or MMM YYYY to YYYY.MM format"""
130+
if isinstance(v, dict):
131+
for field in ["start", "end"]:
132+
if field in v and isinstance(v[field], str):
133+
val = v[field].strip()
134+
if val.lower() in ("present", "current", ""):
135+
v[field] = None
136+
elif match := re.match(r'(\d{2})\.(\d{4})', val): # MM.YYYY
137+
month, year = match.groups()
138+
v[field] = f"{year}.{month}"
139+
elif match := re.match(r'([A-Za-z]{3})\s+(\d{4})', val): # MMM YYYY
140+
month_name, year = match.groups()
141+
month_map = {
142+
'jan': '01', 'feb': '02', 'mar': '03', 'apr': '04',
143+
'may': '05', 'jun': '06', 'jul': '07', 'aug': '08',
144+
'sep': '09', 'oct': '10', 'nov': '11', 'dec': '12'
145+
}
146+
month = month_map.get(month_name.lower()[:3], '01')
147+
v[field] = f"{year}.{month}"
148+
elif match := re.match(r'(\d{4})', val): # YYYY only
149+
v[field] = f"{val}.01"
150+
return v
151+
126152

127153
class KeyPoint(BaseModel):
128154
text: str
@@ -202,8 +228,8 @@ class EducationItem(BaseModel):
202228
field: str
203229
institution: InstitutionInfo
204230
location: Location | None = None
205-
start: str | None = None
206-
end: str | None = None
231+
start: str | None = None # Format: YYYY.MM
232+
end: str | None = None # Format: YYYY.MM or None for in-progress
207233
status: EducationStatus
208234
coursework: list[Coursework] = Field(default_factory=list)
209235
extras: list[EducationExtra] = Field(default_factory=list)
@@ -213,6 +239,27 @@ class EducationItem(BaseModel):
213239
def accept_legacy_education(cls, v: dict):
214240
if "institution" in v and isinstance(v["institution"], str):
215241
v["institution"] = {"name": v["institution"]}
242+
243+
# Normalize dates to YYYY.MM format
244+
for field in ["start", "end"]:
245+
if field in v and isinstance(v[field], str):
246+
val = v[field].strip()
247+
if val.lower() in ("present", "current", ""):
248+
v[field] = None
249+
elif match := re.match(r'(\d{2})\.(\d{4})', val): # MM.YYYY
250+
month, year = match.groups()
251+
v[field] = f"{year}.{month}"
252+
elif match := re.match(r'([A-Za-z]{3})\s+(\d{4})', val): # MMM YYYY
253+
month_name, year = match.groups()
254+
month_map = {
255+
'jan': '01', 'feb': '02', 'mar': '03', 'apr': '04',
256+
'may': '05', 'jun': '06', 'jul': '07', 'aug': '08',
257+
'sep': '09', 'oct': '10', 'nov': '11', 'dec': '12'
258+
}
259+
month = month_map.get(month_name.lower()[:3], '01')
260+
v[field] = f"{year}.{month}"
261+
elif match := re.match(r'(\d{4})', val): # YYYY only
262+
v[field] = f"{val}.01"
216263
if "start_date" in v and "start" not in v:
217264
v["start"] = v.pop("start_date")
218265
if "end_date" in v and "end" not in v:

backend/core/domain/search.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from dataclasses import dataclass, field
22
from enum import StrEnum
33

4+
from core.domain.resume import EducationStatus
5+
46

57
class SearchType(StrEnum):
68
SEMANTIC = "semantic"
@@ -15,6 +17,8 @@ class SearchFilters:
1517
company: str | None = None
1618
location: str | None = None
1719
years_experience: int | None = None
20+
education_level: str | None = None # Bachelor, Master, PhD, etc.
21+
education_status: EducationStatus | None = None
1822

1923

2024
@dataclass
@@ -29,6 +33,8 @@ class FilterOptionsResult:
2933
roles: list[FilterOption] = field(default_factory=list)
3034
companies: list[FilterOption] = field(default_factory=list)
3135
locations: list[FilterOption] = field(default_factory=list)
36+
education_levels: list[FilterOption] = field(default_factory=list)
37+
education_statuses: list[FilterOption] = field(default_factory=list)
3238

3339

3440
@dataclass

backend/processor/services/cleanup_service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ async def cleanup_job(self, job_id: str) -> bool:
2525
else:
2626
file_ext = None
2727

28-
FileService.cleanup_all_job_files(job_id, file_ext)
28+
await FileService.cleanup_all_job_files(job_id, file_ext)
2929
self.graph_db.delete_resume(resume_id=job_id)
3030
self.vector_db.delete_resume_vectors(job_id)
3131
return await self.job_service.delete_job(job_id)

0 commit comments

Comments
 (0)