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
+37
View File
@@ -62,8 +62,10 @@ func (db *DB) migrate() error {
CREATE TABLE IF NOT EXISTS job_analyses (
id INTEGER PRIMARY KEY AUTOINCREMENT,
raw_job_id INTEGER UNIQUE NOT NULL REFERENCES raw_jobs(id) ON DELETE CASCADE,
filter_score REAL DEFAULT 0.0,
score REAL DEFAULT 0.0,
is_qualified BOOLEAN NOT NULL DEFAULT 0,
summary TEXT,
matched_highlights JSON,
missing_highlights JSON,
tailoring_suggestions JSON,
@@ -125,5 +127,40 @@ func (db *DB) migrate() error {
}
}
// Dynamic migration for job_analyses: check if filter_score and summary exist
var hasFilterScore, hasSummary bool
rowsA, err := db.Query(`PRAGMA table_info(job_analyses)`)
if err == nil {
for rowsA.Next() {
var cid int
var name, ctype string
var notnull, pk int
var dfltValue sql.NullString
if err := rowsA.Scan(&cid, &name, &ctype, &notnull, &dfltValue, &pk); err == nil {
if name == "filter_score" {
hasFilterScore = true
}
if name == "summary" {
hasSummary = true
}
}
}
_ = rowsA.Close()
}
if !hasFilterScore {
if _, err := db.Exec(`ALTER TABLE job_analyses ADD COLUMN filter_score REAL DEFAULT 0.0`); err != nil {
logger.Log.Warnw("Failed to add filter_score column", "component", "DB", "error", err)
} else {
_, _ = db.Exec(`UPDATE job_analyses SET filter_score = score WHERE filter_score IS NULL OR filter_score = 0.0`)
}
}
if !hasSummary {
if _, err := db.Exec(`ALTER TABLE job_analyses ADD COLUMN summary TEXT`); err != nil {
logger.Log.Warnw("Failed to add summary column", "component", "DB", "error", err)
}
}
return nil
}
+3 -1
View File
@@ -41,12 +41,14 @@ type Task struct {
type JobAnalysis struct {
ID int64 `json:"id"`
RawJobID int64 `json:"raw_job_id"`
FilterScore float64 `json:"filter_score"`
Score float64 `json:"score"`
IsQualified bool `json:"is_qualified"`
Summary string `json:"summary"`
MatchedHighlights string `json:"matched_highlights"` // JSON array string
MissingHighlights string `json:"missing_highlights"` // JSON array string
TailoringSuggestions string `json:"tailoring_suggestions"` // JSON array string
Status string `json:"status"` // 'PENDING', 'PARSED', 'FILTERED_OUT', 'FAILED'
Status string `json:"status"` // 'PENDING', 'PARSED', 'FILTERED_OUT', 'REJECTED', 'FAILED'
AnalyzedAt *time.Time `json:"analyzed_at"`
}
+59 -35
View File
@@ -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, &notes, &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, &notes, &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, &notes, &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,
+5 -5
View File
@@ -67,11 +67,11 @@ func TestDeleteTargetCascade(t *testing.T) {
defer func() { _ = database.Close() }()
// 1. Create two search targets
target1, err := database.CreateTarget("Target 1", "https://example.com/target1", "google_jobs")
target1, err := database.CreateTarget("Target 1", "https://example.com/target1", "google_jobs", true)
if err != nil {
t.Fatalf("failed to create target 1: %v", err)
}
target2, err := database.CreateTarget("Target 2", "https://example.com/target2", "apple")
target2, err := database.CreateTarget("Target 2", "https://example.com/target2", "apple", true)
if err != nil {
t.Fatalf("failed to create target 2: %v", err)
}
@@ -91,9 +91,9 @@ func TestDeleteTargetCascade(t *testing.T) {
}
// 3. Create analyses and applications for target 1 jobs and target 2 jobs
_ = database.CreateJobAnalysis(job1.ID, 85.0, true, "DONE")
_ = database.CreateJobAnalysis(job2.ID, 40.0, false, "DONE")
_ = database.CreateJobAnalysis(job3.ID, 90.0, true, "DONE")
_ = database.CreateJobAnalysis(job1.ID, 85.0, 85.0, true, "DONE")
_ = database.CreateJobAnalysis(job2.ID, 40.0, 40.0, false, "DONE")
_ = database.CreateJobAnalysis(job3.ID, 90.0, 90.0, true, "DONE")
_, _ = database.CreateOrUpdateApplication(job1.ID, "APPLIED", "Sent resume")
_, _ = database.CreateOrUpdateApplication(job3.ID, "SAVED", "Review later")
+14 -8
View File
@@ -16,7 +16,7 @@ type targetConfig struct {
} `yaml:"targets"`
}
// LoadTargetsFromYAML reads a YAML file and inserts targets into the database if it's empty
// LoadTargetsFromYAML reads a YAML file and inserts/syncs targets with the database
func (db *DB) LoadTargetsFromYAML(filepath string) error {
data, err := os.ReadFile(filepath)
if err != nil {
@@ -28,23 +28,29 @@ func (db *DB) LoadTargetsFromYAML(filepath string) error {
return fmt.Errorf("could not parse target config: %w", err)
}
// Get existing targets to prevent duplicates
// Get existing targets to prevent duplicates and sync is_active if needed
existingTargets, err := db.GetAllTargets()
if err != nil {
return err
}
existingURLs := make(map[string]bool)
for _, t := range existingTargets {
existingURLs[t.TargetURL] = true
existingURLs := make(map[string]*SearchTarget)
for i := range existingTargets {
existingURLs[existingTargets[i].TargetURL] = &existingTargets[i]
}
for _, t := range config.Targets {
if !existingURLs[t.TargetURL] {
_, err := db.CreateTarget(t.Name, t.TargetURL, t.Platform)
existing, found := existingURLs[t.TargetURL]
if !found {
_, err := db.CreateTarget(t.Name, t.TargetURL, t.Platform, t.IsActive)
if err != nil {
return fmt.Errorf("failed to insert target %s: %w", t.Name, err)
}
} else {
// Sync is_active state with targets.yaml on startup
if existing.IsActive != t.IsActive {
_ = db.SetTargetActive(existing.ID, t.IsActive)
}
}
}
return nil
+59
View File
@@ -0,0 +1,59 @@
package db
import (
"os"
"path/filepath"
"testing"
)
func TestLoadTargetsFromYAMLRespectsIsActive(t *testing.T) {
tempDir := t.TempDir()
dbPath := filepath.Join(tempDir, "test_targets_active.db")
yamlPath := filepath.Join(tempDir, "targets.yaml")
yamlContent := `targets:
- name: "Google Seattle"
target_url: "https://google.com/jobs"
platform: "google_jobs"
is_active: false
- name: "Apple Seattle"
target_url: "https://apple.com/jobs"
platform: "apple"
is_active: true
`
if err := os.WriteFile(yamlPath, []byte(yamlContent), 0644); err != nil {
t.Fatalf("failed to write yaml: %v", err)
}
database, err := InitDB(dbPath)
if err != nil {
t.Fatalf("failed to init db: %v", err)
}
defer func() { _ = database.Close() }()
if err := database.LoadTargetsFromYAML(yamlPath); err != nil {
t.Fatalf("LoadTargetsFromYAML failed: %v", err)
}
targets, err := database.GetAllTargets()
if err != nil {
t.Fatalf("GetAllTargets failed: %v", err)
}
if len(targets) != 2 {
t.Fatalf("expected 2 targets, got %d", len(targets))
}
for _, target := range targets {
if target.TargetURL == "https://google.com/jobs" {
if target.IsActive != false {
t.Errorf("expected Google target to have is_active=false, got %v", target.IsActive)
}
}
if target.TargetURL == "https://apple.com/jobs" {
if target.IsActive != true {
t.Errorf("expected Apple target to have is_active=true, got %v", target.IsActive)
}
}
}
}