diff --git a/my-app/package.json b/my-app/package.json index 76e01cd..d6647f4 100644 --- a/my-app/package.json +++ b/my-app/package.json @@ -7,7 +7,9 @@ "build": "next build --no-lint", "start": "next start", "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": { "@ai-sdk/google": "^1.1.17", diff --git a/my-app/utils/__pycache__/resume_analysis.cpython-312.pyc b/my-app/utils/__pycache__/resume_analysis.cpython-312.pyc new file mode 100644 index 0000000..c2cad20 Binary files /dev/null and b/my-app/utils/__pycache__/resume_analysis.cpython-312.pyc differ diff --git a/my-app/utils/default_openai.txt b/my-app/utils/default_openai.txt new file mode 100644 index 0000000..04e8869 --- /dev/null +++ b/my-app/utils/default_openai.txt @@ -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 + } +} +``` \ No newline at end of file diff --git a/my-app/utils/default_openai_response_02509ff6-af4c-4aee-8b62-a26ec1a8397f.json b/my-app/utils/default_openai_response_02509ff6-af4c-4aee-8b62-a26ec1a8397f.json new file mode 100644 index 0000000..315276c --- /dev/null +++ b/my-app/utils/default_openai_response_02509ff6-af4c-4aee-8b62-a26ec1a8397f.json @@ -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" +} \ No newline at end of file diff --git a/my-app/utils/default_openai_response_504fcbf0-44bc-4ab3-a615-4af84754a895.json b/my-app/utils/default_openai_response_504fcbf0-44bc-4ab3-a615-4af84754a895.json new file mode 100644 index 0000000..4faae1e --- /dev/null +++ b/my-app/utils/default_openai_response_504fcbf0-44bc-4ab3-a615-4af84754a895.json @@ -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" +} \ No newline at end of file diff --git a/my-app/utils/default_openai_response_7701de18-d67c-4da9-ae28-44cb68f35612.json b/my-app/utils/default_openai_response_7701de18-d67c-4da9-ae28-44cb68f35612.json new file mode 100644 index 0000000..a84e06d --- /dev/null +++ b/my-app/utils/default_openai_response_7701de18-d67c-4da9-ae28-44cb68f35612.json @@ -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" +} \ No newline at end of file diff --git a/my-app/utils/default_openai_response_8a31e04d-62e9-4e4d-9f14-c64627bfe6fa.json b/my-app/utils/default_openai_response_8a31e04d-62e9-4e4d-9f14-c64627bfe6fa.json new file mode 100644 index 0000000..96308e7 --- /dev/null +++ b/my-app/utils/default_openai_response_8a31e04d-62e9-4e4d-9f14-c64627bfe6fa.json @@ -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" +} \ No newline at end of file diff --git a/my-app/utils/default_openai_response_ce8672ca-38cb-4b7c-9eb3-a16d3b1741b0.json b/my-app/utils/default_openai_response_ce8672ca-38cb-4b7c-9eb3-a16d3b1741b0.json new file mode 100644 index 0000000..695c6b5 --- /dev/null +++ b/my-app/utils/default_openai_response_ce8672ca-38cb-4b7c-9eb3-a16d3b1741b0.json @@ -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" +} \ No newline at end of file diff --git a/my-app/utils/default_openai_response_d3e2cba2-5386-4662-ad5d-feb2d717b9ea.json b/my-app/utils/default_openai_response_d3e2cba2-5386-4662-ad5d-feb2d717b9ea.json new file mode 100644 index 0000000..a662031 --- /dev/null +++ b/my-app/utils/default_openai_response_d3e2cba2-5386-4662-ad5d-feb2d717b9ea.json @@ -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" +} \ No newline at end of file diff --git a/my-app/utils/default_openai_response_d7a00605-9e98-4a56-837b-2c2d4df0c345.json b/my-app/utils/default_openai_response_d7a00605-9e98-4a56-837b-2c2d4df0c345.json new file mode 100644 index 0000000..ec2fa40 --- /dev/null +++ b/my-app/utils/default_openai_response_d7a00605-9e98-4a56-837b-2c2d4df0c345.json @@ -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" +} \ No newline at end of file diff --git a/my-app/utils/default_openai_response_f0b85bf6-cbcf-4c62-9d98-d9dc873927ba.json b/my-app/utils/default_openai_response_f0b85bf6-cbcf-4c62-9d98-d9dc873927ba.json new file mode 100644 index 0000000..df10872 --- /dev/null +++ b/my-app/utils/default_openai_response_f0b85bf6-cbcf-4c62-9d98-d9dc873927ba.json @@ -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" +} \ No newline at end of file diff --git a/my-app/utils/resume_analysis.py b/my-app/utils/resume_analysis.py index 0b4f0ce..2c9900a 100755 --- a/my-app/utils/resume_analysis.py +++ b/my-app/utils/resume_analysis.py @@ -59,10 +59,11 @@ Required Environment Variables: - MONGODB_URI: MongoDB connection string (optional for mockup mode)""", usage="resume_analysis.py [-h] [-f FILE] [-m]", epilog="""Examples: - Analyze a resume: resume_analysis.py -f my_resume.pdf - Test with mockup data: resume_analysis.py -f test.pdf -m""" + Analyze a resume: resume_analysis.py -f my_resume.txt + 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') # If no arguments provided, show help and exit @@ -79,21 +80,48 @@ Required Environment Variables: if use_mockup: resume_text = "Mockup resume text" else: - if not os.path.exists(args.file): - logger.error(f"File not found: {args.file}") + if args.pdf: + if not os.path.exists(args.pdf): + logger.error(f"PDF file not found: {args.pdf}") + sys.exit(1) + + start_file_read_time = time.time() + try: + resume_text = extract_text(args.pdf) + except Exception as e: + 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) - start_file_read_time = time.time() - with open(args.file, 'r') 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") - # Call the OpenAI API with the resume text start_time = time.time() - response = call_openai_api(resume_text, use_mockup) - openai_api_time = time.time() - start_time - logger.debug(f"OpenAI API call time: {openai_api_time:.2f} seconds") + try: + response = call_openai_api(resume_text, use_mockup) + 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 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}") with open(mockup_file_path, "r") as 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 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.") try: 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: 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) 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.""" if use_mockup: logger.debug("Using mockup response; no OpenAI message to write.") 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 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()) 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: - 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": [ { "message": { @@ -162,46 +205,64 @@ def write_openai_response(response: Any, use_mockup: bool, input_file_path: str "index": choice.index } for choice in response.choices ], - "openai_stats": { + "usage": { "prompt_tokens": response.usage.prompt_tokens, "completion_tokens": response.usage.completion_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 } 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}") + except IOError as e: logger.error(f"Failed to write OpenAI response to file: {e}") else: logger.warning("No choices in OpenAI response to extract message from.") 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.""" logger.debug("Inserting processing data into MongoDB.") + cost = 0.0 # Initialize cost to 0.0 if not use_mockup: if response and response.choices: message_content = response.choices[0].message.content + openai_stats = {} # Initialize openai_stats 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", {}) - cost = openai_stats.get("cost", 0) - except json.JSONDecodeError: - logger.error("Failed to decode JSON from message content for openai_stats.") - openai_stats = {} - cost = 0 + cost = openai_stats.get("cost", 0.0) + except json.JSONDecodeError as e: + logger.error(f"JSONDecodeError in message_content: {e}", exc_info=True) + cost = 0.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: logger.error("Invalid response format or missing usage data.") input_tokens = output_tokens = total_tokens = 0 - cost = 0 + cost = 0.0 openai_stats = {} 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_completion_tokens": output_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 } @@ -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) else: logger.debug("Using mockup; skipping MongoDB insertion.") - return 0 # Return 0 for mockup mode + return cost # Return 0 for mockup mode if __name__ == "__main__": main() diff --git a/my-app/utils/tests/__pycache__/test_resume_analysis.cpython-312-pytest-7.4.4.pyc b/my-app/utils/tests/__pycache__/test_resume_analysis.cpython-312-pytest-7.4.4.pyc new file mode 100644 index 0000000..f71877d Binary files /dev/null and b/my-app/utils/tests/__pycache__/test_resume_analysis.cpython-312-pytest-7.4.4.pyc differ diff --git a/my-app/utils/tests/test_resume_analysis.py b/my-app/utils/tests/test_resume_analysis.py new file mode 100644 index 0000000..f6df1f5 --- /dev/null +++ b/my-app/utils/tests/test_resume_analysis.py @@ -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 \ No newline at end of file diff --git a/plan.md b/plan.md new file mode 100644 index 0000000..650a53f --- /dev/null +++ b/plan.md @@ -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]; \ No newline at end of file