Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 730a5e7c69 |
@ -7,7 +7,9 @@
|
|||||||
"build": "next build --no-lint",
|
"build": "next build --no-lint",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"debug": "NODE_DEBUG=next node server.js"
|
"debug": "NODE_DEBUG=next node server.js",
|
||||||
|
"test": "pytest utils/tests/test_resume_analysis.py",
|
||||||
|
"count_documents": "mongosh mongodb://127.0.0.1:27017/cv_summary_db --eval 'db.cv_processing_collection.countDocuments()'"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/google": "^1.1.17",
|
"@ai-sdk/google": "^1.1.17",
|
||||||
|
|||||||
BIN
my-app/utils/__pycache__/resume_analysis.cpython-312.pyc
Normal file
BIN
my-app/utils/__pycache__/resume_analysis.cpython-312.pyc
Normal file
Binary file not shown.
87
my-app/utils/default_openai.txt
Normal file
87
my-app/utils/default_openai.txt
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
```json
|
||||||
|
{
|
||||||
|
"sections": {
|
||||||
|
"Summary": {
|
||||||
|
"score": 8,
|
||||||
|
"suggestions": [
|
||||||
|
"Consider adding specific achievements or metrics to highlight impact.",
|
||||||
|
"Simplify language for clearer understanding."
|
||||||
|
],
|
||||||
|
"summary": "The summary provides a clear overview of the candidate's experience and roles in business analysis and IT management but can be improved by adding specific achievements to quantify their contributions.",
|
||||||
|
"keywords": {
|
||||||
|
"analityk": 3,
|
||||||
|
"doświadczenie": 2,
|
||||||
|
"architekt": 1,
|
||||||
|
"manager": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Work Experience": {
|
||||||
|
"score": 9,
|
||||||
|
"suggestions": [],
|
||||||
|
"summary": "The work experience section is detailed, presenting clear job roles, responsibilities, and contributions. It utilizes strong action verbs but could be enhanced with quantifiable results in some roles.",
|
||||||
|
"keywords": {
|
||||||
|
"analiz": 5,
|
||||||
|
"biznesowy": 4,
|
||||||
|
"systemowy": 4,
|
||||||
|
"projekt": 4,
|
||||||
|
"współpraca": 3,
|
||||||
|
"wymagania": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Education": {
|
||||||
|
"score": 8,
|
||||||
|
"suggestions": [
|
||||||
|
"Specify the graduation status for higher education.",
|
||||||
|
"Consider listing any honors or relevant coursework."
|
||||||
|
],
|
||||||
|
"summary": "The education section is comprehensive, including degrees and specialized training, but it lacks mention of graduation status and could highlight additional relevant coursework.",
|
||||||
|
"keywords": {
|
||||||
|
"Politechnika": 2,
|
||||||
|
"CISCO": 1,
|
||||||
|
"Magisterskie": 1,
|
||||||
|
"Inżynierskie": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Skills": {
|
||||||
|
"score": 7,
|
||||||
|
"suggestions": [
|
||||||
|
"Categorize skills into technical and soft skills for clarity.",
|
||||||
|
"Add more specific technologies or methodologies relevant to the roles applied for."
|
||||||
|
],
|
||||||
|
"summary": "The skills section is minimal and lacks depth. Categorizing skills can improve clarity and relevance, and including specific technologies or methodologies would strengthen the section.",
|
||||||
|
"keywords": {
|
||||||
|
"szkoleń": 4,
|
||||||
|
"certyfikaty": 2,
|
||||||
|
"prawo jazdy": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Certifications": {
|
||||||
|
"score": 9,
|
||||||
|
"suggestions": [],
|
||||||
|
"summary": "The certifications section is strong, detailing relevant training and certifications that add credibility to the candidate's qualifications.",
|
||||||
|
"keywords": {
|
||||||
|
"certyfikat": 1,
|
||||||
|
"szkolenie": 9
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Projects": {
|
||||||
|
"score": 6,
|
||||||
|
"suggestions": [
|
||||||
|
"Create a separate section for key projects with descriptions and outcomes.",
|
||||||
|
"Highlight individual contributions to collaborative projects."
|
||||||
|
],
|
||||||
|
"summary": "The projects are mentioned informally within work experience; however, creating a dedicated section would better emphasize significant projects and achievements.",
|
||||||
|
"keywords": {
|
||||||
|
"projekt": 4,
|
||||||
|
"wymagania": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"openai_stats": {
|
||||||
|
"input_tokens": 2585,
|
||||||
|
"output_tokens": 677,
|
||||||
|
"total_tokens": 3262,
|
||||||
|
"cost": 0.01308
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"choices": [
|
||||||
|
{
|
||||||
|
"message": {
|
||||||
|
"content": "```json\n{\n \"sections\": {\n \"Summary\": {\n \"score\": 8,\n \"suggestions\": [\n \"Consider adding specific achievements or metrics to illustrate impact.\",\n \"Make the summary more concise by focusing on key strengths.\"\n ],\n \"summary\": \"The summary provides a brief overview of experience and roles but lacks specific accomplishments and is slightly verbose.\",\n \"keywords\": { \"analityk\": 3, \"doświadczenie\": 2, \"systemowy\": 2, \"technologicznych\": 1, \"menedżer\": 1 }\n },\n \"Work Experience\": {\n \"score\": 9,\n \"suggestions\": [],\n \"summary\": \"The work experience section is detailed and relevant, showcasing roles and responsibilities effectively, with clear job titles and dates.\",\n \"keywords\": { \"analityk\": 4, \"systemów\": 4, \"IT\": 6, \"projekty\": 4, \"współpraca\": 3 }\n },\n \"Education\": {\n \"score\": 7,\n \"suggestions\": [\n \"Provide dates for all educational entries for consistency.\",\n \"Consider adding any relevant coursework or projects to enhance completeness.\"\n ],\n \"summary\": \"The education section lists qualifications but lacks specific dates for every entry and does not include additional relevant details.\",\n \"keywords\": { \"studia\": 3, \"Politechnika\": 3, \"certyfikaty\": 1, \"sieci\": 1 }\n },\n \"Skills\": {\n \"score\": 8,\n \"suggestions\": [\n \"Group skills into categories (e.g., technical skills, soft skills) for clarity.\",\n \"Add specific software or tools to demonstrate technical expertise.\"\n ],\n \"summary\": \"The skills section summarizes capabilities but could benefit from organization and inclusion of specific skills relevant to jobs being applied for.\",\n \"keywords\": { \"techniczne\": 1, \"wiedza\": 1, \"umiejętności\": 1 }\n },\n \"Certifications\": {\n \"score\": 8,\n \"suggestions\": [\n \"Organize certifications in chronological order or by relevance.\",\n \"Include the dates of certifications for better context.\"\n ],\n \"summary\": \"The certifications are relevant but could be polished by adding organization and dates to enhance clarity.\",\n \"keywords\": { \"certyfikat\": 2, \"szkolenie\": 6, \"ITIL\": 2 }\n },\n \"Projects\": {\n \"score\": 6,\n \"suggestions\": [\n \"Provide more detail on individual projects, focusing on specific roles and outcomes.\",\n \"Include dates for project completion to establish a timeline.\"\n ],\n \"summary\": \"The projects section is present but lacks depth regarding specific responsibilities or results, making it less impactful.\",\n \"keywords\": { \"projekt\": 3, \"systemy\": 2, \"migrować\": 1 }\n }\n },\n \"openai_stats\": {\n \"input_tokens\": 1424,\n \"output_tokens\": 668,\n \"total_tokens\": 2092,\n \"cost\": 0.002092\n }\n}\n```",
|
||||||
|
"role": "assistant"
|
||||||
|
},
|
||||||
|
"finish_reason": "stop",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"usage": {
|
||||||
|
"prompt_tokens": 2668,
|
||||||
|
"completion_tokens": 679,
|
||||||
|
"total_tokens": 3347
|
||||||
|
},
|
||||||
|
"cost": 0,
|
||||||
|
"model": "gpt-4o-mini-2024-07-18"
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"choices": [
|
||||||
|
{
|
||||||
|
"message": {
|
||||||
|
"content": "```json\n{\n \"sections\": {\n \"Summary\": {\n \"score\": 8,\n \"suggestions\": [\n \"Consider elaborating on specific achievements or key projects to highlight impact.\",\n \"Include more quantifiable metrics to showcase successful outcomes.\"\n ],\n \"summary\": \"The summary provides a clear overview of the candidate's professional background and experience in business analysis and system architecture. It indicates substantial experience but lacks specific examples of accomplishments.\",\n \"keywords\": {\n \"Analityk\": 4,\n \"biznesowy\": 2,\n \"systemowy\": 2,\n \"doświadczenie\": 1,\n \"technologicznych\": 1\n }\n },\n \"Work Experience\": {\n \"score\": 9,\n \"suggestions\": [],\n \"summary\": \"The work experience section is comprehensive, detailing various roles and responsibilities across multiple companies. It demonstrates a strong background in the IT sector with clear responsibilities and contributions but could benefit from more quantifiable outcomes.\",\n \"keywords\": {\n \"analityk\": 6,\n \"systemów\": 5,\n \"projekt\": 4,\n \"współpraca\": 3,\n \"technologii\": 3,\n \"wymagań\": 2,\n \"usług\": 2\n }\n },\n \"Education\": {\n \"score\": 8,\n \"suggestions\": [\n \"Specify the dates for when the education was completed.\",\n \"Only include institutions that are directly relevant to the position being applied for.\"\n ],\n \"summary\": \"The education section lists relevant degrees and institutions, highlighting a solid academic background in technology and information systems. Adding completion dates could enhance clarity.\",\n \"keywords\": {\n \"studia\": 3,\n \"Politechnika\": 2,\n \"informatycznych\": 2,\n \"CISCO\": 1,\n \"specjalność\": 1\n }\n },\n \"Skills\": {\n \"score\": 7,\n \"suggestions\": [\n \"Add more technical skills that are specifically relevant to the industry.\",\n \"Provide a clearer structure, possibly categorizing hard and soft skills.\"\n ],\n \"summary\": \"The skills section is notably brief. It lists language proficiency but lacks a comprehensive enumeration of technical and soft skills essential for the role of a business analyst.\",\n \"keywords\": {\n \"angielski\": 1,\n \"niemiecki\": 1\n }\n },\n \"Certifications\": {\n \"score\": 9,\n \"suggestions\": [],\n \"summary\": \"The certifications are presented clearly, showing a variety of relevant courses and certifications. This indicates a commitment to professional development and continuous learning.\",\n \"keywords\": {\n \"certyfikat\": 2,\n \"szkolenie\": 8,\n \"ITIL\": 2,\n \"IBM\": 3\n }\n },\n \"Projects\": {\n \"score\": 6,\n \"suggestions\": [\n \"Include specific projects with concise descriptions and impacts.\",\n \"List projects in a structured format, summarizing outcomes and key learnings.\"\n ],\n \"summary\": \"The projects section is not explicitly defined and lacks specifics. While detailed experience is found in work experience, this section would benefit from a clear presentation of significant projects and their outcomes.\",\n \"keywords\": {}\n }\n },\n \"openai_stats\": {\n \"input_tokens\": 1244,\n \"output_tokens\": 646,\n \"total_tokens\": 1890,\n \"cost\": 0.002\n }\n}\n```",
|
||||||
|
"role": "assistant"
|
||||||
|
},
|
||||||
|
"finish_reason": "stop",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"usage": {
|
||||||
|
"prompt_tokens": 2668,
|
||||||
|
"completion_tokens": 760,
|
||||||
|
"total_tokens": 3428
|
||||||
|
},
|
||||||
|
"cost": 0.0,
|
||||||
|
"model": "gpt-4o-mini-2024-07-18"
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"choices": [
|
||||||
|
{
|
||||||
|
"message": {
|
||||||
|
"content": "```json\n{\n \"sections\": {\n \"Summary\": {\n \"score\": 8,\n \"suggestions\": [\n \"Consider adding specific achievements or metrics to quantify your impact.\",\n \"Refine language to be more concise and impactful.\"\n ],\n \"summary\": \"The summary provides a clear professional profile highlighting experience in business analysis and technology. However, it lacks specific achievements.\",\n \"keywords\": {\n \"Analityk\": 3,\n \"biznesowy\": 3,\n \"systemowy\": 3,\n \"doświadczenie\": 2,\n \"technologicznych\": 1\n }\n },\n \"Work Experience\": {\n \"score\": 9,\n \"suggestions\": [],\n \"summary\": \"The work experience section is comprehensive, detailing roles and responsibilities with an emphasis on contributions to projects. The use of bullet points enhances readability.\",\n \"keywords\": {\n \"analityk\": 4,\n \"programów\": 3,\n \"systemów\": 4,\n \"projektów\": 4,\n \"współpraca\": 3\n }\n },\n \"Education\": {\n \"score\": 8,\n \"suggestions\": [\n \"Specify the completion dates for each education entry.\",\n \"Include any honors or relevant courses to enhance detail.\"\n ],\n \"summary\": \"The education section lists relevant degrees and certifications, but lacks completion dates and additional achievements.\",\n \"keywords\": {\n \"studia\": 3,\n \"Politechnika\": 2,\n \"CISCO\": 1,\n \"certyfikat\": 1\n }\n },\n \"Skills\": {\n \"score\": 7,\n \"suggestions\": [\n \"List specific technical skills or tools you are proficient in.\",\n \"Group skills into categories for improved clarity.\"\n ],\n \"summary\": \"The skills section is minimal and lacks specificity. Adding more detailed skills related to business analysis and technology would be beneficial.\",\n \"keywords\": {\n \"analityka\": 1,\n \"systemowy\": 1\n }\n },\n \"Certifications\": {\n \"score\": 9,\n \"suggestions\": [],\n \"summary\": \"The certifications section is well-detailed and relevant, showcasing important qualifications for the field.\",\n \"keywords\": {\n \"certyfikat\": 1,\n \"szkolenie\": 5\n }\n },\n \"Projects\": {\n \"score\": 6,\n \"suggestions\": [\n \"Add specific project names and outcomes to illustrate contributions.\",\n \"Include metrics or results achieved in projects.\"\n ],\n \"summary\": \"The projects section is lacking, as it does not list projects explicitly or specify contributions. More detail could improve understanding of expertise.\",\n \"keywords\": {\n \"projekt\": 1,\n \"analiz\": 1\n }\n }\n },\n \"openai_stats\": {\n \"input_tokens\": 1318,\n \"output_tokens\": 509,\n \"total_tokens\": 1827,\n \"cost\": 0.002053\n }\n}\n```",
|
||||||
|
"role": "assistant"
|
||||||
|
},
|
||||||
|
"finish_reason": "stop",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"usage": {
|
||||||
|
"prompt_tokens": 2668,
|
||||||
|
"completion_tokens": 658,
|
||||||
|
"total_tokens": 3326
|
||||||
|
},
|
||||||
|
"cost": 0,
|
||||||
|
"model": "gpt-4o-mini-2024-07-18"
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"choices": [
|
||||||
|
{
|
||||||
|
"message": {
|
||||||
|
"content": "```json\n{\n \"sections\": {\n \"Summary\": {\n \"score\": 8,\n \"suggestions\": [\"Add specific metrics to quantify achievements.\", \"Clarify the type of industries and roles you are most experienced in.\"],\n \"summary\": \"The summary provides a brief professional profile, emphasizing business and system analysis experience. However, it lacks specific metrics or examples of achievements.\",\n \"keywords\": {\n \"analityk\": 3,\n \"doświadczenie\": 2,\n \"systemowy\": 2,\n \"architekt\": 1,\n \"manager\": 1\n }\n },\n \"Work Experience\": {\n \"score\": 9,\n \"suggestions\": [],\n \"summary\": \"The work experience section is comprehensive, detailing roles, responsibilities, and projects. Each role is clearly delineated, showcasing relevant experience and contributions.\",\n \"keywords\": {\n \"analityk\": 5,\n \"system\": 4,\n \"projekt\": 4,\n \"zespół\": 2,\n \"usługi\": 3,\n \"współpraca\": 2\n }\n },\n \"Education\": {\n \"score\": 8,\n \"suggestions\": [\"Add graduation dates for each educational experience.\", \"Clearly specify the fields of study.\"],\n \"summary\": \"The education section provides various qualifications, but it could benefit from specific graduation dates and clarification of study fields.\",\n \"keywords\": {\n \"Politechnika\": 2,\n \"studia\": 3,\n \"CISCO\": 1,\n \"magisterskie\": 1,\n \"inżynierskie\": 1\n }\n },\n \"Skills\": {\n \"score\": 7,\n \"suggestions\": [\"List both hard and soft skills explicitly.\", \"Include any technical skills relevant to the roles applied for.\"],\n \"summary\": \"The skills section needs improvement; it lacks a clear list of both hard and soft skills that could enhance the individual's candidacy.\",\n \"keywords\": {\n \"CRM\": 2,\n \"analiza\": 2,\n \"zrozumienie\": 1,\n \"systemowy\": 1,\n \"projektowanie\": 1\n }\n },\n \"Certifications\": {\n \"score\": 9,\n \"suggestions\": [],\n \"summary\": \"The certifications section is strong with relevant certifications listed, demonstrating a commitment to professional development.\",\n \"keywords\": {\n \"certyfikat\": 2,\n \"ITIL\": 2,\n \"szkolenie\": 5,\n \"IBM\": 3\n }\n },\n \"Projects\": {\n \"score\": 7,\n \"suggestions\": [\"Add more details about specific projects (e.g., outcomes, skills used).\", \"Highlight any leadership roles in projects.\"],\n \"summary\": \"The projects section is present but lacks depth; it could highlight key achievements and the impact of each project.\",\n \"keywords\": {\n \"projekt\": 4,\n \"systemowy\": 2,\n \"analiza\": 1,\n \"zespół\": 2\n }\n }\n },\n \"openai_stats\": {\n \"input_tokens\": 1291,\n \"output_tokens\": 566,\n \"total_tokens\": 1857,\n \"cost\": 0.004\n }\n}\n```",
|
||||||
|
"role": "assistant"
|
||||||
|
},
|
||||||
|
"finish_reason": "stop",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"usage": {
|
||||||
|
"prompt_tokens": 2668,
|
||||||
|
"completion_tokens": 720,
|
||||||
|
"total_tokens": 3388
|
||||||
|
},
|
||||||
|
"cost": 0.0,
|
||||||
|
"model": "gpt-4o-mini-2024-07-18"
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"choices": [
|
||||||
|
{
|
||||||
|
"message": {
|
||||||
|
"content": "```json\n{\n \"sections\": {\n \"Summary\": {\n \"score\": 8,\n \"suggestions\": [\n \"Make the summary more concise by focusing on key skills and achievements.\",\n \"Add specific examples of business analysis and architecture achievements.\"\n ],\n \"summary\": \"Strong professional summary indicating a solid background in business and system analysis with over 10 years of relevant experience, but lacks specific accomplishments.\",\n \"keywords\": {\n \"business analyst\": 1,\n \"system architect\": 1,\n \"manager\": 1,\n \"experience\": 1\n }\n },\n \"Work Experience\": {\n \"score\": 9,\n \"suggestions\": [],\n \"summary\": \"Detailed work experience in various roles with a focus on business analysis and IT management. Effective descriptions of responsibilities and contributions, although some job roles could highlight specific achievements more clearly.\",\n \"keywords\": {\n \"business analysis\": 5,\n \"system\": 6,\n \"IT\": 4,\n \"project\": 2,\n \"analysis\": 3,\n \"documentation\": 2\n }\n },\n \"Education\": {\n \"score\": 8,\n \"suggestions\": [\n \"Specify graduation dates for each educational qualification.\",\n \"Include any honors or distinctions received during studies.\"\n ],\n \"summary\": \"Solid educational background with relevant degrees and certifications in technology and electronics, but lacks detail on specific achievements or honors.\",\n \"keywords\": {\n \"degree\": 3,\n \"education\": 2,\n \"network associate\": 1\n }\n },\n \"Skills\": {\n \"score\": 7,\n \"suggestions\": [\n \"Expand on the range of technical and soft skills relevant to the positions sought.\",\n \"Organize skills into categories (e.g., Technical, Analytical, Interpersonal) for better clarity.\"\n ],\n \"summary\": \"Skills listed are somewhat general; better categorization and specificity could improve overall relevance.\",\n \"keywords\": {\n \"skills\": 1,\n \"analysis\": 2,\n \"communication\": 1\n }\n },\n \"Certifications\": {\n \"score\": 9,\n \"suggestions\": [],\n \"summary\": \"The section is well-structured and lists relevant certifications clearly, showcasing continuous professional development.\",\n \"keywords\": {\n \"certification\": 1,\n \"ITIL\": 1,\n \"CISCO\": 1\n }\n },\n \"Projects\": {\n \"score\": 6,\n \"suggestions\": [\n \"Provide more specific details about project outcomes or impacts.\",\n \"Highlight personal contributions or leadership roles in notable projects.\"\n ],\n \"summary\": \"Projects are mentioned but lack depth regarding impact and individual contributions. More concrete successes would strengthen the narrative.\",\n \"keywords\": {\n \"project\": 3,\n \"migration\": 1,\n \"implementation\": 1\n }\n }\n },\n \"openai_stats\": {\n \"input_tokens\": 2155,\n \"output_tokens\": 722,\n \"total_tokens\": 2877,\n \"cost\": 0.002877\n }\n}\n```",
|
||||||
|
"role": "assistant"
|
||||||
|
},
|
||||||
|
"finish_reason": "stop",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"usage": {
|
||||||
|
"prompt_tokens": 2668,
|
||||||
|
"completion_tokens": 668,
|
||||||
|
"total_tokens": 3336
|
||||||
|
},
|
||||||
|
"cost": 0.0,
|
||||||
|
"model": "gpt-4o-mini-2024-07-18"
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"choices": [
|
||||||
|
{
|
||||||
|
"message": {
|
||||||
|
"content": "```json\n{\n \"sections\": {\n \"Summary\": {\n \"score\": 8,\n \"suggestions\": [\"Consider including specific achievements or metrics to highlight your impact.\", \"Make the language more concise and powerful.\"],\n \"summary\": \"The summary provides a clear overview of the candidate's role and experience but lacks specific accomplishments that could strengthen it.\",\n \"keywords\": { \"Analityk\": 2, \"doświadczenie\": 1, \"manager\": 1, \"architekt\": 1 }\n },\n \"Work Experience\": {\n \"score\": 9,\n \"suggestions\": [],\n \"summary\": \"The work experience section is detailed and comprehensive, showcasing a strong career progression and relevant expertise in various roles.\",\n \"keywords\": { \"IT\": 6, \"analityk\": 5, \"systemów\": 5, \"projekt\": 5, \"współpraca\": 4, \"klientów\": 3, \"usług\": 3 }\n },\n \"Education\": {\n \"score\": 9,\n \"suggestions\": [],\n \"summary\": \"Education section is informative and highlights relevant degrees and certifications, showcasing the candidate's academic background.\",\n \"keywords\": { \"studia\": 3, \"Politechnika Warszawska\": 2, \"CISCO\": 1, \"Magister\": 1, \"Inżynierskie\": 1 }\n },\n \"Skills\": {\n \"score\": 7,\n \"suggestions\": [\"List skills specifically related to the positions applied for.\", \"Consider organizing skills into relevant categories.\"],\n \"summary\": \"Skills section is not explicitly defined, making it difficult to quickly assess the candidate's qualifications. Specific skills and categories would add clarity.\",\n \"keywords\": { \"analiza\": 2, \"systemy\": 1, \"współpraca\": 1, \"usługi\": 1 }\n },\n \"Certifications\": {\n \"score\": 8,\n \"suggestions\": [\"Add the date for each certification obtained for better clarity.\", \"Consider grouping certifications by relevance.\"],\n \"summary\": \"The certifications section lists various relevant training and qualifications but would benefit from more organization and specificity.\",\n \"keywords\": { \"certyfikat\": 1, \"szkolenie\": 1, \"ITIL\": 2 }\n },\n \"Projects\": {\n \"score\": 7,\n \"suggestions\": [\"Include specific project names and outcomes to enhance detail.\", \"Highlight individual contributions more clearly.\"],\n \"summary\": \"The projects section provides some context but lacks clear delineation of specific projects or the candidate's individual contributions and results.\",\n \"keywords\": { \"projekt\": 3, \"współpraca\": 2, \"systemy\": 1 }\n }\n },\n \"openai_stats\": {\n \"input_tokens\": 1526,\n \"output_tokens\": 469,\n \"total_tokens\": 1995,\n \"cost\": 0.09975\n }\n}\n```",
|
||||||
|
"role": "assistant"
|
||||||
|
},
|
||||||
|
"finish_reason": "stop",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"usage": {
|
||||||
|
"prompt_tokens": 2668,
|
||||||
|
"completion_tokens": 647,
|
||||||
|
"total_tokens": 3315
|
||||||
|
},
|
||||||
|
"cost": 0.0,
|
||||||
|
"model": "gpt-4o-mini-2024-07-18"
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"choices": [
|
||||||
|
{
|
||||||
|
"message": {
|
||||||
|
"content": "```json\n{\n \"sections\": {\n \"Summary\": {\n \"score\": 8,\n \"suggestions\": [\n \"Consider adding specific achievements or metrics to highlight impact.\",\n \"Simplify language for clearer understanding.\"\n ],\n \"summary\": \"The summary provides a clear overview of the candidate's experience and roles in business analysis and IT management but can be improved by adding specific achievements to quantify their contributions.\",\n \"keywords\": {\n \"analityk\": 3,\n \"doświadczenie\": 2,\n \"architekt\": 1,\n \"manager\": 1\n }\n },\n \"Work Experience\": {\n \"score\": 9,\n \"suggestions\": [],\n \"summary\": \"The work experience section is detailed, presenting clear job roles, responsibilities, and contributions. It utilizes strong action verbs but could be enhanced with quantifiable results in some roles.\",\n \"keywords\": {\n \"analiz\": 5,\n \"biznesowy\": 4,\n \"systemowy\": 4,\n \"projekt\": 4,\n \"współpraca\": 3,\n \"wymagania\": 2\n }\n },\n \"Education\": {\n \"score\": 8,\n \"suggestions\": [\n \"Specify the graduation status for higher education.\",\n \"Consider listing any honors or relevant coursework.\"\n ],\n \"summary\": \"The education section is comprehensive, including degrees and specialized training, but it lacks mention of graduation status and could highlight additional relevant coursework.\",\n \"keywords\": {\n \"Politechnika\": 2,\n \"CISCO\": 1,\n \"Magisterskie\": 1,\n \"Inżynierskie\": 1\n }\n },\n \"Skills\": {\n \"score\": 7,\n \"suggestions\": [\n \"Categorize skills into technical and soft skills for clarity.\",\n \"Add more specific technologies or methodologies relevant to the roles applied for.\"\n ],\n \"summary\": \"The skills section is minimal and lacks depth. Categorizing skills can improve clarity and relevance, and including specific technologies or methodologies would strengthen the section.\",\n \"keywords\": {\n \"szkoleń\": 4,\n \"certyfikaty\": 2,\n \"prawo jazdy\": 1\n }\n },\n \"Certifications\": {\n \"score\": 9,\n \"suggestions\": [],\n \"summary\": \"The certifications section is strong, detailing relevant training and certifications that add credibility to the candidate's qualifications.\",\n \"keywords\": {\n \"certyfikat\": 1,\n \"szkolenie\": 9\n }\n },\n \"Projects\": {\n \"score\": 6,\n \"suggestions\": [\n \"Create a separate section for key projects with descriptions and outcomes.\",\n \"Highlight individual contributions to collaborative projects.\"\n ],\n \"summary\": \"The projects are mentioned informally within work experience; however, creating a dedicated section would better emphasize significant projects and achievements.\",\n \"keywords\": {\n \"projekt\": 4,\n \"wymagania\": 2\n }\n }\n },\n \"openai_stats\": {\n \"input_tokens\": 2585,\n \"output_tokens\": 677,\n \"total_tokens\": 3262,\n \"cost\": 0.01308\n }\n}\n```",
|
||||||
|
"role": "assistant"
|
||||||
|
},
|
||||||
|
"finish_reason": "stop",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"usage": {
|
||||||
|
"prompt_tokens": 2668,
|
||||||
|
"completion_tokens": 706,
|
||||||
|
"total_tokens": 3374
|
||||||
|
},
|
||||||
|
"cost": 0.0,
|
||||||
|
"model": "gpt-4o-mini-2024-07-18"
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"choices": [
|
||||||
|
{
|
||||||
|
"message": {
|
||||||
|
"content": "```json\n{\n \"sections\": {\n \"Summary\": {\n \"score\": 8,\n \"suggestions\": [\n \"Add specific accomplishments or metrics to demonstrate impact\",\n \"Consider using bullet points for easier readability\"\n ],\n \"summary\": \"The summary provides a clear overview of the candidate's experience and roles as a business analyst, architect, and manager. However, it lacks specific details on achievements or metrics that could enhance its impact.\",\n \"keywords\": {\n \"Analityk biznesowy\": 1,\n \"systemowy\": 1,\n \"architekt\": 1,\n \"manager\": 1,\n \"doświadczenie\": 1\n }\n },\n \"Work Experience\": {\n \"score\": 9,\n \"suggestions\": [],\n \"summary\": \"This section provides comprehensive details about the candidate's relevant work experience, including roles, responsibilities, and achievements. It is well-structured and effectively highlights the candidate’s expertise.\",\n \"keywords\": {\n \"analityk\": 5,\n \"systemowy\": 2,\n \"kierownik\": 2,\n \"dzieło\": 2,\n \"projekt\": 3,\n \"współpraca\": 2,\n \"systemy\": 3,\n \"dokumentacja\": 2\n }\n },\n \"Education\": {\n \"score\": 8,\n \"suggestions\": [\n \"Include graduation years for better context\",\n \"Consider adding any honors or relevant coursework\"\n ],\n \"summary\": \"The education section lists relevant degrees and certifications, but lacks graduation dates and specifics about honors which could strengthen the presentation.\",\n \"keywords\": {\n \"Magisterskie\": 1,\n \"Inżynierskie\": 1,\n \"Politechnika\": 2,\n \"CISCO\": 1,\n \"specjalność\": 3\n }\n },\n \"Skills\": {\n \"score\": 7,\n \"suggestions\": [\n \"Add more specific technical and soft skills\",\n \"Group skills into categories for clarity\"\n ],\n \"summary\": \"The skills section is brief and could benefit from more detail. Including specific technical skills, soft skills, and grouping them would enhance this section’s effectiveness.\",\n \"keywords\": {}\n },\n \"Certifications\": {\n \"score\": 9,\n \"suggestions\": [],\n \"summary\": \"The certifications section is well-detailed, showcasing a range of relevant training and certifications that support the candidate's qualifications. No improvements needed.\",\n \"keywords\": {\n \"certyfikat\": 3,\n \"szkolenie\": 6,\n \"ITIL\": 2\n }\n },\n \"Projects\": {\n \"score\": 8,\n \"suggestions\": [\n \"Provide more detailed descriptions of key projects\",\n \"Highlight any specific outcomes or results achieved\"\n ],\n \"summary\": \"The projects section includes relevant experiences but would be improved by elaborating on the specifics of projects and their outcomes, including metrics or achievements.\",\n \"keywords\": {\n \"projekt\": 4,\n \"analiza\": 2,\n \"współpraca\": 1\n }\n }\n },\n \"openai_stats\": {\n \"input_tokens\": 1695,\n \"output_tokens\": 712,\n \"total_tokens\": 2407,\n \"cost\": 0.0035\n }\n}\n```",
|
||||||
|
"role": "assistant"
|
||||||
|
},
|
||||||
|
"finish_reason": "stop",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"usage": {
|
||||||
|
"prompt_tokens": 2668,
|
||||||
|
"completion_tokens": 729,
|
||||||
|
"total_tokens": 3397
|
||||||
|
},
|
||||||
|
"cost": 0,
|
||||||
|
"model": "gpt-4o-mini-2024-07-18"
|
||||||
|
}
|
||||||
@ -59,10 +59,11 @@ Required Environment Variables:
|
|||||||
- MONGODB_URI: MongoDB connection string (optional for mockup mode)""",
|
- MONGODB_URI: MongoDB connection string (optional for mockup mode)""",
|
||||||
usage="resume_analysis.py [-h] [-f FILE] [-m]",
|
usage="resume_analysis.py [-h] [-f FILE] [-m]",
|
||||||
epilog="""Examples:
|
epilog="""Examples:
|
||||||
Analyze a resume: resume_analysis.py -f my_resume.pdf
|
Analyze a resume: resume_analysis.py -f my_resume.txt
|
||||||
Test with mockup data: resume_analysis.py -f test.pdf -m"""
|
Test with mockup data: resume_analysis.py -f test.txt -m"""
|
||||||
)
|
)
|
||||||
parser.add_argument('-f', '--file', help='Path to the resume file to analyze (PDF or text)')
|
parser.add_argument('-f', '--file', help='Path to the resume file to analyze (TXT)')
|
||||||
|
parser.add_argument('-p', '--pdf', help='Path to the resume file to analyze (PDF)')
|
||||||
parser.add_argument('-m', '--mockup', action='store_true', help='Use mockup response instead of calling OpenAI API')
|
parser.add_argument('-m', '--mockup', action='store_true', help='Use mockup response instead of calling OpenAI API')
|
||||||
|
|
||||||
# If no arguments provided, show help and exit
|
# If no arguments provided, show help and exit
|
||||||
@ -79,21 +80,48 @@ Required Environment Variables:
|
|||||||
if use_mockup:
|
if use_mockup:
|
||||||
resume_text = "Mockup resume text"
|
resume_text = "Mockup resume text"
|
||||||
else:
|
else:
|
||||||
if not os.path.exists(args.file):
|
if args.pdf:
|
||||||
logger.error(f"File not found: {args.file}")
|
if not os.path.exists(args.pdf):
|
||||||
sys.exit(1)
|
logger.error(f"PDF file not found: {args.pdf}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
start_file_read_time = time.time()
|
start_file_read_time = time.time()
|
||||||
with open(args.file, 'r') as f:
|
try:
|
||||||
resume_text = f.read()
|
resume_text = extract_text(args.pdf)
|
||||||
file_read_time = time.time() - start_file_read_time
|
except Exception as e:
|
||||||
logger.debug(f"File read time: {file_read_time:.2f} seconds")
|
logger.error(f"Error extracting text from PDF: {e}", exc_info=True)
|
||||||
|
sys.exit(1)
|
||||||
|
file_read_time = time.time() - start_file_read_time
|
||||||
|
logger.debug(f"PDF file read time: {file_read_time:.2f} seconds")
|
||||||
|
# Save extracted text to file
|
||||||
|
pdf_filename = os.path.splitext(os.path.basename(args.pdf))[0]
|
||||||
|
text_file_path = os.path.join(os.path.dirname(args.pdf), f"{pdf_filename}_text.txt")
|
||||||
|
with open(text_file_path, "w", encoding="utf-8") as text_file:
|
||||||
|
text_file.write(resume_text)
|
||||||
|
logger.debug(f"Extracted text saved to: {text_file_path}")
|
||||||
|
elif args.file:
|
||||||
|
if not os.path.exists(args.file):
|
||||||
|
logger.error(f"File not found: {args.file}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
start_file_read_time = time.time()
|
||||||
|
with open(args.file, 'r', encoding='latin-1') as f:
|
||||||
|
resume_text = f.read()
|
||||||
|
file_read_time = time.time() - start_file_read_time
|
||||||
|
logger.debug(f"File read time: {file_read_time:.2f} seconds")
|
||||||
|
else:
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
# Call the OpenAI API with the resume text
|
# Call the OpenAI API with the resume text
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
response = call_openai_api(resume_text, use_mockup)
|
try:
|
||||||
openai_api_time = time.time() - start_time
|
response = call_openai_api(resume_text, use_mockup)
|
||||||
logger.debug(f"OpenAI API call time: {openai_api_time:.2f} seconds")
|
openai_api_time = time.time() - start_time
|
||||||
|
logger.debug(f"OpenAI API call time: {openai_api_time:.2f} seconds")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error during OpenAI API call: {e}", exc_info=True)
|
||||||
|
response = None
|
||||||
# Initialize MongoDB collection only when needed
|
# Initialize MongoDB collection only when needed
|
||||||
cv_collection = get_mongo_collection()
|
cv_collection = get_mongo_collection()
|
||||||
|
|
||||||
@ -111,7 +139,7 @@ def load_mockup_response(mockup_file_path: str) -> dict:
|
|||||||
raise FileNotFoundError(f"Mockup file not found at: {mockup_file_path}")
|
raise FileNotFoundError(f"Mockup file not found at: {mockup_file_path}")
|
||||||
with open(mockup_file_path, "r") as f:
|
with open(mockup_file_path, "r") as f:
|
||||||
response = json.load(f)
|
response = json.load(f)
|
||||||
response.setdefault("openai_stats", {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0})
|
#response.setdefault("openai_stats", {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0})
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def call_openai_api(text: str, use_mockup: bool) -> Optional[Any]:
|
def call_openai_api(text: str, use_mockup: bool) -> Optional[Any]:
|
||||||
@ -119,7 +147,7 @@ def call_openai_api(text: str, use_mockup: bool) -> Optional[Any]:
|
|||||||
logger.debug("Calling OpenAI API.")
|
logger.debug("Calling OpenAI API.")
|
||||||
try:
|
try:
|
||||||
if use_mockup:
|
if use_mockup:
|
||||||
return load_mockup_response(MOCKUP_FILE_PATH)
|
return load_mockup_response(os.path.join(os.path.dirname(__file__), 'tests', 'mockup_response.json'))
|
||||||
|
|
||||||
with open(os.path.join(os.path.dirname(__file__), "prompt.txt"), "r") as prompt_file:
|
with open(os.path.join(os.path.dirname(__file__), "prompt.txt"), "r") as prompt_file:
|
||||||
system_content = prompt_file.read()
|
system_content = prompt_file.read()
|
||||||
@ -138,20 +166,35 @@ def call_openai_api(text: str, use_mockup: bool) -> Optional[Any]:
|
|||||||
logger.error(f"Error during OpenAI API call: {e}", exc_info=True)
|
logger.error(f"Error during OpenAI API call: {e}", exc_info=True)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def write_openai_response(response: Any, use_mockup: bool, input_file_path: str = None, cost: float = 0) -> None: # Add cost argument
|
def write_openai_response(response: Any, use_mockup: bool, input_file_path: str = None, cost: float = 0) -> None:
|
||||||
"""Write raw OpenAI response to a file."""
|
"""Write raw OpenAI response to a file."""
|
||||||
if use_mockup:
|
if use_mockup:
|
||||||
logger.debug("Using mockup response; no OpenAI message to write.")
|
logger.debug("Using mockup response; no OpenAI message to write.")
|
||||||
return
|
return
|
||||||
if response and response.choices: # Changed from hasattr to direct attribute access
|
|
||||||
|
if response and response.choices:
|
||||||
message_content = response.choices[0].message.content
|
message_content = response.choices[0].message.content
|
||||||
logger.debug(f"Raw OpenAI message content: {message_content}")
|
logger.debug(f"Raw OpenAI message content: {message_content}")
|
||||||
output_dir = os.path.dirname(input_file_path) if input_file_path else '.'
|
|
||||||
base_filename = os.path.splitext(os.path.basename(input_file_path))[0] if input_file_path else "default"
|
if input_file_path:
|
||||||
|
output_dir = os.path.dirname(input_file_path)
|
||||||
|
base_filename = os.path.splitext(os.path.basename(input_file_path))[0]
|
||||||
|
else:
|
||||||
|
logger.warning("Input file path not provided. Using default output directory and filename.")
|
||||||
|
output_dir = os.path.join(os.path.dirname(__file__)) # Default to script's directory
|
||||||
|
base_filename = "default" # Default filename
|
||||||
|
|
||||||
processing_id = str(uuid.uuid4())
|
processing_id = str(uuid.uuid4())
|
||||||
file_path = os.path.join(output_dir, f"{base_filename}_openai_response_{processing_id}") + ".json"
|
file_path = os.path.join(output_dir, f"{base_filename}_openai_response_{processing_id}") + ".json"
|
||||||
|
openai_file_path = os.path.join(output_dir, f"{base_filename}_openai.txt")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
serializable_response = { # Create a serializable dictionary
|
message_content = response.choices[0].message.content if response and response.choices else "No content"
|
||||||
|
with open(openai_file_path, "w", encoding="utf-8") as openai_file:
|
||||||
|
openai_file.write(message_content)
|
||||||
|
logger.debug(f"OpenAI response written to {openai_file_path}")
|
||||||
|
|
||||||
|
serializable_response = {
|
||||||
"choices": [
|
"choices": [
|
||||||
{
|
{
|
||||||
"message": {
|
"message": {
|
||||||
@ -162,46 +205,64 @@ def write_openai_response(response: Any, use_mockup: bool, input_file_path: str
|
|||||||
"index": choice.index
|
"index": choice.index
|
||||||
} for choice in response.choices
|
} for choice in response.choices
|
||||||
],
|
],
|
||||||
"openai_stats": {
|
"usage": {
|
||||||
"prompt_tokens": response.usage.prompt_tokens,
|
"prompt_tokens": response.usage.prompt_tokens,
|
||||||
"completion_tokens": response.usage.completion_tokens,
|
"completion_tokens": response.usage.completion_tokens,
|
||||||
"total_tokens": response.usage.total_tokens
|
"total_tokens": response.usage.total_tokens
|
||||||
},
|
},
|
||||||
"cost": cost, # Include cost in the output JSON
|
"cost": cost, # Include cost in the output JSON
|
||||||
"model": response.model
|
"model": response.model
|
||||||
}
|
}
|
||||||
with open(file_path, "w") as f:
|
with open(file_path, "w") as f:
|
||||||
json.dump(serializable_response, f, indent=2) # Dump the serializable dictionary
|
json.dump(serializable_response, f, indent=2, ensure_ascii=False)
|
||||||
logger.debug(f"OpenAI response written to {file_path}")
|
logger.debug(f"OpenAI response written to {file_path}")
|
||||||
|
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
logger.error(f"Failed to write OpenAI response to file: {e}")
|
logger.error(f"Failed to write OpenAI response to file: {e}")
|
||||||
else:
|
else:
|
||||||
logger.warning("No choices in OpenAI response to extract message from.")
|
logger.warning("No choices in OpenAI response to extract message from.")
|
||||||
logger.debug(f"Response object: {response}")
|
logger.debug(f"Response object: {response}")
|
||||||
|
|
||||||
def insert_processing_data(text_content: str, summary: dict, response: Any, args: argparse.Namespace, processing_id: str, use_mockup: bool, cv_collection) -> None:
|
def insert_processing_data(text_content: str, summary: dict, response: Any, args: argparse.Namespace, processing_id: str, use_mockup: bool, cv_collection) -> float:
|
||||||
"""Insert processing data into MongoDB."""
|
"""Insert processing data into MongoDB."""
|
||||||
logger.debug("Inserting processing data into MongoDB.")
|
logger.debug("Inserting processing data into MongoDB.")
|
||||||
|
cost = 0.0 # Initialize cost to 0.0
|
||||||
if not use_mockup:
|
if not use_mockup:
|
||||||
if response and response.choices:
|
if response and response.choices:
|
||||||
message_content = response.choices[0].message.content
|
message_content = response.choices[0].message.content
|
||||||
|
openai_stats = {} # Initialize openai_stats
|
||||||
try:
|
try:
|
||||||
openai_stats_content = json.loads(message_content)
|
# Attempt to decode JSON, handling potential decode errors
|
||||||
|
openai_stats_content = json.loads(message_content.encode('utf-8').decode('unicode_escape'))
|
||||||
openai_stats = openai_stats_content.get("openai_stats", {})
|
openai_stats = openai_stats_content.get("openai_stats", {})
|
||||||
cost = openai_stats.get("cost", 0)
|
cost = openai_stats.get("cost", 0.0)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError as e:
|
||||||
logger.error("Failed to decode JSON from message content for openai_stats.")
|
logger.error(f"JSONDecodeError in message_content: {e}", exc_info=True)
|
||||||
openai_stats = {}
|
cost = 0.0
|
||||||
cost = 0
|
except AttributeError as e:
|
||||||
|
logger.error(f"AttributeError accessing openai_stats: {e}", exc_info=True)
|
||||||
|
cost = 0.0
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Unexpected error extracting cost: {e}", exc_info=True)
|
||||||
|
cost = 0.0
|
||||||
|
|
||||||
|
except AttributeError as e:
|
||||||
|
logger.error(f"AttributeError when accessing openai_stats or cost: {e}", exc_info=True)
|
||||||
|
cost = 0.0
|
||||||
|
|
||||||
|
try:
|
||||||
|
usage = response.usage
|
||||||
|
input_tokens = usage.prompt_tokens
|
||||||
|
output_tokens = usage.completion_tokens
|
||||||
|
total_tokens = usage.total_tokens
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error extracting usage data: {e}", exc_info=True)
|
||||||
|
input_tokens = output_tokens = total_tokens = 0
|
||||||
|
|
||||||
usage = response.usage
|
|
||||||
input_tokens = usage.prompt_tokens
|
|
||||||
output_tokens = usage.completion_tokens
|
|
||||||
total_tokens = usage.total_tokens
|
|
||||||
else:
|
else:
|
||||||
logger.error("Invalid response format or missing usage data.")
|
logger.error("Invalid response format or missing usage data.")
|
||||||
input_tokens = output_tokens = total_tokens = 0
|
input_tokens = output_tokens = total_tokens = 0
|
||||||
cost = 0
|
cost = 0.0
|
||||||
openai_stats = {}
|
openai_stats = {}
|
||||||
usage = {}
|
usage = {}
|
||||||
|
|
||||||
@ -214,9 +275,6 @@ def insert_processing_data(text_content: str, summary: dict, response: Any, args
|
|||||||
"usage_prompt_tokens": input_tokens, # Renamed to avoid collision
|
"usage_prompt_tokens": input_tokens, # Renamed to avoid collision
|
||||||
"usage_completion_tokens": output_tokens, # Renamed to avoid collision
|
"usage_completion_tokens": output_tokens, # Renamed to avoid collision
|
||||||
"usage_total_tokens": total_tokens, # Renamed to avoid collision
|
"usage_total_tokens": total_tokens, # Renamed to avoid collision
|
||||||
"openai_stats_input_tokens": openai_stats.get("input_tokens"),
|
|
||||||
"openai_stats_output_tokens": openai_stats.get("output_tokens"),
|
|
||||||
"openai_stats_total_tokens": openai_stats.get("total_tokens"),
|
|
||||||
"cost": cost
|
"cost": cost
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,7 +286,7 @@ def insert_processing_data(text_content: str, summary: dict, response: Any, args
|
|||||||
logger.error(f"Failed to insert processing data into MongoDB: {e}", exc_info=True)
|
logger.error(f"Failed to insert processing data into MongoDB: {e}", exc_info=True)
|
||||||
else:
|
else:
|
||||||
logger.debug("Using mockup; skipping MongoDB insertion.")
|
logger.debug("Using mockup; skipping MongoDB insertion.")
|
||||||
return 0 # Return 0 for mockup mode
|
return cost # Return 0 for mockup mode
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
Binary file not shown.
174
my-app/utils/tests/test_resume_analysis.py
Normal file
174
my-app/utils/tests/test_resume_analysis.py
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import pytest
|
||||||
|
from unittest.mock import patch, MagicMock
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import argparse # Import argparse
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
# Add the project root to the sys path to allow imports from the main package
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||||
|
|
||||||
|
from resume_analysis import (
|
||||||
|
call_openai_api,
|
||||||
|
insert_processing_data,
|
||||||
|
load_mockup_response,
|
||||||
|
main,
|
||||||
|
get_mongo_collection
|
||||||
|
)
|
||||||
|
|
||||||
|
# Load environment variables for testing
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
# Constants for Mocking
|
||||||
|
MOCKUP_FILE_PATH = os.path.join(os.path.dirname(__file__), 'mockup_response.json')
|
||||||
|
TEST_RESUME_PATH = os.path.join(os.path.dirname(__file__), 'test_resume.txt')
|
||||||
|
|
||||||
|
# Create a logger
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
# Create a handler and set the formatter
|
||||||
|
ch = logging.StreamHandler()
|
||||||
|
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||||
|
ch.setFormatter(formatter)
|
||||||
|
|
||||||
|
# Add the handler to the logger
|
||||||
|
logger.addHandler(ch)
|
||||||
|
|
||||||
|
# Mockup response data
|
||||||
|
MOCKUP_RESPONSE_DATA = {
|
||||||
|
"id": "chatcmpl-123",
|
||||||
|
"object": "chat.completion",
|
||||||
|
"created": 1677652288,
|
||||||
|
"model": "gpt-3.5-turbo-0301",
|
||||||
|
"usage": {
|
||||||
|
"prompt_tokens": 100,
|
||||||
|
"completion_tokens": 200,
|
||||||
|
"total_tokens": 300
|
||||||
|
},
|
||||||
|
"choices": [
|
||||||
|
{
|
||||||
|
"message": {
|
||||||
|
"role": "assistant",
|
||||||
|
"content": '{"openai_stats": {"prompt_tokens": 100, "completion_tokens": 200, "total_tokens": 300}}'
|
||||||
|
},
|
||||||
|
"finish_reason": "stop",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fixtures
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_openai_response():
|
||||||
|
mock_response = MagicMock()
|
||||||
|
mock_response.id = "chatcmpl-123"
|
||||||
|
mock_response.object = "chat.completion"
|
||||||
|
mock_response.created = 1677652288
|
||||||
|
mock_response.model = "gpt-3.5-turbo-0301"
|
||||||
|
mock_response.usage = MagicMock(prompt_tokens=100, completion_tokens=200, total_tokens=300)
|
||||||
|
mock_response.choices = [MagicMock(message=MagicMock(role="assistant", content='{"openai_stats": {"prompt_tokens": 100, "completion_tokens": 200, "total_tokens": 300}}'), finish_reason="stop", index=0)]
|
||||||
|
return mock_response
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def test_resume_file():
|
||||||
|
# Create a dummy resume file for testing
|
||||||
|
with open(TEST_RESUME_PATH, 'w') as f:
|
||||||
|
f.write("This is a test resume.")
|
||||||
|
yield TEST_RESUME_PATH
|
||||||
|
os.remove(TEST_RESUME_PATH)
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_mongo_collection():
|
||||||
|
# Mock MongoDB collection for testing
|
||||||
|
class MockMongoCollection:
|
||||||
|
def __init__(self):
|
||||||
|
self.inserted_data = None
|
||||||
|
|
||||||
|
def insert_one(self, data):
|
||||||
|
self.inserted_data = data
|
||||||
|
|
||||||
|
return MockMongoCollection()
|
||||||
|
|
||||||
|
# Unit Tests
|
||||||
|
def test_load_mockup_response():
|
||||||
|
# Create a mockup response file
|
||||||
|
with open(MOCKUP_FILE_PATH, 'w') as f:
|
||||||
|
json.dump(MOCKUP_RESPONSE_DATA, f)
|
||||||
|
|
||||||
|
response = load_mockup_response(MOCKUP_FILE_PATH)
|
||||||
|
assert response == MOCKUP_RESPONSE_DATA
|
||||||
|
os.remove(MOCKUP_FILE_PATH)
|
||||||
|
|
||||||
|
def test_load_mockup_response_file_not_found():
|
||||||
|
with pytest.raises(FileNotFoundError):
|
||||||
|
load_mockup_response("non_existent_file.json")
|
||||||
|
|
||||||
|
@patch("resume_analysis.openai.chat.completions.create")
|
||||||
|
def test_call_openai_api_success(mock_openai_chat_completions_create, mock_openai_response):
|
||||||
|
mock_openai_chat_completions_create.return_value = mock_openai_response
|
||||||
|
response = call_openai_api("test resume text", False)
|
||||||
|
assert response == mock_openai_response
|
||||||
|
|
||||||
|
@patch("resume_analysis.openai.chat.completions.create")
|
||||||
|
def test_call_openai_api_failure(mock_openai_chat_completions_create):
|
||||||
|
mock_openai_chat_completions_create.side_effect = Exception("API error")
|
||||||
|
response = call_openai_api("test resume text", False)
|
||||||
|
assert response is None
|
||||||
|
|
||||||
|
def test_call_openai_api_mockup_mode():
|
||||||
|
# Create a mockup response file
|
||||||
|
with open(MOCKUP_FILE_PATH, 'w') as f:
|
||||||
|
json.dump(MOCKUP_RESPONSE_DATA, f)
|
||||||
|
|
||||||
|
response = call_openai_api("test resume text", True)
|
||||||
|
assert response == MOCKUP_RESPONSE_DATA
|
||||||
|
os.remove(MOCKUP_FILE_PATH)
|
||||||
|
|
||||||
|
def test_insert_processing_data_success(mock_openai_response, mock_mongo_collection):
|
||||||
|
args = argparse.Namespace(file="test.pdf")
|
||||||
|
cost = insert_processing_data("test resume text", {}, mock_openai_response, args, "test_id", False, mock_mongo_collection)
|
||||||
|
assert mock_mongo_collection.inserted_data is not None
|
||||||
|
assert cost == 0
|
||||||
|
|
||||||
|
def test_insert_processing_data_mockup_mode(mock_mongo_collection):
|
||||||
|
args = argparse.Namespace(file="test.pdf")
|
||||||
|
cost = insert_processing_data("test resume text", {}, MOCKUP_RESPONSE_DATA, args, "test_id", True, mock_mongo_collection)
|
||||||
|
assert mock_mongo_collection.inserted_data is None
|
||||||
|
assert cost == 0
|
||||||
|
|
||||||
|
@patch("resume_analysis.get_mongo_collection")
|
||||||
|
def test_main_success(mock_get_mongo_collection, test_resume_file, mock_openai_response):
|
||||||
|
mock_get_mongo_collection.return_value.insert_one.return_value = None
|
||||||
|
with patch("resume_analysis.call_openai_api") as mock_call_openai_api:
|
||||||
|
mock_call_openai_api.return_value = mock_openai_response
|
||||||
|
with patch("resume_analysis.write_openai_response") as mock_write_openai_response:
|
||||||
|
sys.argv = ["resume_analysis.py", "-f", test_resume_file]
|
||||||
|
main()
|
||||||
|
assert mock_call_openai_api.called
|
||||||
|
assert mock_write_openai_response.called
|
||||||
|
|
||||||
|
@patch("resume_analysis.get_mongo_collection")
|
||||||
|
def test_main_mockup_mode(mock_get_mongo_collection, test_resume_file, mock_openai_response):
|
||||||
|
mock_get_mongo_collection.return_value.insert_one.return_value = None
|
||||||
|
with patch("resume_analysis.call_openai_api") as mock_call_openai_api:
|
||||||
|
mock_call_openai_api.return_value = mock_openai_response
|
||||||
|
with patch("resume_analysis.write_openai_response") as mock_write_openai_response:
|
||||||
|
sys.argv = ["resume_analysis.py", "-f", test_resume_file, "-m"]
|
||||||
|
main()
|
||||||
|
assert mock_call_openai_api.called
|
||||||
|
assert mock_write_openai_response.called
|
||||||
|
|
||||||
|
def test_main_file_not_found():
|
||||||
|
with pytest.raises(SystemExit) as pytest_wrapped_e:
|
||||||
|
sys.argv = ["resume_analysis.py", "-f", "non_existent_file.pdf"]
|
||||||
|
main()
|
||||||
|
assert pytest_wrapped_e.type == SystemExit
|
||||||
|
assert pytest_wrapped_e.value.code == 1
|
||||||
|
|
||||||
|
def test_get_mongo_collection():
|
||||||
|
# Test that the function returns a valid MongoDB collection object
|
||||||
|
collection = get_mongo_collection()
|
||||||
|
assert collection is not None
|
||||||
32
plan.md
Normal file
32
plan.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# Plan for Modifying resume_analysis.py
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
Modify the `my-app/utils/resume_analysis.py` script to save the extracted text from a PDF file and the OpenAI response to separate text files, with filenames derived from the original PDF's basename.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. **Examine `resume_analysis.py`:** Read the file to understand the existing PDF processing logic and how the OpenAI response is handled.
|
||||||
|
2. **Clarify Naming Convention:** Confirm the exact naming convention for the output files.
|
||||||
|
3. **Implement Changes:** Modify the script to:
|
||||||
|
* Extract the PDF's basename.
|
||||||
|
* Save the extracted text to a file named `basename._text.txt` in the same directory as the PDF.
|
||||||
|
* Save the OpenAI response to a file named `basename_openai.txt` in the same directory.
|
||||||
|
4. **Test:** Ensure that the changes work correctly for different PDF files and that the output files are created with the correct content and naming.
|
||||||
|
5. **Create a Plan File:** Create a markdown file with the plan.
|
||||||
|
6. **Switch Mode:** Switch to code mode to implement the changes.
|
||||||
|
|
||||||
|
## Mermaid Diagram
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
A[Start] --> B{Examine resume_analysis.py};
|
||||||
|
B --> C{Clarify Naming Convention};
|
||||||
|
C --> D{Modify Script};
|
||||||
|
D --> E{Extract PDF Basename};
|
||||||
|
E --> F{Save Extracted Text};
|
||||||
|
F --> G{Save OpenAI Response};
|
||||||
|
G --> H{Test Changes};
|
||||||
|
H --> I{Create Plan File};
|
||||||
|
I --> J{Switch to Code Mode};
|
||||||
|
J --> K[End];
|
||||||
Loading…
x
Reference in New Issue
Block a user