From 730a5e7c69dc95c88cdb2e7402a770b00f2bd344 Mon Sep 17 00:00:00 2001 From: Ireneusz Bachanowicz Date: Fri, 14 Mar 2025 00:59:09 +0100 Subject: [PATCH] some shit --- my-app/package.json | 4 +- .../resume_analysis.cpython-312.pyc | Bin 0 -> 12593 bytes my-app/utils/default_openai.txt | 87 +++++++++ ..._02509ff6-af4c-4aee-8b62-a26ec1a8397f.json | 19 ++ ..._504fcbf0-44bc-4ab3-a615-4af84754a895.json | 19 ++ ..._7701de18-d67c-4da9-ae28-44cb68f35612.json | 19 ++ ..._8a31e04d-62e9-4e4d-9f14-c64627bfe6fa.json | 19 ++ ..._ce8672ca-38cb-4b7c-9eb3-a16d3b1741b0.json | 19 ++ ..._d3e2cba2-5386-4662-ad5d-feb2d717b9ea.json | 19 ++ ..._d7a00605-9e98-4a56-837b-2c2d4df0c345.json | 19 ++ ..._f0b85bf6-cbcf-4c62-9d98-d9dc873927ba.json | 19 ++ my-app/utils/resume_analysis.py | 140 +++++++++----- ...sume_analysis.cpython-312-pytest-7.4.4.pyc | Bin 0 -> 16896 bytes my-app/utils/tests/test_resume_analysis.py | 174 ++++++++++++++++++ plan.md | 32 ++++ 15 files changed, 547 insertions(+), 42 deletions(-) create mode 100644 my-app/utils/__pycache__/resume_analysis.cpython-312.pyc create mode 100644 my-app/utils/default_openai.txt create mode 100644 my-app/utils/default_openai_response_02509ff6-af4c-4aee-8b62-a26ec1a8397f.json create mode 100644 my-app/utils/default_openai_response_504fcbf0-44bc-4ab3-a615-4af84754a895.json create mode 100644 my-app/utils/default_openai_response_7701de18-d67c-4da9-ae28-44cb68f35612.json create mode 100644 my-app/utils/default_openai_response_8a31e04d-62e9-4e4d-9f14-c64627bfe6fa.json create mode 100644 my-app/utils/default_openai_response_ce8672ca-38cb-4b7c-9eb3-a16d3b1741b0.json create mode 100644 my-app/utils/default_openai_response_d3e2cba2-5386-4662-ad5d-feb2d717b9ea.json create mode 100644 my-app/utils/default_openai_response_d7a00605-9e98-4a56-837b-2c2d4df0c345.json create mode 100644 my-app/utils/default_openai_response_f0b85bf6-cbcf-4c62-9d98-d9dc873927ba.json create mode 100644 my-app/utils/tests/__pycache__/test_resume_analysis.cpython-312-pytest-7.4.4.pyc create mode 100644 my-app/utils/tests/test_resume_analysis.py create mode 100644 plan.md 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 0000000000000000000000000000000000000000..c2cad200cc9b787ce41580dfe28320becf98ab47 GIT binary patch literal 12593 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 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 0000000000000000000000000000000000000000..f71877d44181c3ce2578641c7b4231563443119f GIT binary patch literal 16896 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 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