
    in1                       d Z ddlmZ ddlZddlZddlZddlZddlmZ ddlm	Z	 ej                  j                  d e e	e      j                         j                               ddlmZmZmZmZmZmZmZmZ  e       Zed   Zed   Zed	   Z ej:                  d
      ZdZdZ ddZ!ddZ"ddZ#ddZ$e%dk(  r ejL                   e#              yy)u[  G4.3 canary — demographic descriptor presence ratio (Task 4.4).

Measures the ratio of FIRST id appearances (per
`(scene_index, shot_index, variation_index, id_token)` tuple) that have any
ethnicity component (10 entries) OR age band (6 entries) within ±50 chars
window. Override R1R2-B3 — char-level proximity, NO sentence regex.

Scope: pinned (si, shi) tuples — this canary measures the denominator
(all first-appearance id tuples in pinned shots) and the numerator (those
with descriptor present). Composite-ID-only shot scope per spec §5.1 is
realised structurally — denominator is naturally 0 for shots with no
composite IDs (R1R2-B3 / R3-I1).

Override R4-M3 carry — ratio floor:
  if total_first_id_appearances < 10, append measurement_failure
  ("baseline scene set too small for stable ratio per Override R2-M6")
  and exit 1. Do NOT silently return ratio = 0.0.

Exit criteria (analyzer, OUT-OF-BAND):
  candidate.metrics.demographic_descriptor_present_ratio
    >= baseline.metrics.demographic_descriptor_present_ratio  (no degradation)

Usage:
    python scripts/canary/g4_3_demographic_descriptor_present.py \
        --config canary_config.json \
        --cp-root data/projects/<pid>/checkpoints/scene_detail \
        --prompt-version 17.<timestamp> \
        --role candidate \
        --output results/demographic_descriptor_candidate.json
    )annotationsN)datetime)Path)_load_scenes_or_fail_validate_scene_preflightbuild_pinning_scene_setis_close_framingload_cp_manifestload_pinning_from_argsrender_prompt_card_importsvalidate_pinning_ID_ETHNICITY_COMPONENTS_ID_AGE_BANDS_ID_REPRODUCTION_SURFACESz\bC\d{2}(?:O\d{2})?\b2   
   c                 L    t        j                         j                         dz   S )NZ)r   utcnow	isoformat     5scripts/canary/g4_3_demographic_descriptor_present.pyiso8601_nowr   D   s    ??&&(3..r   c                4   i }t               }| j                         }t        j                  |       D ]  }|j	                         }||v r|j                         ||<   t        d|j                         t        z
        }t        t        |       |j                         t        z         }||| }d}	t        D ])  }
|
j                         |v s|j                  |       d}	 n |	rt        D ]'  }|j                         |v s|j                  |          ||fS )aJ  Return (first_pos_by_id_token, descriptor_present_id_token_set).

    For each id token, only the FIRST occurrence in the prompt is recorded.
    descriptor_present is the subset of first-occurrence id tokens whose
    [start-50, end+50] window contains any ethnicity OR age-band substring
    (case-insensitive, char-level).
    r   FT)setlower	_ID_REGEXfinditergroupstartmaxWINDOW_CHARSminlenendr   addr   )promptseen_idsdescriptor_presentplmatchid_token	win_startwin_endwindowmatched	ethnicityages               r   #first_appearance_descriptor_presentr4   H   s     "H#&5	B##F+;;=x"[[]5;;=<78	c&k599;#=>Ig&1I F*"&&x0	 2
  Cyy{f$"&&x0 !% ,. '''r   c                    t        j                  d      } | j                  dt        d        | j                  dt        d        | j                  dt        d        | j                  dt        d	       | j                  d
t        dddg       | j                  dt        d        | j                  dt        d        | j                  dt        d        | j                  dt        d g d       | j                  dt        d        | j                  dt        d        | j                  dt        d	       | j	                         }t        |      }t        |       d }|j                  rUt        |j                        j                  dd      5 }t        j                  |      }d d d        j                  d      }|s|j                  }|st        dt        j                          yt        |      }|j#                         st        d| t        j                          yt%        |      }g }t'        ||      }	|	<t)               |j*                  |j,                  |g dddddd |d!d"id#}
t/        |
|       yt1        |      }t3               }d}d}d}d}g }|	D ]$  }t5        |t6              s|j9                  d$       &|j                  d%      }|j                  d&      }||Mt;        |      t;        |      f|vrf|j=                  t;        |      t;        |      f       t?        ||||      }|tA        |      \  }}||j9                  d'| d(| d)|        d*}d*}|D ]}  }t5        |t6              s|j                  d+      }t5        |t              s6tB        jE                  |      rd}|jG                         }tH        D ]  }|jG                         |v sd} }  |j9                  |||||d,       |dz  }|r|dz  }tK        |      D ]  \  }}t5        |t6              s|j9                  d'| d(| d-| d.       2|j                  d+      }t5        |t              r|jM                         s|j9                  d'| d(| d-| d/       tO        |      \  } }!|tQ        |       z  }|tQ        |!      z  } ' |D ]$  \  }}||f|vs|j9                  d0| d(| d1       & |tR        k  r|j9                  d2| d3tR                |dkD  r||z  }"nd}"t)               |j*                  |j,                  |||||||"d |d!d"id#}
t/        |
|       t        d4|j,                   d5|j*                   d6| d7| d8| d9| d:|"d;d<tQ        |       d=t        j                          |ryy# 1 sw Y   xY w)>Nu_   G4.3 canary: demographic descriptor presence ratio (composite-ID first-appearance, ±50 chars).)descriptionz--config)typedefaultz--pidz	--cp-rootz--prompt-versionT)r7   requiredz--role	candidatebaseline)r7   r8   choicesz--scene-index-listz--shot-index-list-per-scenez--model-routingz--prompt-source-mode)Nfiledbz--card-commit-hashz--id-policy-card-snapshot-hashz--outputrutf-8encodingcp_rootz?[g4_3_demographic_descriptor_present] ERROR: --cp-root required)r=      zB[g4_3_demographic_descriptor_present] ERROR: --cp-root not found: r   g        )total_shotsshots_with_composite_id1demographic_descriptor_total_first_id_appearances0demographic_descriptor_present_first_appearances$demographic_descriptor_present_ratiodemographic_descriptor_presentux   scripts/canary/g4_3_demographic_descriptor_present.py (composite-ID shots only, 4-tuple denominator — R1R2-B3 / R4-M3))	timestampprompt_versionrolepinning	scene_setmetricsmeasurement_failuresmeasurement_scriptszscene entry is not dictscene_index_shot_indexs_shz: F
t2i_prompt)rS   
shot_indexr	   has_composite_idhas_reproduction_surface_vz: variation not a dictz': t2i_prompt missing, empty, or not strz
pinning (sz ): not present in CP data.scenesz]baseline scene set too small for stable ratio per Override R2-M6: total_first_id_appearances=z < RATIO_MIN_SAMPLE=z+[g4_3_demographic_descriptor_present] role=z prompt_version=z
  total_shots=z  shots_with_composite_id=z
  total_first_id_appearances=z(
  descriptor_present_first_appearances=z	
  ratio=z.4fz
  measurement_failures: zP
Exit criteria (analyzer): candidate.ratio >= baseline.ratio (degradation block))*argparseArgumentParseradd_argumentstr
parse_argsr   r   configr   openjsonloadgetrC   printsysstderrexistsr
   r   r   rL   rM   _emitr   r   
isinstancedictappendintr'   r   r	   r   searchr   r   	enumeratestripr4   r%   RATIO_MIN_SAMPLE)#parserargsrN   cp_root_strfpcfgrC   cprQ   scenesoutpinning_set
seen_in_cprE   rF   total_first_id_appearances$descriptor_present_first_appearancesscene_set_outscenesishi
variationscf_flagcf_errrY   rZ   varpr+   surfvir(   seenr*   ratios#                                      r   mainr   n   sf   $$uF 
d;
c48
#t<
*tD
sK!+[ 9  ;
,3E
5CN
)TB
.S$ 4  6
,3E
8sDQ

t<D$T*GW"K{{$++##C'#:b))B-C ;ggi(llO::	;G>>RSZR[\::		'	"B&(!"&:;F~$"11II +,EFDE8; %90Y$
* 	c4)'2K'*uJK!"+,( "M%& ''(ABYY}%ii&:GSXk1BS*+ /ub#?ST
*51 ''!B4s3%r&(BC !#( Cc4(%A!S!##A&'+$WWY5Dzz|r)370 6  	 ' 0(@
 	 	q#q(# ,GBc4($++t3se2bT)?@ WW\*Ffc*&,,.$++t3se2bT)PQ 'J6'R$D$ '#d)3&0C8J4KK0# -e J !	S9J& ''RDC5(HI ! "$44##**D)E F  013	
 "A%03MM 	  !]--		"&'>A[@d49
 !5,U 
C, 
#t	
5dii[@PQUQdQdPe f$%?@W?X Y((B'C D22V1W X+ ##&';#<"= >Z	[ ZZ	 k ;:s   V77Wc                   |j                   dk(  rHt        j                  | t        j                  ddd       t        j                  j                  d       y t        |j                         }|j                  j                  dd       |j                  dd	
      5 }t        j                  | |ddd       d d d        y # 1 sw Y   y xY w)N-F   T)ensure_asciiindent	sort_keys
)parentsexist_okwr@   rA   )
outputrc   dumprg   stdoutwriter   parentmkdirrb   )rz   rt   out_pathrv   s       r   rj   rj   ?  s    {{c		#szza4P

$dT:]]3]1RIIc2E!tL 211s   CC
__main__)returnr_   )r(   r_   r   ztuple[dict[str, int], set[str]])r   rn   )rz   rl   rt   zargparse.Namespacer   None)'__doc__
__future__r   r\   rc   rerg   r   pathlibr   pathinsertr_   __file__resolver   _g4_3_commonr   r   r   r	   r
   r   r   r   _CONSTSr   r   r   compiler   r#   rr   r   r4   r   rj   __name__exitr   r   r   <module>r      s   < #   	 
   3tH~--/667 8	 	 	 %
&"#=> (#$?@  BJJ/0	  /#(LNbM zCHHTV r   