
    /ii0                       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
mZ ddlZddlZddlmZmZ  ed      Ze	j&                  j)                  d ee             ddlmZmZ ddlmZ d	Zd
Zg dZdddddddddddddddgZddddddZd-dZ d.dZ!d.dZ"d.d Z#d.d!Z$d.d"Z%ejL                  jN                  d/d#       Z(ejL                  jR                  ejL                  jU                  d$ e+d%      D  cg c]  }  e+d%      D ]  }| |f  c}}       d0d&              Z,ejL                  jR                  d1d'       Z-ejL                  jR                  d1d(       Z.ejL                  jR                  d1d)       Z/ejL                  jR                  d1d*       Z0ejL                  jR                  d1d+       Z1ejL                  jR                  d1d,       Z2yc c}} w )2u  test_real_render_25.py — task-2428 Phase 2-4 회귀 테스트.

실 렌더링 25장 (h{1..5} × v{1..5}) + 적대 케이스 5건 검증:
  - 25장 batch: visual_diversity / brand_color / hybrid_pattern threshold 통과 보장
  - 적대 케이스 5건 (TV-static, 단조, 99% 회색, 한글 미달, 폰트 미달): FAIL 검출 보장

결정성 전략:
  - BASE_SEED=42 + 패턴별 1000 offset + 변형별 100 offset (재현 가능)
  - 환경 BLOCKED (tesseract 미설치): font_size/ocr_confidence 검증 BLOCKED 처리
  - smoke (단일) vs full (25장) 분리 — pytest -m smoke / -m full
  - 타임아웃: 패턴당 60초 (Satori 호출 timeout 120초의 1/2)

작성: 디자인팀 카구야 (단위 테스트 담당)
    )annotationsN)Path)Image	ImageDrawz*/home/jay/workspace/skills/satori-cardnews)
EvalResultevaluate_image)_render_with_seed*   )i8  iF  )h1_photo_cardh2_illustration_cardh3_gpt_style_cardh4_gradient_cardh5_user_photo_cardu   보험료 과다 청구u   올해 평균 23% 인상.)titlebodyu   실손보험 비교의 정석u!   월 4만원대 보장 격차 87%.u   갱신형 vs 비갱신형u"   10년 후 보험료 차이 2.4배.u   운전자보험 1만원 룰u&   과실 50% 합의금 평균 380만원.u   암보험 진단금 100% 약속u$   유사암 약관 회피 6개 상품.z#0f1729z#d4a853InsuRoz#fafaf8z#d4d8e0)primary	secondarynametitle_color
body_colorc                j    | j                   D cg c]  }d|vrd|vr| }}t        |      dkD  S c c}w )uN   visual/brand/hybrid 중 1개 이상 FAIL인지 확인 (환경 BLOCKED 무시).z[font_size]z[ocr_confidence]r   )fail_reasonslen)resultr
real_failss      >/home/jay/workspace/tests/skills/satori/test_real_render_25.py_is_visual_fail_onlyr   >   sM     &&!&8&A 	
J  z?Q	s   0c                    t         j                  j                  d      }|j                  dd|| dft         j                        }t        j                  |d      S )	uF   TV-static unstructured noise — 로키 [중대] 회귀 차단 대상.i  )seedr         )sizedtypeRGBmode)nprandomdefault_rngintegersuint8r   	fromarray)whrngarrs       r   _make_tv_staticr3   K   sJ    
))

S

)C
,,q#Q1IRXX,
>C??3U++    c                    t        j                  || dft         j                        }t        |      D ]"  }t	        d||z  dz  z         }|||ddddf<   $ t        j                  |d      S )u:   단조 그라데이션 — task-2401 회귀 차단 대상.r#   r%         Nr&   r'   )r)   zerosr-   rangeintr   r.   )r/   r0   r2   yvs        r   _make_monotone_gradientr>   R   sh    
((Aq!9BHH
-C1X a!er\!"Aq!G ??3U++r4   c                    t        j                  || dfdt         j                        }t        d|dz        }d|d|d| df<   d|d|d| df<   d|d|d| d	f<   t	        j
                  |d
      S )uE   99% 회색 + 1% 청록 — 로키 MEDIUM 면적 비율 우회 차단.r#      r6      d   r   N      r&   r'   )r)   fullr-   maxr   r.   )r/   r0   r2   band_hs       r   _make_99_gray_1_brandrH   [   s    
''1a)S
1CAHFC!QC!QC!Q??3U++r4   c                    t        j                  d| |fd      }t        j                  |      }|j	                  ddd       |S )u   OCR 한글 비율 < 50% (영문 위주) — 로키 LOW 우회 차단.

    실제 OCR 검증을 위한 이미지 — 환경에 tesseract 없으면 BLOCKED 처리됨.
    r&   r7   r7      color(   rO   u   SAVE 50% NOW JOIN HOM 한글   rQ   rQ   fillr   newr   Drawtextr/   r0   imgdraws       r   _make_korean_lt_50r[   f   s@    
 ))EAq6
6C>>#DIIh6_IMJr4   c                    t        j                  d| |fd      }t        j                  |      }|j	                  ddd       |S )u   폰트 < 40px — dq-rules.json absolute_min 위반.

    환경에 tesseract 없으면 BLOCKED — task-2421 정책에 따라 명시 알림.
    r&   rJ   rL   rN   u   tiny korean 한글 12pxrP   rR   rT   rX   s       r   _make_font_lt_40r]   r   s@    
 ))EAq6
6C>>#DIIh1IHJr4   c           	     	   | dz  }t         dz   dz   }t        t        d   t        dt        ||i        |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}}|j                  } |       }|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                  |      t        j                  |      t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            d
x}x}x}x}}t!        |t        dt              }|j"                  d   }|j"                  d   }|j"                  d   }|d   }d}||k\  }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j$                  d|d          dz   d|iz  }t        t        j                  |            d
x}x}}|d   }d}||k\  }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j$                  d|d          dz   d|iz  }t        t        j                  |            d
x}x}}|d   }d}||k  }|st        j                  d|fd ||f      t        j                  |      t        j                  |      dz  }t        j$                  d!|d          dz   d|iz  }t        t        j                  |            d
x}x}}|d"   }|s`t        j$                  d#|j'                  d$             d%z   d&t        j                  |      iz  }t        t        j                  |            d
}|d"   }|s`t        j$                  d'|j'                  d$             d%z   d&t        j                  |      iz  }t        t        j                  |            d
}y
)(u   smoke: h4 단일 렌더 — 통합 동작 빠른 확인 (< 5초).

    재현 시드: BASE_SEED + 3000 (h4 패턴 base offset).
    이 시드는 evidence-25-stratified-v4 h4_v1.png와 동일한 결과 산출.
    zsmoke_h4.pngi    r   r   content	design_mdhybrid_patterntarget_sizeoutput_pathr!   hintsAassert %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}outpy0py2py4N)>)z_%(py6)s
{%(py6)s = %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.stat
}()
}.st_size
} > %(py9)s)rj   rk   rl   py6py9zassert %(py11)spy11visual_diversitybrand_color_matchrc   std_mean      8@>=)z%(py1)s >= %(py4)spy1rl   zsmoke h4 std_mean too low: 
>assert %(py6)srn   unique_colorsz smoke h4 unique_colors too low: spatial_diff      9@<=)z%(py1)s <= %(py4)sz smoke h4 spatial_diff too high: passedzbrand_color_match FAIL: reason
>assert %(py1)srx   zhybrid_pattern FAIL: )	BASE_SEEDr	   CONTENTS	DESIGN_MDSIZEexists@py_builtinslocals
@pytest_ar_should_repr_global_name	_safereprAssertionError_format_explanationstatst_size_call_reprcomparer   details_format_assertmsgget)tmp_pathrh   r!   @py_assert1@py_assert3@py_format5@py_assert5@py_assert8@py_assert7@py_format10@py_format12r   visualbrandhybrid@py_assert0@py_assert2@py_format7@py_format2s                      r   test_smoke_h4_single_renderr      s    ^
#C td"D) :::<<33:<88$8:$:$$$$$$$$$$$$3$$$3$$$8$$$:$$$$$$$$$$$$$ C,>EF^^./FNN./E^^,-F*  %          "&    &fZ&8%9:     /" d "d*  "d    #    '+    +6/+B*CD     .! T !T)  !T    "    &*    +6.+A*BC     ?L?LL6uyy7J6KLLLL?LLLLL(KKK4VZZ5I4JKKKKKKKKKKr4   zpi,vi   c                @   t         |   }t        |   }t        |dz  z   |dz  z   dz   }| |j                  d      d    d|dz    dz  }t	        |t
        |t        ||i        |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        |t
        |t              }
|
j                   d   }|
j                   d   }|dk(  rg }|d   }d}||k\  }|}|r|d   }d}||k\  }|}|r|d   }d}||k  }|}|st        j"                  d|fd||f      t        j                  |      t        j                  |      dz  }dd|iz  }|j%                  |       |rt        j"                  dfdf      t        j                  |      t        j                  |      dz  }dd|iz  }|j%                  |       |r_t        j"                  dfdf      t        j                  |      t        j                  |      d z  }d!d"|iz  }|j%                  |       t        j&                  |d      i z  }t        j(                  d#|dz    d$|d    d%|d    d&|d          d'z   d(|iz  }t        t        j                  |            dx}x}x}x}x}x}x}x}x}x}}n|d)   }|st        j(                  | d*|dz    d+|j+                  d,       d-|j+                  d       d%|j+                  d       	      d.z   d/t        j                  |      iz  }t        t        j                  |            d}|d)   }|sht        j(                  | d*|dz    d0|j+                  d,             d.z   d/t        j                  |      iz  }t        t        j                  |            d}y)1u   25장 stratified: 각 (pattern, variant) 쌍이 visual+brand+hybrid 통과.

    환경 BLOCKED (OCR/font_size)는 별도 검증. 본 테스트는 visual_diversity,
    brand_color_match, hybrid_pattern threshold만 검증한다.
    r_   rB   _r   _vrA   .pngr`   rg   rh   ri   Nrq   rr   r   rs   rt   rz   r{   r|   ru   )z%(py3)s >= %(py6)s)py3rn   z%(py8)spy8)z%(py11)s >= %(py14)s)rp   py14z%(py16)spy16r}   )z%(py19)s <= %(py22)s)py19py22z%(py24)spy24zh4 vz: std=z unique=z spat=z
>assert %(py27)spy27r   z vz visual_diversity FAIL: r   z; std=r   rx   z brand_color FAIL: )PATTERNSr   r   splitr	   r   r   r   r   r   r   r   r   r   r   r   r   r   append_format_boolopr   r   ) r   pivipatternra   r!   rh   r   r   r   r   r   r   r   r   @py_assert4r   @py_assert10@py_assert13@py_assert12@py_assert18@py_assert21@py_assert20r   @py_format9@py_format15@py_format17@py_format23@py_format25@py_format26@py_format28r   s                                    r   !test_25_stratified_visual_qualityr      s"    rlGrlG rDy 28+d2D
c*1-.ba=
=C :::<<33:<CGT:F ^^./FNN./E$$	
vj! 	
T 	
!T) 	
f_.E 	
 	
.E.M 	
RXYgRh 	
lp 	
RhlpRp 	
 	
 	
!T 	
 	
 
	 " 	
 	
 
	 &* 	
 	
 	
 
6	
 
	
 	
.E 	
 	
 
	 /F 	
 	
 
	 JN 	
 	
 	
 
6	
 
	
 	
Rhlp 	
 	
 
	 Si 	
 	
 
	 mq 	
 	
 	
 
6	
 
	
 	
 	
  2a4&vj12(6/;R:S T>*+-	
 	
 	
 	
 	
 	
 	
 	

 h 	
 	
  ir"Q$7

88L7M N::j)*(6::o3N2OQ	
 	
 
	   	
 	
 	
 	
 	
 ? ?   )2bdV.uyy/B.CD        r4   c                    t        t         } t        d      }| j                  |       t	        |t
        dt              }t        |      }|st        j                  d|j                  d   d    d|j                  d   d          dz   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z  }t        t        j                  |            d}y)uY   적대 케이스 1: TV-static spatial_diff > 25 → FAIL 검출 보장 (로키 [중대]).z/tmp/adv_tv_static.pngr   u8   TV-static 차단 실패 (silent pass 회귀!): std_mean=rq   rs   z, spatial_diff=r{   z.
>assert %(py3)s
{%(py3)s = %(py0)s(%(py1)s)
}r   r   )rj   rx   r   N)r3   r   r   saver   r   r   r   r   r   r   r   r   r   r   r   )rY   tmpr   r   @py_format4s        r   "test_adversarial_tv_static_blockedr      s     4
 C
'
(CHHSMCOTBF' '  NN#56zBC D'9:>JK	M  	  	     	     	  	 !'   	 !'   	 (    	 r4   c                    t        t         } t        d      }| j                  |       t	        |t
        dt              }|j                  d   }|d   }| }|sZt        j                  d|d    d|d          d	z   d
t        j                  |      iz  }t        t        j                  |            dx}}|d   }d}||k  }|st        j                  d|fd||f      t        j                  |      t        j                  |      dz  }t        j                  d|d    d      dz   d|iz  }	t        t        j                  |	            dx}x}}y)uU   적대 케이스 2: 단조 그라데이션 std_mean < 25 → FAIL 검출 (task-2401).z/tmp/adv_monotone.pngr   rq   r   u&   단조 회귀 차단 실패: std_mean=rs   z, unique_colors=rz   
>assert not %(py1)srx   Nr|   )<)z%(py1)s < %(py4)srw   u&   단조 std_mean 임계 검증 실패: z >= 25ry   rn   )r>   r   r   r   r   r   r   r   r   r   r   r   r   )
rY   r   r   r   r   r   @py_format3r   r   r   s
             r   *test_adversarial_monotone_gradient_blockedr      sX    "4
(C
&
'CHHSMC,>EF^^./Fh     1
1C0D E01	3          *  $          !%    1
1C0DFK     r4   c                    t        t         } t        d      }| j                  |       i t        ddi}t        ||dt              }|j                  d   }|d   }| }|s`t        j                  d|j                  d             d	z   d
t        j                  |      iz  }t        t        j                  |            dx}}y)uc   적대 케이스 3: 99% 회색 + 1% 청록 → matching_area_ratio < 0.10 → FAIL (로키 MEDIUM).z/tmp/adv_99gray.pngr   z#00c5c5r   rr   r   u4   1% 면적 우회 차단 실패: matching_area_ratio=matching_area_ratior   rx   N)rH   r   r   r   r   r   r   r   r   r   r   r   r   )rY   r   mdr   r   r   r   r   s           r   (test_adversarial_99_gray_1_brand_blockedr      s      
&C
$
%CHHSM	,I	,y)	,BC%7>FNN./EX     ?uyyI^?_>`a        r4   c                    t        t         } t        d      }| j                  |       t	        |t
        dt              }|j                  d   }g }|j                  }d}d} |||      }| }	|	}
|	s|j                  }d}d} |||      }|}
|
sdd	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      t        j                  |      t        j                  |      d
z  }|j                  |       |	sdd	t        j                         v st        j                  |      rt        j                  |      nd	t        j                        t        j                        t        j                        t        j                        dz  }|j                  |       t        j                  |d      i z  }t        j                  d|       dz   d|iz  }t!        t        j"                  |            dx}
x}x}x}x}x}x}	x}x}x}}y)u   적대 케이스 4: OCR 한글 < 50% → FAIL or BLOCKED (로키 LOW).

    환경에 tesseract 있으면 한글 비율 < 0.5 → FAIL.
    환경에 tesseract 없으면 BLOCKED (silent pass 차단).
    z/tmp/adv_korean_lt50.pngr   ocr_confidencer   TblockedFMnot %(py10)s
{%(py10)s = %(py4)s
{%(py4)s = %(py2)s.get
}(%(py6)s, %(py8)s)
}ocrrk   rl   rn   r   py10N%(py21)s
{%(py21)s = %(py15)s
{%(py15)s = %(py13)s.get
}(%(py17)s, %(py19)s)
}py13py15py17r   py21rA   u(   한글 비율 우회 차단 실패: ocr=
>assert %(py24)sr   N)r[   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   )rY   r   r   r   r   r   r   r   @py_assert9@py_assert11r   @py_assert14@py_assert16r   r   r   @py_format22r   r   s                      r   %test_adversarial_korean_lt_50_blockedr     s+    d
#C
)
*CHHSMCOTBF
..)
*C  $ $' '' ' 'CGG I u GIu,E ,E                  !    #'    (    (     -0    -0    -4    5>    @E    -F       33%8       r4   c                    t        t         } t        d      }| j                  |       t	        |t
        dt              }|j                  d   }g }|j                  }d}d} |||      }| }	|	}
|	s|j                  }d}d} |||      }|}
|
sdd	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      t        j                  |      t        j                  |      t        j                  |      d
z  }|j                  |       |	sdd	t        j                         v st        j                  |      rt        j                  |      nd	t        j                        t        j                        t        j                        t        j                        dz  }|j                  |       t        j                  |d      i z  }t        j                  d|       dz   d|iz  }t!        t        j"                  |            dx}
x}x}x}x}x}x}	x}x}x}}y)uS   적대 케이스 5: 폰트 < 40px → FAIL or BLOCKED (dq-rules.json absolute_min).z/tmp/adv_font_lt40.pngr   	font_sizer   Tr   Fr   fontr   r   r   rA   u"   폰트 미달 차단 실패: font=r   r   N)r]   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   )rY   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   s                      r   #test_adversarial_font_lt_40_blockedr     s+    D
!C
'
(CHHSMCOTBF>>+&D  4 4( (( ( (TXX i  Xi-G -G                  "    $(    )    )     .2    .2    .6    7@    BG    .H       -TF3       r4   c                    t        d      } | j                         st        j                  d       t        D ]  }|j                  d      d   }t        dd      D ]  }| | d| dz  }| | d| d	z  }|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                  } |       }|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}}  | dz  }	d}|	|z  }|j                  }
 |
       }|sddt        j                         v st        j                  |	      rt        j                  |	      ndt        j                  |      t        j                  |
      t        j                  |      dz  }t        t        j                  |            dx}x}x}
}d}|	|z  }|j                  }
 |
       }|sddt        j                         v st        j                  |	      rt        j                  |	      ndt        j                  |      t        j                  |
      t        j                  |      dz  }t        t        j                  |            dx}x}x}
}d}| |z  }|j                  }
 |
       }|sddt        j                         v st        j                  |       rt        j                  |       ndt        j                  |      t        j                  |
      t        j                  |      dz  }t        t        j                  |            dx}x}x}
}y)uY   evidence-25-stratified-v4 디렉토리 무결성 — 27장 PNG + meta JSON 존재 확인.zF/home/jay/workspace/memory/reports/task-2424-evidence-25-stratified-v4uH   evidence 디렉토리 미생성 — render_25.py 실행 후 재테스트r   r   rA      r   r   z
.meta.jsonu   PNG 누락: zC
>assert %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}pngri   Nu   meta 누락: metaextraszsupabase_h4.pngzMassert %(py7)s
{%(py7)s = %(py5)s
{%(py5)s = (%(py0)s / %(py2)s).exists
}()
})rj   rk   py5py7zfinancial_h4.pngz
SUMMARY.mdbase)r   r   pytestskipr   r   r:   r   r   r   r   r   r   r   r   )r   pshortr=   r   r   r   r   r   r   r   @py_assert6@py_format8s                r   %test_evidence_25_dir_artifact_presentr   '  s    XYD;;=^_ 9Qq! 	9AE7"QCt,,CUG2aS
33D::5:<5<55<u!555555535553555:555<555555;;8;=8=88M$"888888848884888;888=888888		99 H_F&0F&&0&..0.00000000F000F000&000.0000000000'1F''1'//1/11111111F111F111'111/1111111111)D<)'')'))))))))D)))D)))<)))'))))))))))r4   )r   r   returnbool)r/   r;   r0   r;   r   zImage.Image)r   r   r   None)r   r   r   r;   r   r;   r   r   )r   r   )3__doc__
__future__r   builtinsr   _pytest.assertion.rewrite	assertionrewriter   syspathlibr   numpyr)   r   PILr   r   _SATORI_ROOTpathinsertstrscripts.quality_evaluatorr   r   scripts.retry_loopr	   r   r   r   r   r   r   r3   r>   rH   r[   r]   marksmoker   rE   parametrizer:   r   r   r   r   r   r   r   )r   r=   s   00r   <module>r     s   #   
      @A 3|$ % 1
 	 (1LM-7Z[)3WX+5]^/9_` 	,,,		  'L 'LT 58"NaU1X"NAq6"N6"NO) P )X 
 
    
 
  " 	 	 * *u #Os   9G