feat: implement YAML-based configuration, .env support, and customizable prompt templates for AI analysis
This commit is contained in:
@@ -43,7 +43,7 @@ const jobListHTML = `{{ define "jobs" }}
|
||||
<tr>
|
||||
<th class="px-4 py-3" data-sort="title">Job Title & Company</th>
|
||||
<th class="px-4 py-3 text-center">Link</th>
|
||||
<th class="px-4 py-3" data-sort="status">Analysis</th>
|
||||
<th class="px-4 py-3" data-sort="status">Status</th>
|
||||
<th class="px-4 py-3" data-sort="score">Score & Qualification</th>
|
||||
<th class="px-4 py-3">Reason / Tags</th>
|
||||
<th class="px-4 py-3" data-sort="date">Last Updated</th>
|
||||
@@ -86,9 +86,13 @@ const jobListHTML = `{{ define "jobs" }}
|
||||
<td class="px-4 py-3" data-sort-val="{{ if .Analysis }}{{ .Analysis.Status }}{{ else }}UNANALYZED{{ end }}">
|
||||
{{ if .Analysis }}
|
||||
{{ if eq .Analysis.Status "PARSED" }}
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-bold bg-indigo-500/20 text-indigo-300 border border-indigo-500/30">PARSED</span>
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-bold bg-indigo-500/20 text-indigo-300 border border-indigo-500/30">AI PARSED</span>
|
||||
{{ else if eq .Analysis.Status "PENDING" }}
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-bold bg-amber-500/20 text-amber-400 border border-amber-500/30">PENDING</span>
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-bold bg-amber-500/20 text-amber-400 border border-amber-500/30">QUEUED</span>
|
||||
{{ else if eq .Analysis.Status "FILTERED_OUT" }}
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-bold bg-slate-700/40 text-slate-400 border border-slate-600/30">LOW FIT</span>
|
||||
{{ else if eq .Analysis.Status "REJECTED" }}
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-bold bg-rose-500/20 text-rose-400 border border-rose-500/30">REJECTED</span>
|
||||
{{ else if eq .Analysis.Status "DONE" }}
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-bold bg-emerald-500/20 text-emerald-400 border border-emerald-500/30">DONE</span>
|
||||
{{ else }}
|
||||
@@ -102,14 +106,32 @@ const jobListHTML = `{{ define "jobs" }}
|
||||
<!-- Score & Qualification -->
|
||||
<td class="px-4 py-3" data-sort-val="{{ if .Analysis }}{{ printf "%.0f" .Analysis.Score }}{{ else }}-1{{ end }}">
|
||||
{{ if .Analysis }}
|
||||
{{ if .Analysis.IsQualified }}
|
||||
<span class="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-black border {{ scoreBadgeClass .Analysis.Score }}">
|
||||
★ {{ printf "%.0f" .Analysis.Score }}
|
||||
</span>
|
||||
{{ if eq .Analysis.Status "PARSED" }}
|
||||
{{ if .Analysis.IsQualified }}
|
||||
<span class="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-black border {{ scoreBadgeClass .Analysis.Score }}" title="AI Match Score (DeepSeek)">
|
||||
🤖 {{ printf "%.0f" .Analysis.Score }}
|
||||
</span>
|
||||
{{ else }}
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-semibold bg-rose-500/10 text-rose-400 border border-rose-500/20" title="AI Score: {{ printf "%.0f" .Analysis.Score }}">
|
||||
✗ {{ printf "%.0f" .Analysis.Score }} Not Qual
|
||||
</span>
|
||||
{{ end }}
|
||||
{{ else if eq .Analysis.Status "PENDING" }}
|
||||
<span class="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-semibold bg-amber-500/10 text-amber-300 border border-amber-500/30" title="Keyword Fit: {{ printf "%.0f" .Analysis.FilterScore }} - Waiting for AI">
|
||||
⚡ {{ printf "%.0f" .Analysis.FilterScore }}
|
||||
</span>
|
||||
{{ else if eq .Analysis.Status "FILTERED_OUT" }}
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-medium bg-slate-800 text-slate-400 border border-slate-700" title="Keyword Fit: {{ printf "%.0f" .Analysis.FilterScore }}">
|
||||
⚡ {{ printf "%.0f" .Analysis.FilterScore }}
|
||||
</span>
|
||||
{{ else if eq .Analysis.Status "REJECTED" }}
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-semibold bg-rose-500/10 text-rose-400 border border-rose-500/20">
|
||||
🚫 Disqualified
|
||||
</span>
|
||||
{{ else }}
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-semibold bg-rose-500/10 text-rose-400 border border-rose-500/20">
|
||||
✗ Not Qualified
|
||||
</span>
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-semibold bg-slate-700/50 text-slate-400 border border-slate-600/30">
|
||||
{{ printf "%.0f" .Analysis.Score }}
|
||||
</span>
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
<span class="text-slate-500 text-[11px]">–</span>
|
||||
@@ -138,6 +160,8 @@ const jobListHTML = `{{ define "jobs" }}
|
||||
{{ $missing := parseJSON .Analysis.MissingHighlights }}
|
||||
{{ if $missing }}
|
||||
<span class="text-[10px] text-rose-400/90 leading-snug line-clamp-2" title="{{ index $missing 0 }}">{{ index $missing 0 }}</span>
|
||||
{{ else if .Analysis.Summary }}
|
||||
<span class="text-[10px] text-slate-400/90 leading-snug line-clamp-2" title="{{ .Analysis.Summary }}">{{ .Analysis.Summary }}</span>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
@@ -232,23 +256,50 @@ const jobDetailHTML = `{{ define "jobDetail" }}
|
||||
</div>
|
||||
|
||||
{{ if .Analysis }}
|
||||
<!-- Score + Status Grid -->
|
||||
<!-- Dual Score Breakdown Grid -->
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<!-- AI Match Score Card -->
|
||||
<div class="p-4 rounded-xl bg-slate-800/60 border border-slate-700/60">
|
||||
<span class="text-[10px] text-slate-400 uppercase tracking-wider font-semibold block">Match Score</span>
|
||||
{{ if .Analysis.IsQualified }}
|
||||
<span class="text-2xl font-black text-emerald-400 mt-1 block">{{ printf "%.1f" .Analysis.Score }} <span class="text-sm font-medium text-slate-400">/ 100</span></span>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-[10px] text-indigo-300 uppercase tracking-wider font-bold">🤖 AI Match Score</span>
|
||||
<span class="text-[10px] font-semibold px-2 py-0.5 rounded {{ if eq .Analysis.Status "PARSED" }}bg-indigo-500/20 text-indigo-300 border border-indigo-500/30{{ else }}bg-amber-500/20 text-amber-300 border border-amber-500/30{{ end }}">
|
||||
{{ .Analysis.Status }}
|
||||
</span>
|
||||
</div>
|
||||
{{ if eq .Analysis.Status "PARSED" }}
|
||||
{{ if .Analysis.IsQualified }}
|
||||
<span class="text-2xl font-black text-emerald-400 mt-1.5 block">{{ printf "%.1f" .Analysis.Score }} <span class="text-xs font-normal text-slate-400">/ 100</span></span>
|
||||
<span class="text-[10px] text-emerald-400/90 font-medium">✓ Qualified for interview profile</span>
|
||||
{{ else }}
|
||||
<span class="text-2xl font-black text-rose-400 mt-1.5 block">{{ printf "%.1f" .Analysis.Score }} <span class="text-xs font-normal text-slate-400">/ 100</span></span>
|
||||
<span class="text-[10px] text-rose-400 font-medium">✗ Does not meet core fit threshold</span>
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
<span class="text-base font-bold text-rose-400 mt-1 block">Not Qualified</span>
|
||||
<span class="text-lg font-bold text-amber-300 mt-1.5 block">Pending Evaluation</span>
|
||||
<span class="text-[10px] text-slate-400">DeepSeek LLM analysis queued</span>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
<!-- Keyword Relevance Card -->
|
||||
<div class="p-4 rounded-xl bg-slate-800/60 border border-slate-700/60">
|
||||
<span class="text-[10px] text-slate-400 uppercase tracking-wider font-semibold block">Analysis Status</span>
|
||||
<span class="text-sm font-bold text-indigo-300 mt-1 block uppercase">{{ .Analysis.Status }}</span>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-[10px] text-amber-400 uppercase tracking-wider font-bold">⚡ Keyword Fit</span>
|
||||
<span class="text-[10px] text-slate-400">Pre-filter rule</span>
|
||||
</div>
|
||||
<span class="text-2xl font-black text-white mt-1.5 block">{{ printf "%.1f" .Analysis.FilterScore }} <span class="text-xs font-normal text-slate-400">/ 100</span></span>
|
||||
<span class="text-[10px] text-slate-400">Lexical match against filters.yaml</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Highlights -->
|
||||
<!-- AI Executive Summary -->
|
||||
{{ if .Analysis.Summary }}
|
||||
<div class="p-3.5 rounded-xl bg-indigo-950/40 border border-indigo-500/30">
|
||||
<span class="text-[10px] font-bold text-indigo-300 uppercase tracking-wider block mb-1">💬 AI Executive Assessment</span>
|
||||
<p class="text-xs text-indigo-100/90 leading-relaxed">{{ .Analysis.Summary }}</p>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<!-- Highlights & Gaps -->
|
||||
<div class="space-y-4">
|
||||
{{ if .Analysis.IsQualified }}
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user