|
|
|
@@ -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,
|
|
|
|
|