
    g"iRF                       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ZddlmZ ddlmZmZmZ ddlmZ ddlmZmZ ddlZdZeej0                  vrej0                  j3                  de       dd	lmZmZmZmZmZ dd
Z dddZ!	 	 	 	 	 	 	 	 	 	 	 	 d dZ"d!dZ#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 Z0d Z1d Z2y)$uJ  
tests/track_b/test_story_8_05.py

Story 8.05: ScarAggregator — L3 Failure Pattern Collector

Black Box Tests (BB1–BB4):
    BB1  10 scars with similar vectors (cosine > 0.85) → 1-2 clusters returned
    BB2  new_since_last_epoch > 0 when new scars added after last epoch
    BB3  Aggregation report written to log (tmp_path)
    BB4  get_top_clusters(2) returns exactly 2 clusters sorted by member_count DESC

White Box Tests (WB1–WB4):
    WB1  Clustering uses cosine similarity threshold 0.85 (not k-means)
    WB2  get_top_clusters(5) returns ≤5 clusters sorted by member_count DESC
    WB3  _compute_cosine_similarity returns value between 0 and 1 for unit vectors
    WB4  Empty scars input → ScarReport with total_scars=0, empty clusters

ALL tests use mocks for Qdrant — no real connections.
ALL file I/O uses tmp_path.
    )annotationsN)	dataclass)datetime	timedeltatimezone)Any)	MagicMockpatchz/mnt/e/genesis-system)ScarAggregatorScarCluster
ScarReportCLUSTER_THRESHOLD_ScarRecordc                "    dg| z  }d||| z  <   |S )u   
    Return a unit vector of length dim with a 1.0 at position idx.
    All such vectors are orthogonal to each other → cosine similarity = 0.
                  ? )dimidxvs      6/mnt/e/genesis-system/tests/track_b/test_story_8_05.py_unit_vectorr   ;   s!    
 
AAcCiLH    c                    dg| z  }d||| z  <   t        |       D ]  }||| z  k7  s|||<    t        j                  t        d |D                    }|D cg c]  }||z  	 c}S c c}w )z
    Return a near-unit vector dominated by position idx.
    These will have cosine similarity > 0.999 with _unit_vector(dim, idx).
    r   r   c              3  &   K   | ]	  }||z    y w)Nr   ).0xs     r   	<genexpr>z$_near_unit_vector.<locals>.<genexpr>Q   s     *1Q*s   )rangemathsqrtsum)r   r   noiser   jnormr   s          r   _near_unit_vectorr&   E   sv    
 
AAcCiL3Z c	>AaD 99S***+D AH   s   A%c                L    t               }| |_        |||d|_        ||_        |S )z*Return a mock Qdrant ScoredPoint / Record.)textseverity	timestamp)r	   idpayloadvector)point_idr(   r)   r*   r-   points         r   _make_qdrant_pointr0   U   s2     KEEHEM
 ELLr   c                @    t               }| df|j                  _        |S )zCReturn a mock QdrantClient whose scroll() returns the given points.N)r	   scrollreturn_value)pointsclients     r   _mock_client_with_pointsr6   h   s    [F"($FMMMr   c                    t        j                  t        j                        t	        |       z
  }|j                         S )z4Return an ISO 8601 UTC timestamp N hours before now.)tz)hours)r   nowr   utcr   	isoformat)	hours_agodts     r   _tsr?   o   s*    		&)C	CB<<>r   c                   t        dd      }t        d      D cg c]7  }t        d| d| dd|z  z   t        |dz  	      t        ddd
            9 }}t	        |      }t        |t        | dz              }|j                  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"                  }t%        |      }d}||k  }|s6t        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                  |      t        j                  |      t        j                  |      dz  }t        j&                  dt%        |j"                               dz   d|iz  }t        t        j                   |            dx}x}x}}|j"                  d   }|j(                  }d }||k\  }|st        j                  d!|fd"||f      t        j                  |      t        j                  |      t        j                  |      d#z  }d$d%|iz  }t        t        j                   |            dx}x}x}}yc c}w )&u   
    BB1: 10 scars with very similar vectors (cosine > 0.85) → 1-2 clusters.

    We use near-unit vectors all pointing in the same direction, so they are
    highly similar and should collapse into a single cluster.
        r   
   scar_z DB connection timeout iteration 333333?g{Gz?g?r=   gMb@?r#   r.   r(   r)   r*   r-   scar_log.jsonlqdrant_clientlog_path   lookback_days==z3%(py2)s
{%(py2)s = %(py0)s.total_scars
} == %(py5)sreportpy0py2py5assert %(py7)spy7N   <=)zN%(py5)s
{%(py5)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.clusters
})
} <= %(py8)slen)rT   py1py3rV   py8u0   Expected ≤2 clusters for similar vectors, got 
>assert %(py10)spy10   >=)z4%(py3)s
{%(py3)s = %(py1)s.member_count
} >= %(py6)sr]   r^   py6assert %(py8)sr_   )r&   r   r0   r?   r6   r   str	aggregatetotal_scars
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanationclustersr\   _format_assertmsgmember_count)tmp_pathbase_vecir4   r5   aggrR   @py_assert1@py_assert4@py_assert3@py_format6@py_format8@py_assert2@py_assert7@py_assert6@py_format9@py_format11@py_assert0@py_assert5@py_format7s                       r   4test_bb1_similar_vectors_produce_one_or_two_clustersr   z   s    !Q'H r	  	QC[3A374!8^AG,$R&9	
	F 	 &f-F
X 001C ]]]+F############6###6############# 3 1 1$   1                                 $%    ;3v;O:PQ      ??1/**/a/*a////*a//////*///a////////	s   <M"c                   t        d      }t        d      D cg c]/  }t        d| d| dt        |dz         t        dd	      
      1 }}t        d      D cg c]/  }t        d| d| dt        d|z         t        dd      
      1 }}t	        ||z         }t        ||t        | dz              }|j                  d      }|j                  }d	}	||	kD  }
|
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c c}w c c}w )zT
    BB2: new_since_last_epoch > 0 when scars exist after last_epoch_timestamp.
    0   rE      new_zRecent failure       ?   rb   r   rG      old_zOld failure 皙?<   rH   )rJ   last_epoch_timestamprK   rL   rM   )>)z;%(py2)s
{%(py2)s = %(py0)s.new_since_last_epoch
} > %(py5)srR   rS   rW   rX   NrO   z<%(py2)s
{%(py2)s = %(py0)s.new_since_last_epoch
} == %(py5)s)r?   r   r0   r&   r6   r   rh   ri   new_since_last_epochrk   rl   rm   rn   ro   rp   rq   rr   )rv   
last_epochrx   recent_points
old_pointsr5   ry   rR   rz   r{   r|   r}   r~   s                r   7test_bb2_new_since_last_epoch_positive_for_recent_scarsr      s   
 r"J q	  	A3Z"1#&AE*$Q*	
	M 	& q	  	A3Zs#BF+$Q*	
	J 	 &mj&@AF
'X 001C
 ]]]+F&&**&****&******6***6***&**********&&+!+&!++++&!++++++6+++6+++&+++!+++++++C		s   4I4I#c           	     (   | dz  }t        dd      }t        dddt        d      |      t        dd	d
t        d      |      g}t        |      }t	        |t        |            }|j                  d       |j                  } |       }|st        j                  d      dz   dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        t        j                  |            dx}}|j!                  d      j#                         j%                  d      }	t'        |	      }
d}|
|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                  |
      t        j                  |      dz  }t        j                  dt'        |	             dz   d|iz  }t        t        j                  |            dx}
x}}t+        j,                  |	d         }|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}
}d$}||v }
|
st        j(                  d%|
fd&||f      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}}
d+}||v }
|
st        j(                  d%|
fd&||f      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}}
d,}||v }
|
st        j(                  d%|
fd&||f      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}}
|d,   }t/        |t0              }|sd-d.t        j                         v st        j                  t.              rt        j                  t.              nd.t        j                  |      d/t        j                         v st        j                  t0              rt        j                  t0              nd/t        j                  |      d0z  }t        t        j                  |            dx}}y)1zG
    BB3: aggregate() writes the ScarReport to the JSONL log file.
    zscar_aggregation_log.jsonl   r   s1ztimeout errorffffff?r   s2ztimeout error againg?rY   rI   rL   rM   zLog file was not createdzC
>assert %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}log_filerT   rU   py4Nutf-8encoding
rO   z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} == %(py6)sr\   linesrT   r]   r^   rf   zExpected 1 log line, got 
>assert %(py8)sr_   rj   z%(py1)s == %(py4)sr]   r   assert %(py6)srf   cluster_countinz%(py1)s in %(py3)sentryr]   r^   assert %(py5)srV   r   rs   z5assert %(py5)s
{%(py5)s = %(py0)s(%(py2)s, %(py3)s)
}
isinstancelistrT   rU   r^   rV   )r   r0   r?   r6   r   rh   ri   existsrk   rt   rm   rn   ro   rp   rq   rr   	read_textstripsplitr\   rl   jsonloadsr   r   )rv   r   vecr4   r5   ry   rz   r|   @py_format5r   r   r   r{   r   r   r   r   @py_format4r}   s                      r   #test_bb3_report_written_to_log_filer      s    66H
q!
C4#s1vsC4!6SVSIF &f-F
XC MMM" ??8?888888888888888888?8888888880668>>tDEu:DD:?DDD:DDDDDD3DDD3DDDDDDuDDDuDDD:DDDDDD7E
|DDDDDDDDJJuQx E$1$1$$$$1$$$$$$1$$$$$$$#?e####?e###?######e###e#######!*!U****!U***!******U***U*******:::J'.:'........:...:...'...................r   c                F   g }t        d      D ]<  }|j                  t        d| d| dt        |dz         t	        dd                   > t        d      D ]<  }|j                  t        d	| d
| dt        |dz         t	        dd                   > t        d      D ]<  }|j                  t        d| d| dt        |dz         t	        dd                   > t        |      }t        |t        | dz              }|j                  d       |j                  d      }t        |      }d}||k(  }|st        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                   |      t        j                   |      dz  }	dd|	iz  }
t#        t        j$                  |
            dx}x}}|d   }|j&                  }|d   }|j&                  }||k\  }|st        j                  d|fd ||f      t        j                   |      t        j                   |      t        j                   |      t        j                   |      d!z  }
d"d#|
iz  }t#        t        j$                  |            dx}x}x}x}}|d   }|j&                  }d}||k(  }|st        j                  d|fd$||f      t        j                   |      t        j                   |      t        j                   |      d%z  }	dd|	iz  }
t#        t        j$                  |
            dx}x}x}}y)&zZ
    BB4: get_top_clusters(2) returns exactly 2 clusters sorted by member_count DESC.
    r   g0_zgroup0 err r   r      r   r   g1_zgroup1 err rD   rB   rY   g2_zgroup2 err r      rH   rI   rL   rM   nrO   r   r\   top2r   rg   r_   Nrc   zV%(py3)s
{%(py3)s = %(py1)s.member_count
} >= %(py8)s
{%(py8)s = %(py6)s.member_count
}r]   r^   rf   r_   zassert %(py10)sra   z4%(py3)s
{%(py3)s = %(py1)s.member_count
} == %(py6)sre   )r   appendr0   r?   r   r6   r   rh   ri   get_top_clustersr\   rk   rl   rm   rn   ro   rp   rq   rr   ru   )rv   r4   rx   r5   ry   r   r   r   r{   r   r   r   r   r   s                 r   :test_bb4_get_top_clusters_returns_n_sorted_by_member_countr      s    F1X 
QCyKs*;S#a!e*l[]_`Nab	

 1X 
QCyKs*;S#a"f+|\^`aObc	

 1X 
QCyKs*;S#a"f+|\^`aObc	


 &f-F
X 001C MMM"!$Dt99>933tt977774777#7#77#77777#777777777777777#777777777$7$1$1$$$$1$$$7$$$$$$1$$$$$$$r   c           
     V
   d}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z  }t        j                  dt                dz   d|iz  }t        t        j                  |            d	x}}t        t               t        | d
z              }t        dd      }t        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                  |      dz  }t        j                  d|d      dz   d|iz  }t        t        j                  |            d	x}}t        dd      }	t        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                  |      dz  }t        j                  d|d      dz   d|iz  }t        t        j                  |            d	x}}t!        d      }t#        ddd||      t#        dd d!||      t#        d"d#d$||	      t#        d%d&d'||
      g}|j%                  |      }t'        |      }d}||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                  |      t        j                  |      d+z  }t        j                  d,t'        |       d-|D cg c]  }|j(                  |j*                  f c}       d.z   d/|iz  }t        t        j                  |            d	x}x}}t-        |D cg c]  }|j*                   c}d01      }|d   }d2}||k(  }|slt        j                  d|fd3||f      t        j                  |      t        j                  |      d4z  }d5d6|iz  }t        t        j                  |            d	x}x}}y	c c}w c c}w )7u|  
    WB1: Clustering uses cosine similarity threshold 0.85 — not k-means.

    We verify by:
    1. Confirming CLUSTER_THRESHOLD constant is 0.85.
    2. Checking that near-identical vectors have similarity >= 0.85.
    3. Checking that perfectly orthogonal vectors have similarity 0.0 < 0.85.
    4. Showing that a pair above threshold merges, pairs below threshold split.
    g333333?rO   z%(py0)s == %(py3)sr   rT   r^   z(Expected CLUSTER_THRESHOLD == 0.85, got 
>assert %(py5)srV   N	log.jsonlrI   rb   r   MbP?rF   rc   z%(py0)s >= %(py3)ssim_highu'   Expected high similarity ≥ 0.85, got z.4fr   r   <)z%(py0)s < %(py3)ssim_lowz$Expected low similarity < 0.85, got r   azerror A1rD   bzerror A2r   czerror C1r   dzerror C2r   r   r\   rs   r   z0Expected 3 clusters (1 merged + 2 singles), got z: r   r_   T)reverserY   r   r   r   rf   )r   rk   rl   rm   rn   ro   rp   rt   rq   rr   r   r	   rh   r&   _compute_cosine_similarityr   r?   r   _cluster_scarsr\   
cluster_idru   sorted)rv   r   rz   r   r}   ry   vavbr   vcvdr   r:   scarsrs   r   r{   r   r   r   cluster_sizesr   r|   r   s                           r   -test_wb1_clustering_uses_cosine_threshold_085r     s    !% $                !%    33D2EF     y{SKAW=X
YC 
1a	 B	1au	-B--b"5HU8tUUU8tUUUUUU8UUU8UUUtUUUFxPSnUUUUUUU 
a	B	a	B,,R4GO7T>OOO7TOOOOOO7OOO7OOOTOOOA'#OOOOOOO a&CCS#r2CS#r2CS#r2CS#r2	E !!%(H x= A =A   =A                                ;3x=/4<=qQ\\1>>*=
>	@     H=qANN=tLM q q    q      q        >=s   'T!T&c                	   d}g }t        d      D ]9  }|j                  t        d| ddt        |dz         t	        |d                   ; t        d      D ]9  }|j                  t        d	| ddt        |d
z         t	        |d                   ; |j                  t        dddt        d      t	        |d                   t        |      }t        |t        | dz              }|j                  d       |j                  d      }t        |      }d}||k  }	|	st        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                   |      t        j                   |      dz  }
dd|
iz  }t#        t        j$                  |            dx}x}	}t        |      }d}||k(  }	|	st        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                   |      t        j                   |      dz  }
dd|
iz  }t#        t        j$                  |            dx}x}	}t        t        |      dz
        D ]  }||   }|j&                  }||dz      }|j&                  }||k\  }	|	st        j                  d|	fd||f      t        j                   |      t        j                   |      t        j                   |      t        j                   |      d z  }t        j(                  d!|D cg c]  }|j&                   c}       d"z   d#|iz  }t#        t        j$                  |            dx}x}x}	x}} |d   }|j&                  }d}||k(  }	|	st        j                  d|	fd$||f      t        j                   |      t        j                   |      t        j                   |      d%z  }
dd|
iz  }t#        t        j$                  |            dx}x}x}	}yc c}w )&u   
    WB2: get_top_clusters(5) returns ≤5 clusters sorted by member_count DESC.

    When fewer than 5 clusters exist, returns all of them — not exactly 5.
    rb   r   r   errr   r   r   rY   r   rB   c0r   r   rI   rL   rM   r   r   rZ   )z0%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} <= %(py6)sr\   top5r   rg   r_   Nr   rO   r   rc   r   r   zClusters not sorted: r`   ra   r   re   )r   r   r0   r?   r   r6   r   rh   ri   r   r\   rk   rl   rm   rn   ro   rp   rq   rr   ru   rt   )rv   r   r4   rx   r5   ry   r   r   r   r{   r   r   r   r   r   r   s                   r   ,test_wb2_get_top_clusters_sorted_and_boundedr   D  s    CF1X a(1QC%c!a%j,WZ\]J^_`a1X b(1QC%c!b&k<X[]^K_`ab
MM$T5#s2wSRS@TUV%f-F
X+,C MMM" !$Dt99>933tt9t99>933tt9 3t9q=! 
Aw 	
w## 	
tAE{ 	
{'?'? 	
#'?? 	
 	
#'? 	
 	
 		  	
 	
 		 $ 	
 	
 		 (3 	
 	
 		 (@ 	
 	
  $T$BQ^^$B#CD	
 	
 	
 	
 	
 	

 7$7$1$1$$$$1$$$7$$$$$$1$$$$$$$ %Cs   5S&c                   t        t               t        | dz              }g d}|j                  ||      }d}||k  }d}||k  }|r|st	        j
                  d||fd|||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      d	z  }d
d|iz  }	t        t	        j                  |	            dx}x}x}}d}||z
  }
t        |
      }d}||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                  |      t	        j                  |      t	        j                  |      dz  }t	        j                  d|       dz   d|iz  }t        t	        j                  |            dx}x}
x}x}}g d}|j                  ||      }d}||k  }d}||k  }|r|st	        j
                  d||fd|||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      d	z  }d
d|iz  }	t        t	        j                  |	            dx}x}x}}d}||z
  }
t        |
      }d}||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                  |      t	        j                  |      t	        j                  |      dz  }t	        j                  d|       dz   d|iz  }t        t	        j                  |            dx}x}
x}x}}t        dd      }t        ddd      }|j                  ||      }d}||k  }d}||k  }|r|st	        j
                  d||fd|||f      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      d	z  }d
d|iz  }	t        t	        j                  |	            dx}x}x}}d}||k\  }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      d z  }t	        j                  d!|d"      d#z   d$|iz  }t        t	        j                  |            dx}}g d%}|j                  ||      }d}||k(  }|st	        j
                  d&|fd'||f      d(t        j                         v st	        j                  |      rt	        j                  |      nd(t	        j                  |      d z  }d)d$|iz  }t        t	        j                  |            dx}}y)*zY
    WB3: _compute_cosine_similarity returns value between 0 and 1 for unit vectors.
    r   rI   )r   r   r   r   r   )r[   r[   )z%(py1)s <= %(py4)sz%(py4)s <= %(py6)ssim_identical)r]   r   rf   rg   r_   Ng&.>r   )z;%(py6)s
{%(py6)s = %(py0)s((%(py1)s - %(py3)s))
} < %(py9)sabs)rT   r]   r^   rf   py9zExpected 1.0, got z
>assert %(py11)spy11)r   r   r   	sim_orthozExpected 0.0, got r   r   gMb`?rF   sim_neargGz?rc   r   r   zExpected near 1.0, got z.6fr   rV   )r   r   r   rO   r   sim_zeror   )r   r	   rh   r   rk   rl   rp   rm   rn   ro   rq   rr   r   rt   r&   )rv   ry   r   r   r   r   r   r|   r   r   r{   @py_assert8r   @py_format10@py_format12r   r   r   r   r   rz   r   r}   vzr   s                            r   /test_wb3_compute_cosine_similarity_unit_vectorsr   g  s:    y{SKAW=X
YC 
B222r:M&3-&&3&-3&&&&&3-3&&&3&&&&&&-&&&-&&&3&&&&&&&"P}s"P3"#PdP#d*PPP#dPPPPPP3PPP3PPPPPP}PPP}PPPsPPP#PPPdPPP.@,PPPPPPPP 
B..r26I"3)""s")s"""""3)s"""3"""""")""")"""s"""""""Hy3H3H$H$&HHH$HHHHHH3HHH3HHHHHHyHHHyHHH3HHHHHH$HHH*<YK(HHHHHHHH 
2q	!B	2q	.B--b"5H!3(!!c!(c!!!!!3(c!!!3!!!!!!(!!!(!!!c!!!!!!!E8tEEE8tEEEEEE8EEE8EEEtEEE6xnEEEEEEE 
B--b"5H8s?8s88sr   c                   t        g       }| dz  }t        |t        |            }|j                  d      }t	        |t
              }|sd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dt        j                         v st        j                  t
              rt        j                  t
              ndt        j                  |      d	z  }t        t        j                  |            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                  }g }||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"                  } |       }|sddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }t        t        j                  |            d
x}}t%        j&                  |j)                  d      j+                               }|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}}|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}}y
)uY   
    WB4: Empty scars from Qdrant → ScarReport with total_scars=0, empty clusters.
    rH   rI   rL   rM   z5assert %(py4)s
{%(py4)s = %(py0)s(%(py1)s, %(py2)s)
}r   rR   r   )rT   r]   rU   r   Nr   rO   rQ   rS   rW   rX   )z0%(py2)s
{%(py2)s = %(py0)s.clusters
} == %(py5)sr   zAassert %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}r   r   r   r   rj   r   r   r   rf   r   )r6   r   rh   ri   r   r   rm   rn   rk   ro   rp   rq   rr   rj   rl   rs   r   r   r   r   r   r   )rv   r5   r   ry   rR   r|   r   rz   r{   r}   r~   liner   r   r   s                  r   *test_wb4_empty_scars_produces_empty_reportr     s=    &b)F**H
XC ]]]+Ffj)))))))):))):))))))f)))f))))))j)))j))))))))))""""""""""""6"""6"""""""""""""?? b ?b    ?b      6   6   ?   b       &&+!+&!++++&!++++++6+++6+++&+++!+++++++ ???88?::h(('(:@@BCD#!#!####!######!####### %A% A%%%% A%%% %%%A%%%%%%%r   c                 ~   ddl } | j                  } |t              }|sd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t        j                  |      dz  }t        t        j                  |            dx}}| j                  t              D ch c]  }|j                   }}d}||v }|st        j                  d|fd	||f      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}}d}||v }|st        j                  d|fd	||f      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}}d}||v }|st        j                  d|fd	||f      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}}d}||v }|st        j                  d|fd	||f      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}}yc c}w )z;ScarCluster is a proper dataclass with all required fields.r   NNassert %(py5)s
{%(py5)s = %(py2)s
{%(py2)s = %(py0)s.is_dataclass
}(%(py3)s)
}dataclassesr   r   r   r   r   field_namesr   r   rV   representative_scarru   avg_severity)r  is_dataclassr   rm   rn   rk   ro   rp   rq   rr   fieldsnamerl   	r  rz   r{   r}   fr  r   r   r   s	            r   test_scarcluster_is_dataclassr    s   ##0#K00000000;000;000#000000K000K0000000000#.#5#5k#BCa166CKC&<;&&&&<;&&&<&&&&&&;&&&;&&&&&&& / K//// K/// //////K///K///////(>[((((>[(((>(((((([((([((((((((>[((((>[(((>(((((([((([(((((((	 Ds   N:c                 4   ddl } | j                  } |t              }|sd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t        j                  |      dz  }t        t        j                  |            dx}}| j                  t              D ch c]  }|j                   }}d}||v }|st        j                  d|fd	||f      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}}d}||v }|st        j                  d|fd	||f      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}}d}||v }|st        j                  d|fd	||f      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}}yc c}w )z:ScarReport is a proper dataclass with all required fields.r   Nr  r  r   r   rj   r   r   r  r   r   rV   rs   r   )r  r  r   rm   rn   rk   ro   rp   rq   rr   r  r  rl   r	  s	            r   test_scarreport_is_dataclassr    s   ##/#J////////;///;///#//////J///J//////////#.#5#5j#ABa166BKB'=K''''=K'''=''''''K'''K'''''''$:$$$$:$$$:$$$$$$$$$$$$$$$$!0![0000![000!000000[000[0000000 Cs   Lc                    t        t               t        | dz              }t        j                  t
        d      5  |j                  d       ddd       y# 1 sw Y   yxY w)zEget_top_clusters raises RuntimeError if aggregate() was never called.r   rI   zNo report available)matchr   r   N)r   r	   rh   pytestraisesRuntimeErrorr   )rv   ry   s     r   -test_get_top_clusters_raises_before_aggregater    sU    
kX+,C 
|+@	A "q!" " "s   AA"c                 L   ddl m} m}m} | t        u }|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        u }|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        u }|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
}y
)zKcore.evolution.__init__.py exports ScarAggregator, ScarReport, ScarCluster.r   )r   r   r   )is)z%(py0)s is %(py2)sSAr   )rT   rU   zassert %(py4)sr   NSRr   SCr   )core.evolutionr   r   r   rk   rl   rm   rn   ro   rp   rq   rr   )r  r  r  rz   @py_format3r   s         r   test_package_init_exportsr    sF     222222222r   c                   | dz  }t        dd      }t        dddt        d      |      g}t        |      }t	        |t        |            }|j                  d	
       |df|j                  _        |j                  d	
       |j                  d      j                         j                  d      }t        |      }d}||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$                  |      t        j$                  |      dz  }
t        j&                  dt        |             dz   d|
iz  }t)        t        j*                  |            dx}x}	}y)zCEach call to aggregate() appends a new JSONL line (not overwrites).zmulti.jsonlr   r   r   r   r   r   rI   rL   rM   Nr   r   r   rY   rO   r   r\   r   r   z Expected 2 lines (2 calls), got r   r_   )r   r0   r?   r6   r   rh   ri   r2   r3   r   r   r   r\   rk   rl   rm   rn   ro   rp   rt   rq   rr   )rv   r   r   r4   r5   ry   r   r   r   r{   r   r   s               r   #test_log_appended_on_multiple_callsr    sB   -'H
q!
C uc3q63?@F%f-F
vH
FCMMM" #)$FMMMMM"0668>>tDEu:KK:?KKK:KKKKKK3KKK3KKKKKKuKKKuKKK:KKKKKK>s5zlKKKKKKKKr   c                   t        dd      }t        dddt        d      |      }t        ddd	t        d
      |      }t        ||g      }t	        |t        | dz              }|j                  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z   d|
iz  }t        t        j                   |            dx}x}	}y)z<Scars with timestamps older than lookback_days are excluded.r   r   r1z
recent errrD   rY   o1zold errg?   r   rI   rL   rM   r   rO   rQ   rR   rS   z)Expected 1 scar within 7-day window, got z
>assert %(py7)srX   N)r   r0   r?   r6   r   rh   ri   rj   rk   rl   rm   rn   ro   rp   rt   rq   rr   )rv   r   recentoldr5   ry   rR   rz   r{   r|   r}   r~   s               r   +test_scars_outside_lookback_window_excludedr$    s;   
q!
ClCQEF
T9c3s8S
AC%vsm4F
vH{<R8S
TC]]]+F  "                    "#    4F4F4F3GH     r   )r   intr   r%  returnlist[float])r   )r   r%  r   r%  r#   floatr&  r'  )r.   rh   r(   rh   r)   r(  r*   rh   r-   r'  r&  r	   )r4   zlist[MagicMock]r&  r	   )r   )r=   r(  r&  rh   )3__doc__
__future__r   builtinsrm   _pytest.assertion.rewrite	assertionrewriterk   r   r    sysr  r   r   r   r   typingr   unittest.mockr	   r
   r  GENESIS_ROOTpathinsertcore.evolution.scar_aggregatorr   r   r   r   r   r   r&   r0   r6   r?   r   r   r   r   r   r   r   r   r  r  r  r  r  r$  r   r   r   <module>r6     s   * #     
 ! 2 2  *  'sxxHHOOA|$ ! 
  	
  &0D),X/>"%T0!f %F>&<	)1"
L$r   