| """ |
| CV Content Extractor for AI Knowledge Base |
| Structures Giuseppe Rumore's CV content into organized categories for vectorization and AI chat. |
| """ |
|
|
| import json |
| import re |
| from typing import Dict, List, Any |
| from pathlib import Path |
|
|
|
|
| class CVContentExtractor: |
| """Extracts and structures CV content from text file for AI knowledge base.""" |
| |
| def __init__(self, cv_file_path: str): |
| self.cv_file_path = Path(cv_file_path) |
| self.raw_content = "" |
| self.structured_content = {} |
| |
| def load_cv_content(self) -> str: |
| """Load CV content from text file.""" |
| try: |
| with open(self.cv_file_path, 'r', encoding='utf-8') as file: |
| self.raw_content = file.read() |
| return self.raw_content |
| except FileNotFoundError: |
| raise FileNotFoundError(f"CV file not found at: {self.cv_file_path}") |
| except Exception as e: |
| raise Exception(f"Error reading CV file: {str(e)}") |
| |
| def extract_contact_info(self) -> Dict[str, str]: |
| """Extract contact information and links.""" |
| lines = self.raw_content.split('\n') |
| |
| |
| name = "" |
| title = "" |
| links = "" |
| details = "" |
| |
| for line in lines: |
| line = line.strip() |
| if "Giuseppe Rumore" in line: |
| name = line |
| elif line.startswith("Data Scientist"): |
| title = line |
| elif line.startswith("Links:"): |
| links = line.replace("Links: ", "").strip() |
| elif line.startswith("Details:"): |
| details = line.replace("Details: ", "").strip() |
| |
| |
| email_match = re.search(r'[\w\.-]+@[\w\.-]+\.\w+', details) |
| email = email_match.group(0) if email_match else "" |
| |
| |
| phone_match = re.search(r'\+\d+\s+\d+', details) |
| phone = phone_match.group(0) if phone_match else "" |
| |
| |
| location_match = re.search(r'^([^,]+,[^,]+,[^,]+,[^,]+)', details) |
| location = location_match.group(1) if location_match else "" |
| |
| return { |
| "name": name, |
| "title": title, |
| "email": email, |
| "phone": phone, |
| "location": location, |
| "links": links |
| } |
| |
| def extract_profile(self) -> str: |
| """Extract profile/summary section.""" |
| lines = self.raw_content.split('\n') |
| profile_start = None |
| profile_end = None |
| |
| for i, line in enumerate(lines): |
| if line.strip() == "Profile": |
| profile_start = i + 1 |
| elif profile_start and line.strip() == "Professional Experience": |
| profile_end = i |
| break |
| |
| if profile_start and profile_end: |
| profile_lines = lines[profile_start:profile_end] |
| return ' '.join(line.strip() for line in profile_lines if line.strip()) |
| |
| return "" |
| |
| def extract_professional_experience(self) -> List[Dict[str, Any]]: |
| """Extract professional experience with companies, dates, and responsibilities.""" |
| experiences = [] |
| lines = self.raw_content.split('\n') |
| |
| exp_start = None |
| for i, line in enumerate(lines): |
| if line.strip() == "Professional Experience": |
| exp_start = i + 1 |
| break |
| |
| if not exp_start: |
| return experiences |
| |
| current_job = {} |
| current_responsibilities = [] |
| |
| for i in range(exp_start, len(lines)): |
| line = lines[i].strip() |
| |
| if not line: |
| continue |
| |
| |
| if line in ["Data Science and Machine Learning Projects", "Education"]: |
| |
| if current_job: |
| current_job['responsibilities'] = current_responsibilities |
| experiences.append(current_job) |
| break |
| |
| |
| if re.match(r'^[A-Z][^,]+,\s*[A-Z][^,]+,\s*[A-Z]', line): |
| |
| if current_job: |
| current_job['responsibilities'] = current_responsibilities |
| experiences.append(current_job) |
| |
| |
| parts = line.split(', ') |
| current_job = { |
| 'title': parts[0].strip() if len(parts) > 0 else "", |
| 'company': parts[1].strip() if len(parts) > 1 else "", |
| 'location': parts[2].strip() if len(parts) > 2 else "" |
| } |
| current_responsibilities = [] |
| |
| |
| elif re.match(r'^[A-Z]+ \d{4} — [A-Z]+ \d{4}$', line): |
| if current_job: |
| current_job['period'] = line |
| |
| |
| elif ':' in line and not line.startswith('Tel:'): |
| current_responsibilities.append(line) |
| |
| |
| if current_job: |
| current_job['responsibilities'] = current_responsibilities |
| experiences.append(current_job) |
| |
| return experiences |
| |
| def extract_projects(self) -> List[Dict[str, Any]]: |
| """Extract data science and machine learning projects.""" |
| projects = [] |
| lines = self.raw_content.split('\n') |
| |
| proj_start = None |
| for i, line in enumerate(lines): |
| if line.strip() == "Data Science and Machine Learning Projects": |
| proj_start = i + 1 |
| break |
| |
| if not proj_start: |
| return projects |
| |
| current_project = {} |
| |
| for i in range(proj_start, len(lines)): |
| line = lines[i].strip() |
| |
| if not line: |
| continue |
| |
| |
| if line == "Education": |
| |
| if current_project: |
| projects.append(current_project) |
| break |
| |
| |
| if re.match(r'^\d{4}$', line): |
| |
| if current_project: |
| projects.append(current_project) |
| |
| current_project = {'year': line, 'description': ''} |
| |
| |
| elif current_project and 'title' not in current_project and not re.match(r'^\d{4}$', line): |
| current_project['title'] = line |
| |
| |
| elif current_project and 'title' in current_project: |
| if current_project['description']: |
| current_project['description'] += ' ' + line |
| else: |
| current_project['description'] = line |
| |
| |
| if current_project: |
| projects.append(current_project) |
| |
| return projects |
| |
| def extract_education(self) -> List[Dict[str, Any]]: |
| """Extract education information.""" |
| education = [] |
| lines = self.raw_content.split('\n') |
| |
| edu_start = None |
| for i, line in enumerate(lines): |
| if line.strip() == "Education": |
| edu_start = i + 1 |
| break |
| |
| if not edu_start: |
| return education |
| |
| current_education = {} |
| current_details = [] |
| |
| for i in range(edu_start, len(lines)): |
| line = lines[i].strip() |
| |
| if not line: |
| continue |
| |
| |
| if line == "Languages": |
| |
| if current_education: |
| current_education['details'] = current_details |
| education.append(current_education) |
| break |
| |
| |
| if ', ' in line and ('Master' in line or 'Data Science' in line): |
| |
| if current_education: |
| current_education['details'] = current_details |
| education.append(current_education) |
| |
| |
| parts = line.split(', ', 1) |
| current_education = { |
| 'degree': parts[0].strip(), |
| 'institution': parts[1].strip() if len(parts) > 1 else "" |
| } |
| current_details = [] |
| |
| |
| elif re.match(r'^[A-Z]+ \d{4} — [A-Z]+ \d{4}$', line): |
| if current_education: |
| current_education['period'] = line |
| |
| |
| else: |
| current_details.append(line) |
| |
| |
| if current_education: |
| current_education['details'] = current_details |
| education.append(current_education) |
| |
| return education |
| |
| def extract_skills(self) -> Dict[str, List[str]]: |
| """Extract skills from the Skills section.""" |
| lines = self.raw_content.split('\n') |
| |
| skills_start = None |
| for i, line in enumerate(lines): |
| if line.strip() == "Skills": |
| skills_start = i + 1 |
| break |
| |
| if not skills_start: |
| return {} |
| |
| skills = { |
| 'programming': [], |
| 'technical': [], |
| 'tools': [], |
| 'frameworks': [] |
| } |
| |
| for i in range(skills_start, len(lines)): |
| line = lines[i].strip() |
| |
| if not line: |
| continue |
| |
| |
| if line == "Extra-curricular activities" or i >= len(lines) - 1: |
| break |
| |
| |
| if any(lang in line.lower() for lang in ['python', 'sql', 'git']): |
| skills['programming'].append(line) |
| elif any(tech in line.lower() for tech in ['computer vision', 'mlops', 'linux', 'docker']): |
| skills['technical'].append(line) |
| elif any(tool in line.lower() for tool in ['pytorch', 'tensorflow', 'ansys', 'catia', 'solidworks']): |
| skills['tools'].append(line) |
| else: |
| skills['frameworks'].append(line) |
| |
| return skills |
| |
| def extract_languages(self) -> List[Dict[str, str]]: |
| """Extract language proficiency information.""" |
| languages = [] |
| lines = self.raw_content.split('\n') |
| |
| lang_start = None |
| for i, line in enumerate(lines): |
| if line.strip() == "Languages": |
| lang_start = i + 1 |
| break |
| |
| if not lang_start: |
| return languages |
| |
| for i in range(lang_start, len(lines)): |
| line = lines[i].strip() |
| |
| if not line: |
| continue |
| |
| |
| if line == "Certificates": |
| break |
| |
| |
| if ':' in line: |
| parts = line.split(':', 1) |
| lang_names = parts[0].strip() |
| proficiency = parts[1].strip() |
| |
| |
| for lang in lang_names.split(', '): |
| languages.append({ |
| 'language': lang.strip(), |
| 'proficiency': proficiency |
| }) |
| |
| return languages |
| |
| def extract_certificates(self) -> List[str]: |
| """Extract certificates and certifications.""" |
| certificates = [] |
| lines = self.raw_content.split('\n') |
| |
| cert_start = None |
| for i, line in enumerate(lines): |
| if line.strip() == "Certificates": |
| cert_start = i + 1 |
| break |
| |
| if not cert_start: |
| return certificates |
| |
| for i in range(cert_start, len(lines)): |
| line = lines[i].strip() |
| |
| if not line: |
| continue |
| |
| |
| if line == "Skills": |
| break |
| |
| certificates.append(line) |
| |
| return certificates |
| |
| def structure_content(self) -> Dict[str, Any]: |
| """Structure all CV content into organized categories.""" |
| if not self.raw_content: |
| self.load_cv_content() |
| |
| self.structured_content = { |
| "contact": self.extract_contact_info(), |
| "profile": self.extract_profile(), |
| "experience": self.extract_professional_experience(), |
| "projects": self.extract_projects(), |
| "education": self.extract_education(), |
| "skills": self.extract_skills(), |
| "languages": self.extract_languages(), |
| "certificates": self.extract_certificates(), |
| "metadata": { |
| "extraction_date": "2025-09-13", |
| "source": "Giuseppe_Rumore_CV.txt", |
| "version": "1.0" |
| } |
| } |
| |
| return self.structured_content |
| |
| def save_structured_content(self, output_path: str) -> str: |
| """Save structured content to JSON file.""" |
| if not self.structured_content: |
| self.structure_content() |
| |
| output_file = Path(output_path) |
| output_file.parent.mkdir(parents=True, exist_ok=True) |
| |
| with open(output_file, 'w', encoding='utf-8') as file: |
| json.dump(self.structured_content, file, indent=2, ensure_ascii=False) |
| |
| return str(output_file) |
| |
| def get_content_for_ai(self) -> Dict[str, str]: |
| """Get content formatted for AI knowledge base and vectorization.""" |
| if not self.structured_content: |
| self.structure_content() |
| |
| |
| ai_content = {} |
| |
| |
| contact = self.structured_content['contact'] |
| ai_content['personal_info'] = f""" |
| Name: {contact['name']} |
| Title: {contact['title']} |
| Location: {contact['location']} |
| Contact: {contact['email']}, {contact['phone']} |
| Links: {contact['links']} |
| """ |
| |
| |
| ai_content['profile_summary'] = self.structured_content['profile'] |
| |
| |
| exp_text = [] |
| for exp in self.structured_content['experience']: |
| exp_section = f""" |
| {exp['title']} at {exp['company']}, {exp['location']} ({exp.get('period', '')}) |
| Responsibilities: |
| """ |
| for resp in exp.get('responsibilities', []): |
| exp_section += f"• {resp}\n" |
| exp_text.append(exp_section) |
| ai_content['professional_experience'] = '\n'.join(exp_text) |
| |
| |
| proj_text = [] |
| for proj in self.structured_content['projects']: |
| proj_section = f""" |
| {proj['title']} ({proj['year']}) |
| {proj['description']} |
| """ |
| proj_text.append(proj_section) |
| ai_content['projects'] = '\n'.join(proj_text) |
| |
| |
| edu_text = [] |
| for edu in self.structured_content['education']: |
| edu_section = f""" |
| {edu['degree']} - {edu['institution']} ({edu.get('period', '')}) |
| Details: {' '.join(edu.get('details', []))} |
| """ |
| edu_text.append(edu_section) |
| ai_content['education'] = '\n'.join(edu_text) |
| |
| |
| skills = self.structured_content['skills'] |
| skills_text = f""" |
| Programming: {', '.join(skills.get('programming', []))} |
| Technical: {', '.join(skills.get('technical', []))} |
| Tools: {', '.join(skills.get('tools', []))} |
| Frameworks: {', '.join(skills.get('frameworks', []))} |
| """ |
| ai_content['skills'] = skills_text |
| |
| |
| lang_list = [f"{lang['language']}: {lang['proficiency']}" |
| for lang in self.structured_content['languages']] |
| ai_content['languages'] = '\n'.join(lang_list) |
| |
| |
| ai_content['certificates'] = '\n'.join(self.structured_content['certificates']) |
| |
| return ai_content |
|
|
|
|
| def main(): |
| """Main function to extract and structure CV content.""" |
| try: |
| |
| cv_path = "../public/cv/Giuseppe_Rumore_CV.txt" |
| extractor = CVContentExtractor(cv_path) |
| |
| |
| print("Loading CV content...") |
| extractor.load_cv_content() |
| |
| print("Structuring content...") |
| structured = extractor.structure_content() |
| |
| |
| output_path = "data/cv_structured.json" |
| saved_path = extractor.save_structured_content(output_path) |
| print(f"Structured content saved to: {saved_path}") |
| |
| |
| ai_content = extractor.get_content_for_ai() |
| ai_output_path = "data/cv_ai_content.json" |
| |
| with open(ai_output_path, 'w', encoding='utf-8') as file: |
| json.dump(ai_content, file, indent=2, ensure_ascii=False) |
| print(f"AI-ready content saved to: {ai_output_path}") |
| |
| |
| print("\n=== CV Content Extraction Summary ===") |
| print(f"Contact: {structured['contact']['name']}") |
| print(f"Experience entries: {len(structured['experience'])}") |
| print(f"Projects: {len(structured['projects'])}") |
| print(f"Education entries: {len(structured['education'])}") |
| print(f"Languages: {len(structured['languages'])}") |
| print(f"Certificates: {len(structured['certificates'])}") |
| |
| return structured, ai_content |
| |
| except Exception as e: |
| print(f"Error extracting CV content: {str(e)}") |
| return None, None |
|
|
|
|
| if __name__ == "__main__": |
| main() |