
    iv$                       d Z ddlmZ ddlZddlmc mZ ddl	Z	ddl
Z
ddlmZ e
j                  j                  d e ee      j#                         j$                  j$                               ddlmZ d dZdZdd	d
dZd Zd Zd Zd Zd Zd Zd Zd Zd Z d Z!d Z"d Z#d Z$d Z%d Z&d Z'd Z(d Z)d!dZ*e+dk(  r e
jX                   e*              yy)"u|  
Coverage for core.json_parsing.extract_json_from_response.

Driven by the 2026-04-29 LIVE incident: Claude returns JSON wrapped in
markdown fences (sometimes with leading/trailing prose). The previous
parser only stripped a fence at the very start/end; prose-prefixed or
prose-suffixed responses raised JSONDecodeError -> API_PARSE_ERROR ->
lost trades on 6C and MNQ.

Two assertion levels per case:
  1) extracted text matches expectation
  2) json.loads(extracted) round-trips when the inner JSON is valid

Negative cases verify graceful fallback (no crash) — the caller is
responsible for handling json.loads failures downstream.
    )annotationsN)Path)extract_json_from_responsec                     t        d|         y )Nz  ok  )print)labels    1/home/work/apex_v16/tests/test_ai_json_parsing.py_okr
      s    	F5'
    z/{"approved": true, "score": 0.82, "note": "OK"}Tg=
ףp=?OK)approvedscorenotec            
     p   dt          d} t        |       }|t         k(  }|s/t        j                  d|fd|t         f      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |       rt        j                  |       ndt        j                  |      dt	        j
                         v st        j                  t               rt        j                  t               nddz  }d	d
|iz  }t        t        j                  |            d x}}t        j                  }t        |       } ||      }|t        k(  }|st        j                  d|fd|t        f      dt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              nddz  }	dd|	iz  }
t        t        j                  |
            d x}x}x}}t        d       y )N```json

```==z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py5)sr   textPAYLOADpy0py1py3py5assert %(py7)spy7zj%(py8)s
{%(py8)s = %(py2)s
{%(py2)s = %(py0)s.loads
}(%(py6)s
{%(py6)s = %(py3)s(%(py4)s)
})
} == %(py10)sjsonPARSEDr   py2r   py4py6py8py10assert %(py12)spy12zfence ```jsonr   r   
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanationr    loadsr!   r
   r   @py_assert2@py_assert4@py_format6@py_format8@py_assert1@py_assert5@py_assert7@py_assert9@py_format11@py_format13s              r	   test_fence_with_json_tagr?   )   s   wiu%D%d+6+w6666+w666666%666%666666d666d666+666666w666w6666666::A06A:67A76AAAA76AAAAAA4AAA4AAA:AAAAAA0AAA0AAAAAAAAAAAA6AAA7AAAAAA6AAA6AAAAAAAr   c            
     p   dt          d} t        |       }|t         k(  }|s/t        j                  d|fd|t         f      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |       rt        j                  |       ndt        j                  |      dt	        j
                         v st        j                  t               rt        j                  t               nddz  }d	d
|iz  }t        t        j                  |            d x}}t        j                  }t        |       } ||      }|t        k(  }|st        j                  d|fd|t        f      dt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              nddz  }	dd|	iz  }
t        t        j                  |
            d x}x}x}}t        d       y )Nz```
r   r   r   r   r   r   r   r   r   r   r    r!   r"   r(   r)   zfence ``` (no language)r*   r4   s              r	   test_fence_no_language_tagrA   0   s   7)5!D%d+6+w6666+w666666%666%666666d666d666+666666w666w6666666::A06A:67A76AAAA76AAAAAA4AAA4AAA:AAAAAA0AAA0AAAAAAAAAAAA6AAA7AAAAAA6AAA6AAAAAAA!"r   c            
     p   dt          d} t        |       }|t         k(  }|s/t        j                  d|fd|t         f      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |       rt        j                  |       ndt        j                  |      dt	        j
                         v st        j                  t               rt        j                  t               nddz  }d	d
|iz  }t        t        j                  |            d x}}t        j                  }t        |       } ||      }|t        k(  }|st        j                  d|fd|t        f      dt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              nddz  }	dd|	iz  }
t        t        j                  |
            d x}x}x}}t        d       y )Nz```JSON
r   r   r   r   r   r   r   r   r   r   r    r!   r"   r(   r)   z fence ```JSON (case-insensitive)r*   r4   s              r	   test_fence_uppercase_json_tagrC   7   s   wiu%D%d+6+w6666+w666666%666%666666d666d666+666666w666w6666666::A06A:67A76AAAA76AAAAAA4AAA4AAA:AAAAAA0AAA0AAAAAAAAAAAA6AAA7AAAAAA6AAA6AAAAAAA*+r   c            
     h   t         } t        |       }|t         k(  }|s/t        j                  d|fd|t         f      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |       rt        j                  |       ndt        j                  |      dt	        j
                         v st        j                  t               rt        j                  t               nddz  }dd|iz  }t        t        j                  |            d x}}t        j                  }t        |       } ||      }|t        k(  }|st        j                  d|fd	|t        f      d
t	        j
                         v st        j                  t              rt        j                  t              nd
t        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              nddz  }	dd|	iz  }
t        t        j                  |
            d x}x}x}}t        d       y )Nr   r   r   r   r   r   r   r   r   r    r!   r"   r(   r)   zraw JSON, no fencer*   r4   s              r	   test_no_fence_raw_jsonrE   >   s   D%d+6+w6666+w666666%666%666666d666d666+666666w666w6666666::A06A:67A76AAAA76AAAAAA4AAA4AAA:AAAAAA0AAA0AAAAAAAAAAAA6AAA7AAAAAA6AAA6AAAAAAAr   c            
     p   dt          d} t        |       }|t         k(  }|s/t        j                  d|fd|t         f      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |       rt        j                  |       ndt        j                  |      dt	        j
                         v st        j                  t               rt        j                  t               nddz  }d	d
|iz  }t        t        j                  |            d x}}t        j                  }t        |       } ||      }|t        k(  }|st        j                  d|fd|t        f      dt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              nddz  }	dd|	iz  }
t        t        j                  |
            d x}x}x}}t        d       y )Nz   
  ```json
z
```   
r   r   r   r   r   r   r   r   r   r    r!   r"   r(   r)   z#fence + leading/trailing whitespacer*   r4   s              r	   "test_fence_with_leading_whitespacerG   E   s   gYj1D%d+6+w6666+w666666%666%666666d666d666+666666w666w6666666::A06A:67A76AAAA76AAAAAA4AAA4AAA:AAAAAA0AAA0AAAAAAAAAAAA6AAA7AAAAAA6AAA6AAAAAAA-.r   c            
     p   dt          d} t        |       }|t         k(  }|s/t        j                  d|fd|t         f      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |       rt        j                  |       ndt        j                  |      dt	        j
                         v st        j                  t               rt        j                  t               nddz  }d	d
|iz  }t        t        j                  |            d x}}t        j                  }t        |       } ||      }|t        k(  }|st        j                  d|fd|t        f      dt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              nddz  }	dd|	iz  }
t        t        j                  |
            d x}x}x}}t        d       y )NzEcco la risposta:
```json
r   r   r   r   r   r   r   r   r   r   r    r!   r"   r(   r)   zfence + prose prefixr*   r4   s              r	   test_fence_with_prose_prefixrI   L   s   )'%8D%d+6+w6666+w666666%666%666666d666d666+666666w666w6666666::A06A:67A76AAAA76AAAAAA4AAA4AAA:AAAAAA0AAA0AAAAAAAAAAAA6AAA7AAAAAA6AAA6AAAAAAAr   c            
     p   dt          d} t        |       }|t         k(  }|s/t        j                  d|fd|t         f      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |       rt        j                  |       ndt        j                  |      dt	        j
                         v st        j                  t               rt        j                  t               nddz  }d	d
|iz  }t        t        j                  |            d x}}t        j                  }t        |       } ||      }|t        k(  }|st        j                  d|fd|t        f      dt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              nddz  }	dd|	iz  }
t        t        j                  |
            d x}x}x}}t        d       y )Nr   z
```

Note: this is the answer.r   r   r   r   r   r   r   r   r   r    r!   r"   r(   r)   zfence + prose suffixr*   r4   s              r	   test_fence_with_prose_suffixrK   S   s   wiABD%d+6+w6666+w666666%666%666666d666d666+666666w666w6666666::A06A:67A76AAAA76AAAAAA4AAA4AAA:AAAAAA0AAA0AAAAAAAAAAAA6AAA7AAAAAA6AAA6AAAAAAAr   c            
     p   dt          d} t        |       }|t         k(  }|s/t        j                  d|fd|t         f      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |       rt        j                  |       ndt        j                  |      dt	        j
                         v st        j                  t               rt        j                  t               nddz  }d	d
|iz  }t        t        j                  |            d x}}t        j                  }t        |       } ||      }|t        k(  }|st        j                  d|fd|t        f      dt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              nddz  }	dd|	iz  }
t        t        j                  |
            d x}x}x}}t        d       y )Nz#Ecco il mio output finale:
```json
u6   
```

Ho deciso così perché RSI è in zona pullback.r   r   r   r   r   r   r   r   r   r    r!   r"   r(   r)   zfence + prose both sidesr*   r4   s              r	    test_fence_with_prose_both_sidesrM   Z   s   9 ;	< 	
 &d+6+w6666+w666666%666%666666d666d666+666666w666w6666666::A06A:67A76AAAA76AAAAAA4AAA4AAA:AAAAAA0AAA0AAAAAAAAAAAA6AAA7AAAAAA6AAA6AAAAAAA"#r   c            
     p   dt          d} t        |       }|t         k(  }|s/t        j                  d|fd|t         f      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |       rt        j                  |       ndt        j                  |      dt	        j
                         v st        j                  t               rt        j                  t               nddz  }d	d
|iz  }t        t        j                  |            d x}}t        j                  }t        |       } ||      }|t        k(  }|st        j                  d|fd|t        f      dt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              nddz  }	dd|	iz  }
t        t        j                  |
            d x}x}x}}t        d       y )Nz```json z ```r   r   r   r   r   r   r   r   r   r    r!   r"   r(   r)   zfence single-liner*   r4   s              r	   test_fence_single_linerO   e   s   gYd#D%d+6+w6666+w666666%666%666666d666d666+666666w666w6666666::A06A:67A76AAAA76AAAAAA4AAA4AAA:AAAAAA0AAA0AAAAAAAAAAAA6AAA7AAAAAA6AAA6AAAAAAAr   c                    d} t        |       }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |       t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x} x}x}}t        d       y )	N r   z0%(py4)s
{%(py4)s = %(py0)s(%(py2)s)
} == %(py7)sr   r   r#   r$   r   assert %(py9)spy9zempty input -> empty output
r   r+   r,   r-   r.   r/   r0   r1   r2   r
   r9   @py_assert3@py_assert6r:   r8   @py_format10s         r	   test_empty_string_returns_emptyr[   p   s    &(/%b)/R/)R////)R//////%///%///b///)///R///////%&r   c                    d} t        |       }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |       t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x} x}x}}t        d	       y )
Nz
   

  	  rQ   r   rR   r   rS   rT   rU   z%whitespace-only input -> empty outputrV   rW   s         r	   "test_whitespace_only_returns_emptyr]   u   s    &5<%o6<"<6"<<<<6"<<<<<<%<<<%<<<o<<<6<<<"<<<<<<</0r   c                    d } t        |       }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j
                  t               rt        j                  t               ndt        j                  |       t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d x} x}x}}t        d       y )	NrQ   r   rR   r   rS   rT   rU   z&None input -> empty output (defensive)rV   rW   s         r	   test_none_input_returns_emptyr_   z   s    &*1%d+1r1+r1111+r111111%111%111d111+111r111111101r   c                 F   d} t        |       }|| k(  }|st        j                  d|fd|| f      dt        j                         v st        j
                  |      rt        j                  |      nddt        j                         v st        j
                  |       rt        j                  |       nddz  }dd|iz  }t        t        j                  |            d	}	 t        j                  |       t        d
      # t        j                  $ r Y nw xY wt        d       y	)z5No fence, no braces -> last-resort raw stripped text.z"Mi dispiace, non posso rispondere.r   z%(py0)s == %(py2)soutr   r   r#   assert %(py4)sr$   Nz,expected JSONDecodeError on prose-only inputz6prose only, no JSON -> stripped raw, json.loads raisesr   r+   r,   r-   r.   r/   r0   r1   r2   r    r3   JSONDecodeErrorr
   )r   rb   r9   @py_format3@py_format5s        r	   (test_prose_only_no_json_returns_strippedri      s    /D
$T
*C$;3$33$$M

3 KLL   @As   C> >DDc                 $   d} d|  d}t        |      }|| k(  }|s#t        j                  d|fd|| f      dt        j                         v st        j
                  t               rt        j                  t               nddt        j                         v st        j
                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j
                  |       rt        j                  |       ndd	z  }d
d|iz  }t        t        j                  |            dx}}	 t        j                  t        |             t        d      # t        j                  $ r Y nw xY wt        d       y)z>Trailing comma -> extraction succeeds, json.loads is the gate.z"{"approved": true, "score": 0.82,}r   r   r   r   r   r   badr   r   r   Nz/expected JSONDecodeError on trailing-comma JSONz;malformed JSON inside fence -> extracted, json.loads raisesre   )rk   r   r5   r6   r7   r8   s         r	   9test_malformed_json_inside_fence_extracts_but_loads_failsrl      s   
.Cse5!D%d+2+s2222+s222222%222%222222d222d222+222222s222s2222222P

-d34 NOO   EFs   E- -FFc                    dt          d} t        |       }|t         k(  }|st        j                  d|fd|t         f      dt	        j
                         v st        j                  |      rt        j                  |      nddt	        j
                         v st        j                  t               rt        j                  t               nddz  }dd	|iz  }t        t        j                  |            d
}t        j                  } ||      }|t        k(  }|sCt        j                  d|fd|t        f      dt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              nddz  }dd|iz  }t        t        j                  |            d
x}x}}t        d       y
)z
    Truncated response (no closing ```): fence regex misses -> brace
    fallback recovers the object. Mirrors a max_tokens cutoff scenario.
    r   z&
  some trailing prose without a fencer   ra   rb   r   rc   rd   r$   NzK%(py5)s
{%(py5)s = %(py2)s
{%(py2)s = %(py0)s.loads
}(%(py3)s)
} == %(py7)sr    r!   r   r#   r   r   r   rT   rU   z5missing closing fence -> brace fallback recovers JSONr*   	r   rb   r9   rg   rh   r6   rY   r8   rZ   s	            r	   )test_missing_closing_fence_brace_fallbackrq      s<   
 wiFGD
$T
*C'>3'33''::$:c?$?f$$$$?f$$$$$$4$$$4$$$:$$$$$$c$$$c$$$?$$$$$$f$$$f$$$$$$$?@r   c                    dt          d} t        |       }|t         k(  }|st        j                  d|fd|t         f      dt	        j
                         v st        j                  |      rt        j                  |      nddt	        j
                         v st        j                  t               rt        j                  t               nddz  }dd	|iz  }t        t        j                  |            d
}t        j                  } ||      }|t        k(  }|sCt        j                  d|fd|t        f      dt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              nddz  }dd|iz  }t        t        j                  |            d
x}x}}t        d       y
)z'No fence, prose around the JSON object.zSure thing! z done.r   ra   rb   r   rc   rd   r$   Nrn   r    r!   ro   rT   rU   z(no fence, prose around -> brace fallbackr*   rp   s	            r	   !test_brace_fallback_when_no_fencers      s9   '&)D
$T
*C'>3'33''::$:c?$?f$$$$?f$$$$$$4$$$4$$$:$$$$$$c$$$c$$$?$$$$$$f$$$f$$$$$$$23r   c                    d} t        |       }| j                  } |       }||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      nddt	        j
                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}x}}	 t        j                  |       t        d
      # t        j                  $ r Y nw xY wt        d       y	)uW  
    2026-04-29 incident #2 (TF entry on 6A): AI returned a long
    markdown chain-of-thought response with zero JSON anywhere — the
    prompt was missing the "JSON only" guard that MR/bias both had.
    Parser must NOT crash. Caller's json.loads raises JSONDecodeError;
    Brain code maps that to API_PARSE_ERROR (not RuntimeError).
    u  # ANALISI 6A — M5 SELL PULLBACK

═══ STEP 1: QUALITÀ DELLO SCONTO ═══
**RSI M5 = 45.5** (zona TF: 42–58)
- Distanza da 58 (resistenza): +12.5 punti = **SCONTO PROFONDO**
- Posizionamento: centro-basso

═══ STEP 2: TIMING PULLBACK ═══
RSI in decay (46.7 → 45.5): coerente.
Bouncing=False (favorevole SELL).

═══ STEP 3: CONTESTO MACRO ═══
Trend H1 BEARISH, divergence assente.
r   )zD%(py0)s == %(py6)s
{%(py6)s = %(py4)s
{%(py4)s = %(py2)s.strip
}()
}rb   r   )r   r#   r$   r%   zassert %(py8)sr&   Nz/expected JSONDecodeError on prose-only responsez;markdown CoT, zero JSON -> graceful fail, json.loads raises)r   stripr+   r,   r-   r.   r/   r0   r1   r2   r    r3   rf   r
   )r   rb   rX   r:   r9   @py_format7@py_format9s          r	   &test_markdown_chain_of_thought_no_jsonrx      s    	2 	  %T
*C***,3,3,33$$*,P

3 NOO   EFs   D= =EEc                 \   d} t        |       }t        j                  |      }|d   }d}||u }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }dd|iz  }t        t        j                  |            d	x}x}}|d
   }d}||k(  }|slt        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }dd|iz  }t        t        j                  |            d	x}x}}t        d       y	)u   
    Reconstructed shape from the 2026-04-29 LIVE incident on 6C.
    response_preview shows ```json
{... — full payload likely had
    trailing prose or a stray fence. Verify the new parser is robust
    to a plausible reconstruction.
    u   ```json
{
  "step_1_qualita_sconto": "OTTIMO: RSI 46.7 in zona pullback",
  "step_2_timing_pullback": "OTTIMO: Bouncing=True",
  "approved": true,
  "confidence": 0.78
}
```

Spiegazione: la setup è ottima per SELL pro-trend.r   T)is)z%(py1)s is %(py4)s)r   r$   zassert %(py6)sr%   N
confidenceg(\?r   )z%(py1)s == %(py4)sz0real-incident shape (6C-style) parses end-to-end)	r   r    r3   r+   r,   r0   r1   r2   r
   )r   rb   parsed@py_assert0rX   r5   rh   rv   s           r	   test_real_incident_shape_6cr~      s    		> 	 %T
*CZZ_F*%%%%%%%%%%%%%%%%%%%,'4'4''''4''''''4''''''':;r   c                    t        d       t                t                t                t	                t                t                t                t                t                t                t                t                t                t                t                t!                t#                t%                t        d       y)Nztest_ai_json_parsing.pyzALL 18 TESTS PASSEDr   )r   r?   rA   rC   rE   rG   rI   rK   rM   rO   r[   r]   r_   ri   rl   rq   rs   rx   r~    r   r	   mainr      s    	
#$ !#&( " "$&#%&(!#,.=?-/%'*,!	
 r   __main__)r   strreturnNone)r   int)-__doc__
__future__r   builtinsr-   _pytest.assertion.rewrite	assertionrewriter+   r    syspathlibr   pathinsertr   __file__resolveparentcore.json_parsingr   r
   r   r!   r?   rA   rC   rE   rG   rI   rK   rM   rO   r[   r]   r_   ri   rl   rq   rs   rx   r~   r   __name__exitr   r   r	   <module>r      s   " #    
  3tH~--/66==> ? 8 <T4	8#,/  $'
1
2BG
A4"GJ<<0 zCHHTV r   