142 lines
3.7 KiB
Go
142 lines
3.7 KiB
Go
package docx
|
|
|
|
import (
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// StyleProfile represents the writing style characteristics of a resume.
|
|
type StyleProfile struct {
|
|
ActionVerbs []string // top 20 most frequent action verbs from bullet starts
|
|
SummaryStyle string // first 2 sentences from any "Summary" or "Profile" section
|
|
BulletPatterns []string // first 5 words of each bullet (up to 30 bullets)
|
|
}
|
|
|
|
// StyleExtractor analyzes parsed resume markdown text.
|
|
type StyleExtractor struct {
|
|
markdown string
|
|
}
|
|
|
|
// NewStyleExtractor creates a new style extractor for the given markdown.
|
|
func NewStyleExtractor(markdown string) *StyleExtractor {
|
|
return &StyleExtractor{markdown: markdown}
|
|
}
|
|
|
|
// Extract analyzes the markdown and returns a StyleProfile.
|
|
func (s *StyleExtractor) Extract() StyleProfile {
|
|
return StyleProfile{
|
|
ActionVerbs: s.extractActionVerbs(),
|
|
SummaryStyle: s.extractSummary(),
|
|
BulletPatterns: s.extractBulletPatterns(),
|
|
}
|
|
}
|
|
|
|
// extractActionVerbs finds lines starting with bullet points, extracts the first word
|
|
// (action verb), deduplicates, and returns the top 20 by frequency.
|
|
func (s *StyleExtractor) extractActionVerbs() []string {
|
|
lines := strings.Split(s.markdown, "\n")
|
|
verbCount := make(map[string]int)
|
|
|
|
for _, line := range lines {
|
|
line = strings.TrimSpace(line)
|
|
// Match lines starting with "- " or "* "
|
|
if strings.HasPrefix(line, "- ") {
|
|
line = strings.TrimPrefix(line, "- ")
|
|
} else if strings.HasPrefix(line, "* ") {
|
|
line = strings.TrimPrefix(line, "* ")
|
|
} else {
|
|
continue
|
|
}
|
|
|
|
// Extract first word
|
|
fields := strings.Fields(line)
|
|
if len(fields) > 0 {
|
|
verb := strings.ToLower(fields[0])
|
|
verbCount[verb]++
|
|
}
|
|
}
|
|
|
|
// Sort by frequency (descending) and then alphabetically for stability
|
|
type verbFreq struct {
|
|
verb string
|
|
count int
|
|
}
|
|
var sorted []verbFreq
|
|
for verb, count := range verbCount {
|
|
sorted = append(sorted, verbFreq{verb, count})
|
|
}
|
|
|
|
sort.Slice(sorted, func(i, j int) bool {
|
|
if sorted[i].count != sorted[j].count {
|
|
return sorted[i].count > sorted[j].count
|
|
}
|
|
return sorted[i].verb < sorted[j].verb
|
|
})
|
|
|
|
var result []string
|
|
for i, vf := range sorted {
|
|
if i >= 20 {
|
|
break
|
|
}
|
|
result = append(result, vf.verb)
|
|
}
|
|
return result
|
|
}
|
|
|
|
// extractSummary finds a heading matching Summary|Profile|About|Objective and
|
|
// returns the next 2 non-empty lines.
|
|
func (s *StyleExtractor) extractSummary() string {
|
|
lines := strings.Split(s.markdown, "\n")
|
|
headingRegex := regexp.MustCompile(`(?i)^\s*#+\s+(summary|profile|about|objective)\s*$`)
|
|
|
|
for i, line := range lines {
|
|
if headingRegex.MatchString(line) {
|
|
// Collect next 2 non-empty lines
|
|
var summaryLines []string
|
|
for j := i + 1; j < len(lines) && len(summaryLines) < 2; j++ {
|
|
trimmed := strings.TrimSpace(lines[j])
|
|
if trimmed != "" && !strings.HasPrefix(trimmed, "#") {
|
|
summaryLines = append(summaryLines, trimmed)
|
|
}
|
|
}
|
|
if len(summaryLines) > 0 {
|
|
return strings.Join(summaryLines, " ")
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// extractBulletPatterns extracts the first 5 words of each bullet line (up to 30 bullets).
|
|
func (s *StyleExtractor) extractBulletPatterns() []string {
|
|
lines := strings.Split(s.markdown, "\n")
|
|
var patterns []string
|
|
|
|
for _, line := range lines {
|
|
if len(patterns) >= 30 {
|
|
break
|
|
}
|
|
line = strings.TrimSpace(line)
|
|
// Match lines starting with "- " or "* "
|
|
if strings.HasPrefix(line, "- ") {
|
|
line = strings.TrimPrefix(line, "- ")
|
|
} else if strings.HasPrefix(line, "* ") {
|
|
line = strings.TrimPrefix(line, "* ")
|
|
} else {
|
|
continue
|
|
}
|
|
|
|
// Extract first 5 words
|
|
fields := strings.Fields(line)
|
|
if len(fields) > 5 {
|
|
fields = fields[:5]
|
|
}
|
|
if len(fields) > 0 {
|
|
pattern := strings.Join(fields, " ")
|
|
patterns = append(patterns, pattern)
|
|
}
|
|
}
|
|
return patterns
|
|
}
|