Feature: Remove message, Upgrade: pull better job data, add dedicated status section to job applications

This commit is contained in:
cesnimda
2026-03-21 21:04:04 +01:00
parent aa804aebe8
commit 5ed5b340a5
7 changed files with 220 additions and 173 deletions
@@ -67,5 +67,17 @@ namespace JobTrackerApi.Controllers
return CreatedAtAction(nameof(GetForJob), new { jobId = message.JobApplicationId }, message);
}
[HttpDelete("{id:int}")]
public async Task<IActionResult> Delete([FromRoute] int id, CancellationToken cancellationToken)
{
var message = await _db.Correspondences.FirstOrDefaultAsync(c => c.Id == id, cancellationToken);
if (message is null) return NotFound();
_db.Correspondences.Remove(message);
await _db.SaveChangesAsync(cancellationToken);
return NoContent();
}
}
}
@@ -576,7 +576,7 @@ namespace JobTrackerApi.Controllers
// Generate and persist a short summary at creation time to avoid repeated model calls.
try
{
var shortSum = await _summarizer.SummarizeAsync(job.Description ?? job.Notes ?? "", 80, 20);
var shortSum = await _summarizer.SummarizeAsync(job.Description ?? job.Notes ?? "", 160, 60);
job.ShortSummary = shortSum;
}
catch
@@ -626,7 +626,8 @@ namespace JobTrackerApi.Controllers
string? CoverLetterText,
string? JobUrl,
DateTime? DateApplied,
DateTime? FeedbackRequestedAt
DateTime? FeedbackRequestedAt,
DateTime? StatusChangedAt
);
[HttpPut("{id:int}")]
@@ -686,7 +687,7 @@ namespace JobTrackerApi.Controllers
Type = "StatusChanged",
OldValue = oldStatus,
NewValue = job.Status,
At = DateTime.Now
At = request.StatusChangedAt ?? DateTime.Now
});
}
@@ -58,7 +58,7 @@ public sealed class JobEnrichmentHostedService : BackgroundService
{
try
{
var shortSummary = await summarizer.SummarizeAsync(sourceText, 80, 20);
var shortSummary = await summarizer.SummarizeAsync(sourceText, 160, 60);
if (!string.IsNullOrWhiteSpace(shortSummary))
{
job.ShortSummary = shortSummary;
+40 -17
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
@@ -7,29 +7,52 @@ namespace JobTrackerApi.Services.JobImport;
public static class SkillTagger
{
private static readonly (string Tag, Regex Pattern)[] Patterns =
private static readonly (string Tag, Regex Pattern, int Weight)[] Patterns =
{
("C#", new Regex(@"\bC#\b", RegexOptions.IgnoreCase | RegexOptions.Compiled)),
(".NET", new Regex(@"\b\.NET\b|\bASP\.NET\b|\bDOTNET\b", RegexOptions.IgnoreCase | RegexOptions.Compiled)),
("Python", new Regex(@"\bPython\b", RegexOptions.IgnoreCase | RegexOptions.Compiled)),
("Docker", new Regex(@"\bDocker\b", RegexOptions.IgnoreCase | RegexOptions.Compiled)),
("Azure", new Regex(@"\bAzure\b", RegexOptions.IgnoreCase | RegexOptions.Compiled)),
("AWS", new Regex(@"\bAWS\b|\bAmazon Web Services\b", RegexOptions.IgnoreCase | RegexOptions.Compiled)),
("React", new Regex(@"\bReact\b|\bReact\.js\b", RegexOptions.IgnoreCase | RegexOptions.Compiled)),
("TypeScript", new Regex(@"\bTypeScript\b|\bTS\b", RegexOptions.IgnoreCase | RegexOptions.Compiled)),
("SQL", new Regex(@"\bSQL\b|\bPostgreSQL\b|\bMySQL\b|\bSQLite\b|\bMS\s*SQL\b|\bT-?SQL\b", RegexOptions.IgnoreCase | RegexOptions.Compiled)),
("Kubernetes", new Regex(@"\bKubernetes\b|\bK8s\b", RegexOptions.IgnoreCase | RegexOptions.Compiled)),
("C#", new Regex(@"\bC#\b|\bcsharp\b", RegexOptions.IgnoreCase | RegexOptions.Compiled), 6),
(".NET", new Regex(@"\b\.NET\b|\bASP\.NET\b|\bDOTNET\b|\bEntity Framework\b|\bEF Core\b", RegexOptions.IgnoreCase | RegexOptions.Compiled), 6),
("Python", new Regex(@"\bPython\b", RegexOptions.IgnoreCase | RegexOptions.Compiled), 6),
("Java", new Regex(@"\bJava\b", RegexOptions.IgnoreCase | RegexOptions.Compiled), 5),
("JavaScript", new Regex(@"\bJavaScript\b|\bJS\b", RegexOptions.IgnoreCase | RegexOptions.Compiled), 5),
("TypeScript", new Regex(@"\bTypeScript\b|\bTS\b", RegexOptions.IgnoreCase | RegexOptions.Compiled), 5),
("React", new Regex(@"\bReact\b|\bReact\.js\b", RegexOptions.IgnoreCase | RegexOptions.Compiled), 5),
("Node.js", new Regex(@"\bNode\b|\bNode\.js\b", RegexOptions.IgnoreCase | RegexOptions.Compiled), 5),
("SQL", new Regex(@"\bSQL\b|\bPostgreSQL\b|\bMySQL\b|\bSQLite\b|\bMS\s*SQL\b|\bT-?SQL\b", RegexOptions.IgnoreCase | RegexOptions.Compiled), 5),
("Docker", new Regex(@"\bDocker\b|\bcontainers?\b", RegexOptions.IgnoreCase | RegexOptions.Compiled), 5),
("Kubernetes", new Regex(@"\bKubernetes\b|\bK8s\b", RegexOptions.IgnoreCase | RegexOptions.Compiled), 5),
("Azure", new Regex(@"\bAzure\b", RegexOptions.IgnoreCase | RegexOptions.Compiled), 5),
("AWS", new Regex(@"\bAWS\b|\bAmazon Web Services\b", RegexOptions.IgnoreCase | RegexOptions.Compiled), 5),
("CI/CD", new Regex(@"\bCI/CD\b|continuous integration|continuous delivery|continuous deployment", RegexOptions.IgnoreCase | RegexOptions.Compiled), 4),
("REST APIs", new Regex(@"\bREST\b|RESTful|API development|web services", RegexOptions.IgnoreCase | RegexOptions.Compiled), 4),
("GraphQL", new Regex(@"\bGraphQL\b", RegexOptions.IgnoreCase | RegexOptions.Compiled), 4),
("Testing", new Regex(@"\bunit tests?\b|\bintegration tests?\b|\btesting\b|\bTDD\b", RegexOptions.IgnoreCase | RegexOptions.Compiled), 4),
("Agile", new Regex(@"\bAgile\b|\bScrum\b|\bKanban\b", RegexOptions.IgnoreCase | RegexOptions.Compiled), 3),
("Communication", new Regex(@"communication skills?|communicate effectively|stakeholder management", RegexOptions.IgnoreCase | RegexOptions.Compiled), 3),
("Collaboration", new Regex(@"collaborat|cross-functional|team player|work closely with", RegexOptions.IgnoreCase | RegexOptions.Compiled), 3),
("Problem Solving", new Regex(@"problem solving|solve complex|analytical|troubleshoot", RegexOptions.IgnoreCase | RegexOptions.Compiled), 3),
("Leadership", new Regex(@"leadership|mentor|coaching|leading teams?", RegexOptions.IgnoreCase | RegexOptions.Compiled), 3),
("Ownership", new Regex(@"ownership|self-starter|take initiative|proactive", RegexOptions.IgnoreCase | RegexOptions.Compiled), 3),
("Adaptability", new Regex(@"adaptable|flexible|fast-paced|ambiguity", RegexOptions.IgnoreCase | RegexOptions.Compiled), 2),
("Attention to Detail", new Regex(@"attention to detail|detail-oriented|quality-focused", RegexOptions.IgnoreCase | RegexOptions.Compiled), 2),
};
public static string[] Detect(string? description)
{
if (string.IsNullOrWhiteSpace(description)) return Array.Empty<string>();
var tags = new List<string>(capacity: 8);
foreach (var (tag, pattern) in Patterns)
var matches = new List<(string Tag, int Score)>();
foreach (var (tag, pattern, weight) in Patterns)
{
if (pattern.IsMatch(description)) tags.Add(tag);
if (pattern.IsMatch(description)) matches.Add((tag, weight));
}
return tags.Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
return matches
.GroupBy(x => x.Tag, StringComparer.OrdinalIgnoreCase)
.Select(g => new { Tag = g.Key, Score = g.Max(x => x.Score) })
.OrderByDescending(x => x.Score)
.ThenBy(x => x.Tag, StringComparer.OrdinalIgnoreCase)
.Take(12)
.Select(x => x.Tag)
.ToArray();
}
}