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)