File size: 5,147 Bytes
9d29c62 7012e88 9d29c62 7012e88 9d29c62 5e09541 7012e88 9d29c62 7012e88 9d29c62 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | # 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)
|