Add CV structure analysis groundwork

This commit is contained in:
cesnimda
2026-03-23 23:07:02 +01:00
parent eb4b517d58
commit 8f04637cff
3 changed files with 157 additions and 0 deletions
@@ -37,6 +37,8 @@ public sealed class ProfileCvController : ControllerBase
}
public sealed record RewriteSectionRequest(string SectionName, string? Style, string? TargetRole);
public sealed record ParseCvRequest(string? Text);
public sealed record ParsedCvSectionDto(string Name, string Content, int WordCount);
[HttpPost("upload")]
[RequestSizeLimit(MaxFileSizeBytes)]
@@ -146,6 +148,22 @@ public sealed class ProfileCvController : ControllerBase
return Ok(new { sectionName, style, targetRole, text = rewritten.Trim() });
}
[HttpPost("parse")]
public async Task<ActionResult<object>> Parse([FromBody] ParseCvRequest? request)
{
var user = await _users.GetUserAsync(User);
if (user is null) return Unauthorized();
var source = string.IsNullOrWhiteSpace(request?.Text) ? user.ProfileCvText : request!.Text;
if (string.IsNullOrWhiteSpace(source)) return BadRequest("Add or import CV text before parsing sections.");
var sections = ParseSections(source)
.Select(section => new ParsedCvSectionDto(section.Name, section.Content, CountWords(section.Content)))
.ToList();
return Ok(new { sections, totalWords = CountWords(source) });
}
[HttpPost("improve")]
public async Task<IActionResult> Improve()
{
@@ -174,6 +192,80 @@ public sealed class ProfileCvController : ControllerBase
return Ok(new { improved = true, characters = user.ProfileCvText.Length, text = user.ProfileCvText });
}
private static int CountWords(string? text)
{
if (string.IsNullOrWhiteSpace(text)) return 0;
return text.Trim().Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries).Length;
}
private static List<(string Name, string Content)> ParseSections(string source)
{
var lines = source.Replace("\r\n", "\n").Split('\n');
var aliases = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
["professional summary"] = "Professional Summary",
["summary"] = "Professional Summary",
["profile"] = "Professional Summary",
["core skills"] = "Core Skills",
["skills"] = "Core Skills",
["technical skills"] = "Core Skills",
["experience"] = "Experience Highlights",
["experience highlights"] = "Experience Highlights",
["work experience"] = "Experience Highlights",
["selected achievements"] = "Selected Achievements",
["achievements"] = "Selected Achievements",
["projects"] = "Projects",
["education"] = "Education",
["certifications"] = "Certifications",
["certificates"] = "Certifications",
};
var sections = new List<(string Name, List<string> Lines)>();
var currentName = "General";
var currentLines = new List<string>();
void Flush()
{
var content = string.Join("\n", currentLines).Trim();
if (!string.IsNullOrWhiteSpace(content))
{
sections.Add((currentName, new List<string>(currentLines)));
}
currentLines.Clear();
}
foreach (var raw in lines)
{
var line = raw.Trim();
var normalized = line.TrimEnd(':').Trim();
var looksLikeHeading = normalized.Length > 0
&& normalized.Length <= 40
&& !normalized.Contains('.')
&& aliases.ContainsKey(normalized.ToLowerInvariant());
if (looksLikeHeading)
{
Flush();
currentName = aliases[normalized.ToLowerInvariant()];
continue;
}
currentLines.Add(raw);
}
Flush();
if (sections.Count == 0)
{
return new List<(string Name, string Content)> { ("General", source.Trim()) };
}
return sections
.Select(section => (section.Name, string.Join("\n", section.Lines).Trim()))
.Where(section => !string.IsNullOrWhiteSpace(section.Item2))
.ToList();
}
private static async Task<string> ExtractTextAsync(IFormFile file, string extension)
{
if (string.Equals(extension, ".txt", StringComparison.OrdinalIgnoreCase) || string.Equals(extension, ".md", StringComparison.OrdinalIgnoreCase))