
    {ih                        d Z ddlZddlmc mZ ddlZej                  j                  dd       ddl
Z
ddlmZmZmZmZmZmZmZ e
j&                  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!e"dk(  r e       Z#dddidfdddidfdddidfddd d!dfd"dd#d$dfd%dd&d!dfgZ$dZ%e$D ][  \  Z&Z'Z(e#jS                  e'      Z*e*jV                  e(k(  Z,e,rd'nd(Z-e,re%d)z  Z% e.d*e- d+e& d,e*jV                   d-e*j^                          ]  e.d.e% d/ e0e$       d0       e% e0e$      k(  r	 e.d1       y ejb                  d)       yy)2u  
Tests for Story 4.02 (Track B): TierClassifier — Heuristic Task Classifier

Black Box tests (BB): verify public contract from the outside
White Box tests (WB): verify internal priority ordering and structural guarantees

Story: 4.02
File under test: core/routing/tier_classifier.py
    Nz/mnt/e/genesis-system)TierClassifierRoutingDecisionT0_TYPEST1_TYPEST0_PAYLOAD_KEYST1_PAYLOAD_KEYSTIER_MODELSc                      t               S )N)r        6/mnt/e/genesis-system/tests/track_b/test_story_4_02.py
classifierr      s    r   c                 J   | j                  ddi      }|j                  }d}||k(  }|st        j                  d|fd||f      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}}|j                  }d}||k(  }|st        j                  d|fd||f      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}}y
)u8   BB1: type='health_check' → T0, model='python_function'typehealth_checkT0==z,%(py2)s
{%(py2)s = %(py0)s.tier
} == %(py5)sdecisionpy0py2py5assert %(py7)spy7Npython_functionz-%(py2)s
{%(py2)s = %(py0)s.model
} == %(py5)sclassifytier
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanationmodelr   r   @py_assert1@py_assert4@py_assert3@py_format6@py_format8s          r   "test_bb1_health_check_routes_to_t0r1   #   s    ""FN#;<H== D =D    =D      8   8   =   D       >>...>.....>.......8...8...>...........r   c                 J   | j                  ddi      }|j                  }d}||k(  }|st        j                  d|fd||f      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}}|j                  }d}||k(  }|st        j                  d|fd||f      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}}y
)u5   BB2: type='lead_qualify' → T1, model='gemini-flash'r   lead_qualifyT1r   r   r   r   r   r   Ngemini-flashr   r   r+   s          r   "test_bb2_lead_qualify_routes_to_t1r6   *   s    ""FN#;<H== D =D    =D      8   8   =   D       >>+^+>^++++>^++++++8+++8+++>+++^+++++++r   c                 J   | j                  ddi      }|j                  }d}||k(  }|st        j                  d|fd||f      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}}|j                  }d}||k(  }|st        j                  d|fd||f      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}}y
)u:   BB3: type='novel_decision' → T2, model='claude-opus-4-6'r   novel_decisionT2r   r   r   r   r   r   Nclaude-opus-4-6r   r   r+   s          r    test_bb3_novel_type_routes_to_t2r;   1   s    ""F,<#=>H== D =D    =D      8   8   =   D       >>...>.....>.......8...8...>...........r   c                 L   | j                  ddd      }|j                  }d}||k(  }|st        j                  d|fd||f      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}}|j                  }d}||k(  }|st        j                  d|fd||f      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}}y)u?   BB4: payload has 'crud_operation' key → T0 regardless of typeanythingINSERTr   crud_operationr   r   r   r   r   r   r   Nr   r   r   r+   s          r   0test_bb4_crud_operation_payload_key_routes_to_t0rA   8   s    ""J(#STH== D =D    =D      8   8   =   D       >>...>.....>.......8...8...>...........r   c                 L   | j                  ddd      }|j                  }d}||k(  }|st        j                  d|fd||f      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}}|j                  }d}||k(  }|st        j                  d|fd||f      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}}y)u9   BB5: payload has 'template' key → T1 regardless of typer=   welcome_emailr   templater4   r   r   r   r   r   r   Nr5   r   r   r+   s          r   *test_bb5_template_payload_key_routes_to_t1rF   ?   s    ""JO#TUH== D =D    =D      8   8   =   D       >>+^+>^++++>^++++++8+++8+++>+++^+++++++r   c                    | j                  ddd      }|j                  }d}||k(  }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        j                  d	|j                   d
      dz   d|iz  }t        t        j                  |            dx}x}}y)uN   WB1: T0 check runs before T1 — payload with a T0 key AND T1 type → T0 winsr3   GETr?   r   r   r   r   r   z$Expected T0 (payload key wins), got z.. T0 payload check must precede T1 type check.
>assert %(py7)sr   N)r    r!   r"   r#   r$   r%   r&   r'   _format_assertmsgr(   r)   r+   s          r   /test_wb1_t0_wins_over_t1_when_both_keys_presentrK   J   s    
 ""$ H == D =D   =D                  !    /x}}o >7 	7     r   c           	         ddidfddidfddidfdd	d
dfddddffD ]  \  }}| j                  |      }|j                  }|st        j                  d|       dz   dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }t        t        j                  |            d}|j                  }||v }|st        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                  |      dz  }t        j                  d|j                   d| d|       dz   d|iz  }t        t        j                  |            dx}} y)zBWB2: Rationale string is non-empty and identifies which rule firedr   	cache_getr   email_draftr   unknown_xyz
defaultingxDELETEr?   r@   trD   rE   z(Rationale must not be empty for payload z/
>assert %(py2)s
{%(py2)s = %(py0)s.rationale
}r   )r   r   Nin)z1%(py0)s in %(py4)s
{%(py4)s = %(py2)s.rationale
}expected_fragment)r   r   py4zRationale 'z' does not contain 'z' for payload z
>assert %(py6)spy6)r    	rationaler"   rJ   r$   r%   r&   r'   r(   r)   r#   )	r   payloadrV   r   r,   @py_format3r.   @py_format5@py_format7s	            r   3test_wb2_rationale_is_non_empty_and_identifies_ruler^   Y   s    +	Z@
-	 Z@
-	 \B	25EF3	'Z@' 
"" &&w/!!W!WW%MgY#WWWWWWWxWWWxWWW!WWWWWW$,$6$6 	
 $66 	
 	
 	
 $6 	
 	
 
6	
 	
  ! 	
 	
 
	 ! 	
 	
 
6	
 	
  %- 	
 	
 
	 %- 	
 	
 
	 %7 	
 	
  (,,--ABSAT U")%	
 	
 	
 	
 	

r   c                 8   ddiddiddifD ]  }| j                  |      }|j                  }d}||u }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      d	z  }t        j                  d
|j                   d|j                         dz   d|iz  }t        t        j                  |            dx}x}} y)z?WB3: RoutingDecision.is_cached defaults to False for every tierr   
redis_read
faq_answercompletely_unknownFisz1%(py2)s
{%(py2)s = %(py0)s.is_cached
} is %(py5)sr   r   z%is_cached must default to False, got z
 for tier rI   r   N)r    	is_cachedr"   r#   r$   r%   r&   r'   rJ   r!   r(   r)   )r   rZ   r   r,   r-   r.   r/   r0   s           r   $test_wb3_is_cached_defaults_to_falserg   j   s*    
		%& 	

 &&w/!! 	
U 	
!U* 	
 	
!U 	
 	
 
6	
 	
   	
 	
 
	  	
 	
 
	 " 	
 	
 
	 &+ 	
 	
  4H4F4F3G H (	
 	
 	
 	
 	
 	
	
r   c                    | j                  ddi      }|j                  }d}||k(  }|st        j                  d|fd||f      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}}d}|j                  }|j                  } |       }	||	v }
|
st        j                  d|
fd||	f      t        j                  |      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |	      dz  }dd|iz  }t        t        j                  |            d
x}x}
x}x}}	y
)uN   WB4: Unknown type with no special keys → T2; rationale mentions 'defaulting'r   some_brand_new_operationr9   r   r   r   r   r   r   NrP   rT   )zc%(py1)s in %(py9)s
{%(py9)s = %(py7)s
{%(py7)s = %(py5)s
{%(py5)s = %(py3)s.rationale
}.lower
}()
})py1py3r   r   py9zassert %(py11)spy11)r    r!   r"   r#   r$   r%   r&   r'   r(   r)   rY   lower)r   r   r,   r-   r.   r/   r0   @py_assert0@py_assert6@py_assert8@py_assert2@py_format10@py_format12s                r   Atest_wb4_unknown_type_defaults_to_t2_with_defaulting_in_rationaleru   x   s'   ""F,F#GHH== D =D    =D      8   8   =   D       58--5-335355<55555<5555<55555585558555-5553555555555555r   c                    t         D ]  }| j                  d|i      }|j                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        j                  d| d|j                         d	z   d
|iz  }t        t        j                  |            dx}x}}|j                  }t        d   }||k(  }|st        j                  d|fd||f      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}} y)z+Every type in T0_TYPES must classify as T0.r   r   r   r   r   r   zExpected T0 for type='', got rI   r   Nr   r   )r   r    r!   r"   r#   r$   r%   r&   r'   rJ   r(   r)   r*   r	   r   rS   r   r,   r-   r.   r/   r0   s           r   test_all_t0_types_route_to_t0ry      $    3&&{3}}XX}$XXX}XXXXXXxXXXxXXX}XXXXXX(>qc&XXXXXXXX~~2T!22~!22222~!2222222x222x222~222!222222223r   c                    t         D ]  }| j                  d|i      }|j                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        j                  d| d|j                         d	z   d
|iz  }t        t        j                  |            dx}x}}|j                  }t        d   }||k(  }|st        j                  d|fd||f      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}} y)zEEvery type in T1_TYPES (with no T0 keys present) must classify as T1.r   r4   r   r   r   r   zExpected T1 for type='rw   rI   r   Nr   r   )r   r    r!   r"   r#   r$   r%   r&   r'   rJ   r(   r)   r*   r	   rx   s           r   test_all_t1_types_route_to_t1r|      rz   r   c                  ^   t        ddd      } | j                  }d}||u }|st        j                  d|fd||f      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}}| j                  }d}||k(  }|st        j                  d|fd||f      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}}| j                  }d}||k(  }|st        j                  d|fd||f      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}}| j                  }d}||k(  }|st        j                  d|fd||f      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}}y)zIRoutingDecision can be constructed directly with expected field defaults.r9   r:   test)r!   r*   rY   Frc   re   rdr   r   r   Nr   r   r   )z1%(py2)s
{%(py2)s = %(py0)s.rationale
} == %(py5)s)r   rf   r"   r#   r$   r%   r&   r'   r(   r)   r!   r*   rY   )r   r,   r-   r.   r/   r0   s         r   "test_routing_decision_is_dataclassr      s   	d*;v	NB<< 5 <5    <5      2   2   <   5       77d7d?7d227d88(((8(((((8(((((((2(((2(((8(((((((((((<<!6!<6!!!!<6!!!!!!2!!!2!!!<!!!6!!!!!!!r   c                    | j                  i       }|j                  }d}||k(  }|st        j                  d|fd||f      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}}y)	u=   Payload without a 'type' key → treated as 'unknown' → T2.r9   r   r   r   r   r   r   N)
r    r!   r"   r#   r$   r%   r&   r'   r(   r)   r+   s          r   $test_missing_type_key_defaults_to_t2r      s    ""2&H== D =D    =D      8   8   =   D       r   __main__u   BB1 health_check → T0r   r   r   u   BB2 lead_qualify → T1r3   r4   u   BB3 novel_decision → T2r8   r9   u   BB4 crud_operation key → T0rQ   r>   r?   u   BB5 template key → T1welcomerD   zWB1 T0 key beats T1 typeGPASSFAIL   z  [z] u
    → tier=z, model=
/z tests passedu)   ALL TESTS PASSED — Story 4.02 (Track B))2__doc__builtinsr$   _pytest.assertion.rewrite	assertionrewriter"   syspathinsertpytestcore.routing.tier_classifierr   r   r   r   r   r   r	   fixturer   r1   r6   r;   rA   rF   rK   r^   rg   ru   ry   r|   r   r   __name__ctestspassednamerZ   expected_tierr    r   r!   okstatusprintr*   lenexitr   r   r   <module>r      s    
 * +     /,//,
"
633"! zA 
#&.)AX\]	"6>*BY]^	$63C*DY]^	(3(*SY]^	"3I*NY]^	#SV(WY]^E F(- W$g}::g&]]m+6aKFF82dV:hmm_HX^^DTUVW 
BvhaE
|=
12U9:5 r   