# problem_understanding.py - V231.14 # Analyzes problem structure BEFORE solving """ Problem Understanding Module Ensures we understand WHAT is being asked before attempting to solve. """ import json import re from utils.safe_json import safe_extract_json # V1.0: Canonical extractor def get_problem_understanding_prompt(ocr_text: str, data_anchor: dict) -> str: """ Prompt LLM to analyze problem structure. Returns JSON with: - problem_type - sub_questions (all parts א, ב, ג, etc.) - solving_order - dependencies """ return f""" ANALYZE this math problem structure. DO NOT SOLVE - only understand what is being asked. Problem Text: {ocr_text} Extracted Data: {json.dumps(data_anchor, ensure_ascii=False, indent=2)} Your task: Identify ALL parts of this problem and create a solving plan. Return JSON: {{ "problem_type": "CIRCLE_EQUATION | LINE_EQUATION | GEOMETRIC_LOCUS | DERIVATIVE_QUOTIENT | etc.", "main_question": "Brief description of main question", "sub_questions": [ {{ "id": "א", "question": "Full text of sub-question א", "requires": ["center", "radius"], "specific_values": ["m=2"], "expected_output": "equation | number | point | etc.", "topic": "CIRCLE_EQUATION" }}, {{ "id": "ב", "question": "Full text of sub-question ב", "requires": ["equation_from_א", "point"], "specific_values": ["a=1"], "expected_output": "line_equation", "topic": "LINE_TANGENT" }} ], "solving_order": ["א", "ב", "ג"], "dependencies": {{ "ב": ["א"], "ג": ["א"] }} }} CRITICAL RULES: 1. Include ALL sub-questions (א, ב, ג, ד, etc.) 2. **CHRONOLOGICAL DATA ISOLATION (V310.0 - CRITICAL):** If a specific value (e.g., a=1, m=2) is mentioned ONLY in a specific sub-question, you MUST include it in the `specific_values` array for THAT sub-question only. NEVER put it in the top-level anchor if it's not global. This prevents data leakage (e.g., using a=1 from section ב' to solve section א'). 3. **EXCEPTION:** If the problem asks for a **Geometric Locus (מקום גיאומטרי)**: - This is a SINGLE QUESTION (even if it looks long). - Set `problem_type` = "GEOMETRIC_LOCUS". - Create ONLY ONE sub-question (id="א") containing the entire text. 4. Identify dependencies (ב needs א's result) 5. Determine topic for EACH sub-question 6. DO NOT solve - only analyze structure Return ONLY valid JSON. """ def parse_understanding(response_text: str) -> dict: """Parse LLM understanding response using canonical safe_extract_json.""" result = safe_extract_json(response_text, caller="PROBLEM_UNDERSTANDING") # If parsing failed, return a minimal fallback so the pipeline continues if isinstance(result, dict) and result.get("logic_error"): return { "problem_type": "UNKNOWN", "sub_questions": [], "solving_order": [], "dependencies": {} } return result def validate_understanding(understanding: dict) -> bool: """Validate understanding structure.""" required_keys = ["problem_type", "sub_questions", "solving_order"] if not all(key in understanding for key in required_keys): return False if not isinstance(understanding["sub_questions"], list): return False if len(understanding["sub_questions"]) == 0: return False # Validate each sub-question for sq in understanding["sub_questions"]: if not all(key in sq for key in ["id", "question", "topic"]): return False return True def enforce_locus_rule(understanding: dict, ocr_text: str) -> dict: """ V260.2: Hard Rule - If 'מקום גיאומטרי' exists, FORCE Locus type. """ if any(k in ocr_text for k in ["מקום גיאומטרי", "Locus", "מצא את המקום", "המקום הגיאומטרי"]): print("🛡️ [BIT-LOG] Hard Logic: Detected 'Geometric Locus' - Forcing Single Question structure.") return { "problem_type": "GEOMETRIC_LOCUS", "main_question": understanding.get("main_question", "Find the Locus"), "sub_questions": [{ "id": "א", "question": ocr_text, # Give the WHOLE text to the single sub-question "requires": [], "expected_output": "equation", "topic": "GEOMETRIC_LOCUS" }], "solving_order": ["א"], "dependencies": {} } return understanding # ==================== USAGE EXAMPLE ==================== if __name__ == "__main__": # Test understanding prompt ocr = """ מעגל עם משוואה x² + y² = 12 א. מצא את משוואת המעגל ב. מצא משיק למעגל בנקודה A ג. מצא את שטח המעגל """ data = { "function_equations": ["x^2 + y^2 = 12"], "points": ["A"], "specific_values": [], "constraints": [] } prompt = get_problem_understanding_prompt(ocr, data) print(prompt)