feat: implement YAML-based configuration, .env support, and customizable prompt templates for AI analysis

This commit is contained in:
2026-08-22 10:17:38 -07:00
parent 7ec91f384f
commit 0de312edbd
30 changed files with 895 additions and 211 deletions
+45 -19
View File
@@ -21,27 +21,36 @@ import (
)
type WorkerPool struct {
db *db.DB
analyzer analyzer.LLMAnalyzer
filterPipe *filter.FilterPipeline
isRunning atomic.Bool
workerCount int
wg sync.WaitGroup
ctx context.Context
cancel context.CancelFunc
db *db.DB
analyzer analyzer.LLMAnalyzer
filterPipe *filter.FilterPipeline
analysisThreshold float64
isRunning atomic.Bool
workerCount int
wg sync.WaitGroup
ctx context.Context
cancel context.CancelFunc
}
func NewWorkerPool(database *db.DB, analyzer analyzer.LLMAnalyzer, numWorkers int, filterCfg *config.FilterConfig) *WorkerPool {
func NewWorkerPool(database *db.DB, analyzer analyzer.LLMAnalyzer, numWorkers int, threshold float64, filterCfg *config.FilterConfig) *WorkerPool {
filterPipe := filter.NewPipeline(
filter.NewHardKeywordFilter(filterCfg.HardKeywords),
filter.NewScoringFilter(filterCfg.ScoringWeights),
)
if threshold <= 0 {
threshold = filterCfg.AnalysisThreshold
}
if threshold <= 0 {
threshold = 25.0
}
return &WorkerPool{
db: database,
analyzer: analyzer,
filterPipe: filterPipe,
workerCount: numWorkers,
db: database,
analyzer: analyzer,
filterPipe: filterPipe,
analysisThreshold: threshold,
workerCount: numWorkers,
}
}
@@ -255,15 +264,22 @@ func (p *WorkerPool) handleProcess(ctx context.Context, task *db.Task) error {
keep, score, reason := p.filterPipe.Execute(ctx, &job.RawJob)
if !keep {
// Create a rejected JobAnalysis record. Store reason in missing_highlights JSON array
// Disqualified by blacklist: create a rejected record
reasonJSON, _ := json.Marshal([]string{reason})
_ = p.db.CreateJobAnalysisFull(payload.RawJobID, score, false, "[]", string(reasonJSON), "[]", "PARSED")
_ = p.db.CreateJobAnalysisFull(payload.RawJobID, 0.0, 0.0, false, "Disqualified: "+reason, "[]", string(reasonJSON), "[]", "REJECTED")
return nil
}
// It's qualified for analysis. Create the base record and enqueue it.
_ = p.db.CreateJobAnalysis(payload.RawJobID, score, true, "PENDING")
// Gating threshold check
if score < p.analysisThreshold {
summary := fmt.Sprintf("Keyword fit (%.0f) below threshold (%.0f)", score, p.analysisThreshold)
_ = p.db.CreateJobAnalysisFull(payload.RawJobID, score, score, false, summary, "[]", "[]", "[]", "FILTERED_OUT")
return nil
}
// Qualified for deep LLM analysis. Create the pending record and enqueue ANALYZE task.
_ = p.db.CreateJobAnalysis(payload.RawJobID, score, score, true, "PENDING")
analyzePayload := AnalyzePayload(payload)
_ = p.db.EnqueueTask("ANALYZE", analyzePayload)
@@ -281,9 +297,16 @@ func (p *WorkerPool) handleAnalyze(ctx context.Context, task *db.Task) error {
return err
}
// Use empty matched/missing keywords for now, or pass from filter if stored
result, err := p.analyzer.AnalyzeJD(ctx, job.Title, job.RawContent)
if err != nil {
logger.Log.Errorw("LLM analysis failed", "component", "Worker", "raw_job_id", payload.RawJobID, "error", err)
filterScore := 0.0
isQual := false
if job.Analysis != nil {
filterScore = job.Analysis.FilterScore
isQual = job.Analysis.IsQualified
}
_ = p.db.UpdateJobAnalysisFull(payload.RawJobID, filterScore, filterScore, isQual, "Analysis error: "+err.Error(), "[]", "[]", "[]", "FAILED")
return err
}
@@ -293,6 +316,9 @@ func (p *WorkerPool) handleAnalyze(ctx context.Context, task *db.Task) error {
err = p.db.UpdateJobAnalysisLLM(
payload.RawJobID,
result.AIScore,
result.IsQualified,
result.Summary,
string(matchedStr),
string(missingStr),
string(tailoringStr),