feat: implement YAML-based configuration, .env support, and customizable prompt templates for AI analysis
This commit is contained in:
+59
-35
@@ -10,11 +10,11 @@ import (
|
||||
)
|
||||
|
||||
// Target Queries
|
||||
func (db *DB) CreateTarget(name, targetURL, platform string) (*SearchTarget, error) {
|
||||
query := `INSERT INTO search_targets (name, target_url, platform, is_active) VALUES (?, ?, ?, 1) RETURNING id, name, target_url, platform, is_active, last_crawled_at`
|
||||
func (db *DB) CreateTarget(name, targetURL, platform string, isActive bool) (*SearchTarget, error) {
|
||||
query := `INSERT INTO search_targets (name, target_url, platform, is_active) VALUES (?, ?, ?, ?) RETURNING id, name, target_url, platform, is_active, last_crawled_at`
|
||||
var target SearchTarget
|
||||
var lastCrawled sql.NullTime
|
||||
err := db.QueryRow(query, name, targetURL, platform).Scan(
|
||||
err := db.QueryRow(query, name, targetURL, platform, isActive).Scan(
|
||||
&target.ID, &target.Name, &target.TargetURL, &target.Platform, &target.IsActive, &lastCrawled,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -78,6 +78,12 @@ func (db *DB) ToggleTargetStatus(id int64) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DB) SetTargetActive(id int64, isActive bool) error {
|
||||
query := `UPDATE search_targets SET is_active = ? WHERE id = ?`
|
||||
_, err := db.Exec(query, isActive, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DB) UpdateTargetCrawledAt(id int64) error {
|
||||
query := `UPDATE search_targets SET last_crawled_at = CURRENT_TIMESTAMP WHERE id = ?`
|
||||
_, err := db.Exec(query, id)
|
||||
@@ -408,49 +414,59 @@ func tokenizeToVector(text string) map[string]int {
|
||||
}
|
||||
|
||||
// Job Analyses Queries
|
||||
func (db *DB) CreateJobAnalysis(rawJobID int64, score float64, isQualified bool, status string) error {
|
||||
func (db *DB) CreateJobAnalysis(rawJobID int64, filterScore, score float64, isQualified bool, status string) error {
|
||||
query := `
|
||||
INSERT INTO job_analyses (raw_job_id, score, is_qualified, status)
|
||||
VALUES (?, ?, ?, ?)
|
||||
ON CONFLICT(raw_job_id) DO UPDATE SET score=excluded.score, is_qualified=excluded.is_qualified, status=excluded.status
|
||||
INSERT INTO job_analyses (raw_job_id, filter_score, score, is_qualified, status)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
ON CONFLICT(raw_job_id) DO UPDATE SET filter_score=excluded.filter_score, score=excluded.score, is_qualified=excluded.is_qualified, status=excluded.status
|
||||
`
|
||||
_, err := db.Exec(query, rawJobID, score, isQualified, status)
|
||||
_, err := db.Exec(query, rawJobID, filterScore, score, isQualified, status)
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DB) CreateJobAnalysisFull(rawJobID int64, score float64, isQualified bool, matched, missing, tailoring, status string) error {
|
||||
func (db *DB) CreateJobAnalysisFull(rawJobID int64, filterScore, score float64, isQualified bool, summary, matched, missing, tailoring, status string) error {
|
||||
query := `
|
||||
INSERT INTO job_analyses (raw_job_id, score, is_qualified, matched_highlights, missing_highlights, tailoring_suggestions, status, analyzed_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||
INSERT INTO job_analyses (raw_job_id, filter_score, score, is_qualified, summary, matched_highlights, missing_highlights, tailoring_suggestions, status, analyzed_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT(raw_job_id) DO UPDATE SET
|
||||
filter_score=excluded.filter_score,
|
||||
score=excluded.score,
|
||||
is_qualified=excluded.is_qualified,
|
||||
summary=excluded.summary,
|
||||
matched_highlights=excluded.matched_highlights,
|
||||
missing_highlights=excluded.missing_highlights,
|
||||
tailoring_suggestions=excluded.tailoring_suggestions,
|
||||
status=excluded.status,
|
||||
analyzed_at=CURRENT_TIMESTAMP
|
||||
`
|
||||
_, err := db.Exec(query, rawJobID, score, isQualified, matched, missing, tailoring, status)
|
||||
_, err := db.Exec(query, rawJobID, filterScore, score, isQualified, summary, matched, missing, tailoring, status)
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DB) UpdateJobAnalysisFull(rawJobID int64, score float64, isQualified bool, matchedJSON, missingJSON, suggestionsJSON, status string) error {
|
||||
func (db *DB) UpdateJobAnalysisFull(rawJobID int64, filterScore, score float64, isQualified bool, summary, matchedJSON, missingJSON, suggestionsJSON, status string) error {
|
||||
query := `
|
||||
UPDATE job_analyses
|
||||
SET score = ?, is_qualified = ?, matched_highlights = ?, missing_highlights = ?, tailoring_suggestions = ?, status = ?, analyzed_at = CURRENT_TIMESTAMP
|
||||
SET filter_score = ?, score = ?, is_qualified = ?, summary = ?, matched_highlights = ?, missing_highlights = ?, tailoring_suggestions = ?, status = ?, analyzed_at = CURRENT_TIMESTAMP
|
||||
WHERE raw_job_id = ?
|
||||
`
|
||||
_, err := db.Exec(query, score, isQualified, matchedJSON, missingJSON, suggestionsJSON, status, rawJobID)
|
||||
_, err := db.Exec(query, filterScore, score, isQualified, summary, matchedJSON, missingJSON, suggestionsJSON, status, rawJobID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DB) UpdateJobAnalysisLLM(rawJobID int64, matchedJSON, missingJSON, suggestionsJSON string) error {
|
||||
func (db *DB) UpdateJobAnalysisLLM(rawJobID int64, aiScore float64, isQualified bool, summary, matchedJSON, missingJSON, suggestionsJSON string) error {
|
||||
query := `
|
||||
UPDATE job_analyses
|
||||
SET matched_highlights = ?, missing_highlights = ?, tailoring_suggestions = ?, status = 'PARSED', analyzed_at = CURRENT_TIMESTAMP
|
||||
SET score = ?, is_qualified = ?, summary = ?, matched_highlights = ?, missing_highlights = ?, tailoring_suggestions = ?, status = 'PARSED', analyzed_at = CURRENT_TIMESTAMP
|
||||
WHERE raw_job_id = ?
|
||||
`
|
||||
_, err := db.Exec(query, matchedJSON, missingJSON, suggestionsJSON, rawJobID)
|
||||
_, err := db.Exec(query, aiScore, isQualified, summary, matchedJSON, missingJSON, suggestionsJSON, rawJobID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DB) GetPendingQualifiedAnalyses() ([]JobWithAnalysis, error) {
|
||||
query := `
|
||||
SELECT r.id, r.canonical_id, r.target_id, r.title, r.company, r.location, r.apply_url, r.raw_content, r.crawled_at, r.last_seen_at,
|
||||
a.id, a.score, a.is_qualified, COALESCE(a.matched_highlights, '[]'), COALESCE(a.missing_highlights, '[]'), COALESCE(a.tailoring_suggestions, '[]'), a.status, a.analyzed_at
|
||||
a.id, COALESCE(a.filter_score, 0), COALESCE(a.score, 0), a.is_qualified, COALESCE(a.summary, ''), COALESCE(a.matched_highlights, '[]'), COALESCE(a.missing_highlights, '[]'), COALESCE(a.tailoring_suggestions, '[]'), a.status, a.analyzed_at
|
||||
FROM raw_jobs r
|
||||
JOIN job_analyses a ON r.id = a.raw_job_id
|
||||
WHERE a.is_qualified = 1 AND a.status = 'PENDING'
|
||||
@@ -466,14 +482,14 @@ func (db *DB) GetPendingQualifiedAnalyses() ([]JobWithAnalysis, error) {
|
||||
var item JobWithAnalysis
|
||||
var tID sql.NullInt64
|
||||
var aID int64
|
||||
var score float64
|
||||
var filterScore, score float64
|
||||
var isQualified bool
|
||||
var matched, missing, suggestions, aStatus string
|
||||
var summary, matched, missing, suggestions, aStatus string
|
||||
var analyzedAt sql.NullTime
|
||||
|
||||
err := rows.Scan(
|
||||
&item.ID, &item.CanonicalID, &tID, &item.Title, &item.Company, &item.Location, &item.ApplyURL, &item.RawContent, &item.CrawledAt, &item.LastSeenAt,
|
||||
&aID, &score, &isQualified, &matched, &missing, &suggestions, &aStatus, &analyzedAt,
|
||||
&aID, &filterScore, &score, &isQualified, &summary, &matched, &missing, &suggestions, &aStatus, &analyzedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -484,8 +500,10 @@ func (db *DB) GetPendingQualifiedAnalyses() ([]JobWithAnalysis, error) {
|
||||
analysis := &JobAnalysis{
|
||||
ID: aID,
|
||||
RawJobID: item.ID,
|
||||
FilterScore: filterScore,
|
||||
Score: score,
|
||||
IsQualified: isQualified,
|
||||
Summary: summary,
|
||||
MatchedHighlights: matched,
|
||||
MissingHighlights: missing,
|
||||
TailoringSuggestions: suggestions,
|
||||
@@ -503,12 +521,12 @@ func (db *DB) GetPendingQualifiedAnalyses() ([]JobWithAnalysis, error) {
|
||||
func (db *DB) GetJobsWithAnalysis() ([]JobWithAnalysis, error) {
|
||||
query := `
|
||||
SELECT r.id, r.canonical_id, r.target_id, r.title, r.company, r.location, r.apply_url, r.raw_content, r.crawled_at, r.last_seen_at,
|
||||
a.id, a.score, a.is_qualified, COALESCE(a.matched_highlights, '[]'), COALESCE(a.missing_highlights, '[]'), COALESCE(a.tailoring_suggestions, '[]'), COALESCE(a.status, 'PENDING'), a.analyzed_at,
|
||||
a.id, COALESCE(a.filter_score, 0), COALESCE(a.score, 0), a.is_qualified, COALESCE(a.summary, ''), COALESCE(a.matched_highlights, '[]'), COALESCE(a.missing_highlights, '[]'), COALESCE(a.tailoring_suggestions, '[]'), COALESCE(a.status, 'PENDING'), a.analyzed_at,
|
||||
app.id, app.status, COALESCE(app.notes, ''), app.applied_at, app.updated_at
|
||||
FROM raw_jobs r
|
||||
LEFT JOIN job_analyses a ON r.id = a.raw_job_id
|
||||
LEFT JOIN job_applications app ON r.id = app.raw_job_id
|
||||
ORDER BY COALESCE(a.score, 0) DESC, r.crawled_at DESC
|
||||
ORDER BY COALESCE(a.score, a.filter_score, 0) DESC, r.crawled_at DESC
|
||||
`
|
||||
rows, err := db.Query(query)
|
||||
if err != nil {
|
||||
@@ -521,9 +539,9 @@ func (db *DB) GetJobsWithAnalysis() ([]JobWithAnalysis, error) {
|
||||
var item JobWithAnalysis
|
||||
var tID sql.NullInt64
|
||||
var aID, appID sql.NullInt64
|
||||
var score sql.NullFloat64
|
||||
var filterScore, score sql.NullFloat64
|
||||
var isQualified sql.NullBool
|
||||
var matched, missing, suggestions, aStatus sql.NullString
|
||||
var summary, matched, missing, suggestions, aStatus sql.NullString
|
||||
var analyzedAt sql.NullTime
|
||||
var appStatus, notes sql.NullString
|
||||
var appliedAt sql.NullTime
|
||||
@@ -531,7 +549,7 @@ func (db *DB) GetJobsWithAnalysis() ([]JobWithAnalysis, error) {
|
||||
|
||||
err := rows.Scan(
|
||||
&item.ID, &item.CanonicalID, &tID, &item.Title, &item.Company, &item.Location, &item.ApplyURL, &item.RawContent, &item.CrawledAt, &item.LastSeenAt,
|
||||
&aID, &score, &isQualified, &matched, &missing, &suggestions, &aStatus, &analyzedAt,
|
||||
&aID, &filterScore, &score, &isQualified, &summary, &matched, &missing, &suggestions, &aStatus, &analyzedAt,
|
||||
&appID, &appStatus, ¬es, &appliedAt, &updatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -544,8 +562,10 @@ func (db *DB) GetJobsWithAnalysis() ([]JobWithAnalysis, error) {
|
||||
analysis := &JobAnalysis{
|
||||
ID: aID.Int64,
|
||||
RawJobID: item.ID,
|
||||
FilterScore: filterScore.Float64,
|
||||
Score: score.Float64,
|
||||
IsQualified: isQualified.Bool,
|
||||
Summary: summary.String,
|
||||
MatchedHighlights: matched.String,
|
||||
MissingHighlights: missing.String,
|
||||
TailoringSuggestions: suggestions.String,
|
||||
@@ -577,7 +597,7 @@ func (db *DB) GetJobsWithAnalysis() ([]JobWithAnalysis, error) {
|
||||
func (db *DB) GetJobByID(rawJobID int64) (*JobWithAnalysis, error) {
|
||||
query := `
|
||||
SELECT r.id, r.canonical_id, r.target_id, r.title, r.company, r.location, r.apply_url, r.raw_content, r.crawled_at, r.last_seen_at,
|
||||
a.id, a.score, a.is_qualified, COALESCE(a.matched_highlights, '[]'), COALESCE(a.missing_highlights, '[]'), COALESCE(a.tailoring_suggestions, '[]'), COALESCE(a.status, 'PENDING'), a.analyzed_at,
|
||||
a.id, COALESCE(a.filter_score, 0), COALESCE(a.score, 0), a.is_qualified, COALESCE(a.summary, ''), COALESCE(a.matched_highlights, '[]'), COALESCE(a.missing_highlights, '[]'), COALESCE(a.tailoring_suggestions, '[]'), COALESCE(a.status, 'PENDING'), a.analyzed_at,
|
||||
app.id, app.status, COALESCE(app.notes, ''), app.applied_at, app.updated_at
|
||||
FROM raw_jobs r
|
||||
LEFT JOIN job_analyses a ON r.id = a.raw_job_id
|
||||
@@ -588,9 +608,9 @@ func (db *DB) GetJobByID(rawJobID int64) (*JobWithAnalysis, error) {
|
||||
var item JobWithAnalysis
|
||||
var tID sql.NullInt64
|
||||
var aID, appID sql.NullInt64
|
||||
var score sql.NullFloat64
|
||||
var filterScore, score sql.NullFloat64
|
||||
var isQualified sql.NullBool
|
||||
var matched, missing, suggestions, aStatus sql.NullString
|
||||
var summary, matched, missing, suggestions, aStatus sql.NullString
|
||||
var analyzedAt sql.NullTime
|
||||
var appStatus, notes sql.NullString
|
||||
var appliedAt sql.NullTime
|
||||
@@ -598,7 +618,7 @@ func (db *DB) GetJobByID(rawJobID int64) (*JobWithAnalysis, error) {
|
||||
|
||||
err := row.Scan(
|
||||
&item.ID, &item.CanonicalID, &tID, &item.Title, &item.Company, &item.Location, &item.ApplyURL, &item.RawContent, &item.CrawledAt, &item.LastSeenAt,
|
||||
&aID, &score, &isQualified, &matched, &missing, &suggestions, &aStatus, &analyzedAt,
|
||||
&aID, &filterScore, &score, &isQualified, &summary, &matched, &missing, &suggestions, &aStatus, &analyzedAt,
|
||||
&appID, &appStatus, ¬es, &appliedAt, &updatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -611,8 +631,10 @@ func (db *DB) GetJobByID(rawJobID int64) (*JobWithAnalysis, error) {
|
||||
analysis := &JobAnalysis{
|
||||
ID: aID.Int64,
|
||||
RawJobID: item.ID,
|
||||
FilterScore: filterScore.Float64,
|
||||
Score: score.Float64,
|
||||
IsQualified: isQualified.Bool,
|
||||
Summary: summary.String,
|
||||
MatchedHighlights: matched.String,
|
||||
MissingHighlights: missing.String,
|
||||
TailoringSuggestions: suggestions.String,
|
||||
@@ -674,7 +696,7 @@ func (db *DB) CreateOrUpdateApplication(rawJobID int64, status, notes string) (*
|
||||
func (db *DB) GetApplications() ([]JobWithAnalysis, error) {
|
||||
query := `
|
||||
SELECT r.id, r.canonical_id, r.target_id, r.title, r.company, r.location, r.apply_url, r.raw_content, r.crawled_at, r.last_seen_at,
|
||||
a.id, a.score, a.is_qualified, COALESCE(a.matched_highlights, '[]'), COALESCE(a.missing_highlights, '[]'), COALESCE(a.tailoring_suggestions, '[]'), COALESCE(a.status, 'PENDING'), a.analyzed_at,
|
||||
a.id, COALESCE(a.filter_score, 0), COALESCE(a.score, 0), a.is_qualified, COALESCE(a.summary, ''), COALESCE(a.matched_highlights, '[]'), COALESCE(a.missing_highlights, '[]'), COALESCE(a.tailoring_suggestions, '[]'), COALESCE(a.status, 'PENDING'), a.analyzed_at,
|
||||
app.id, app.status, COALESCE(app.notes, ''), app.applied_at, app.updated_at
|
||||
FROM job_applications app
|
||||
JOIN raw_jobs r ON app.raw_job_id = r.id
|
||||
@@ -692,9 +714,9 @@ func (db *DB) GetApplications() ([]JobWithAnalysis, error) {
|
||||
var item JobWithAnalysis
|
||||
var tID sql.NullInt64
|
||||
var aID, appID sql.NullInt64
|
||||
var score sql.NullFloat64
|
||||
var filterScore, score sql.NullFloat64
|
||||
var isQualified sql.NullBool
|
||||
var matched, missing, suggestions, aStatus sql.NullString
|
||||
var summary, matched, missing, suggestions, aStatus sql.NullString
|
||||
var analyzedAt sql.NullTime
|
||||
var appStatus, notes sql.NullString
|
||||
var appliedAt sql.NullTime
|
||||
@@ -702,7 +724,7 @@ func (db *DB) GetApplications() ([]JobWithAnalysis, error) {
|
||||
|
||||
err := rows.Scan(
|
||||
&item.ID, &item.CanonicalID, &tID, &item.Title, &item.Company, &item.Location, &item.ApplyURL, &item.RawContent, &item.CrawledAt, &item.LastSeenAt,
|
||||
&aID, &score, &isQualified, &matched, &missing, &suggestions, &aStatus, &analyzedAt,
|
||||
&aID, &filterScore, &score, &isQualified, &summary, &matched, &missing, &suggestions, &aStatus, &analyzedAt,
|
||||
&appID, &appStatus, ¬es, &appliedAt, &updatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -715,8 +737,10 @@ func (db *DB) GetApplications() ([]JobWithAnalysis, error) {
|
||||
analysis := &JobAnalysis{
|
||||
ID: aID.Int64,
|
||||
RawJobID: item.ID,
|
||||
FilterScore: filterScore.Float64,
|
||||
Score: score.Float64,
|
||||
IsQualified: isQualified.Bool,
|
||||
Summary: summary.String,
|
||||
MatchedHighlights: matched.String,
|
||||
MissingHighlights: missing.String,
|
||||
TailoringSuggestions: suggestions.String,
|
||||
|
||||
Reference in New Issue
Block a user