+
    <jJ              
         a  R[ tG0 t R t^ RIt^ RIt^ RIHt ^ RIHt ^ RIH	t	 ^ RI
Ht ]P                  ! ]4      t]R,          tRs] ^ k Rs] ^k ]! R04      t] ^k R	 R
 ltR R ltR R lt]tR R ltR R ltRR/R R lltR\R R lltR R ltR R ltR R ltR R ltR  R! lt R" R# lt!R$ R% lt"R& R' lt#R]R) R* llt$R+^ R(^R,^/t%R- R. lt&R/ R0 lt'R1 R2 lt(R3 R4 lt)]*! 4       t+] ^k R5 R6 lt,R7 t-]R8,          t.Rs/] ^k R9 R: lt0R\R; R< llt1R= R> lt2R? t3. R^Ot4]R@8X  Ed   ]! 4       t5]6! RA]7! ]54       RB24       ]! ]54       F  t8]5]8,          t9]9Pu                  RC]9Pu                  RDRE4      4      t;]6! RF]8 24       ]6! RG]9Pu                  RHRE4       24       ]6! RI]9Pu                  RJRE4       24       ]6! RK]; 24       ]6! RL]9Pu                  RMRE4       24       ]6! RNROPy                  ]9Pu                  RP. 4      4       24       ]6! RQ]9Pu                  RRRE4       24       ]6! 4        K  	  ]0! 4       t=]6! RS]>! RT ]=P                  4        4       4       RU24       ]=P                  4        Fk  w  tAtB]AP                  RV4      '       d   K  ]D! ]B]E4      '       g   K0  ]6! RW]A RX24       ]BP                  4        F  w  tFt8]6! RY]F RZ]8 24       K  	  ]6! 4        Km  	  R# R# )_u   
model_profiles.py — Load and query per-model capabilities from model_profiles.json.

Provides runtime access to model capabilities: supported aspect ratios,
max reference images, cost, API pattern, etc.
N)Path)Optional)CostMissingError)
CONFIG_DIRzmodel_profiles.jsonFschema_versionc                F    V ^8  d   QhR\         R\        \        ,          /# )   profilesreturndictliststr)formats   "ڐ/Users/joeturnerlin/Code/recoil-sessions/Joes-MacBook-Pro/adhoc--claude--atlas-redesign--20260625T050330Z-1fd50d09/recoil/core/model_profiles.py__annotate__r       s     
 
T 
d3i 
    c                |    V  Uu. uF+  pV\         9  g   K  VP                  R4      '       d   K)  VNK-  	  up# u upi )zIterate model IDs in a model_profiles-shaped dict.

Filters out `schema_version` and `_`-prefixed keys. Use for any config
keyed by model_id with single-underscore metadata convention
(model_profiles.json, model_roles.json, prompt_bible).
__METADATA_KEYS
startswith)r	   ks   & r   iter_model_idsr       sC     aN" 	
+,<<+< 	
8     999c                F    V ^8  d   QhR\         R\        \        ,          /# )r   strategyr
   r   )r   s   "r   r   r   -   s      d tCy r   c                |    V  Uu. uF+  pV\         9  g   K  VP                  R4      '       d   K)  VNK-  	  up# u upi )a"  Iterate model IDs in a provider_strategy-shaped dict.

Filters out `schema_version` and `__`-prefixed keys. The `__`-prefix
convention covers legacy `__version__` and any future `__internal`
markers (provider_strategy.json predates the single-underscore
convention used by model_profiles).
__r   )r   r   s   & r   iter_strategy_model_idsr   -   sC     aN" 	
+,<<+= 	
8  r   c                $    V ^8  d   QhR\         /# r   r
   r   )r   s   "r   r   r   ;   s      d r   c                 X    \         f   ^ RIHp  V ! \        R4      s \	        4        \         # )a  Load model profiles from config file (cached after first load).

Note: `schema_version` is preserved (not stripped) because the spec
contract requires `load().get('schema_version') == 1`. Callers that
iterate model entries should use `iter_model_ids()` to skip metadata.
validate_and_loadmodel_profiles)	_profilesrecoil.core.config_schemar%   _PROFILES_PATH!_run_cross_config_validation_oncer$   s    r   loadr+   ;   s&     ?%n6FG	)+r   c                    V ^8  d   QhRR/# )r   r
   N )r   s   "r   r   r   R   s     ' '4 'r   c                 d   \         '       d   R# ^ RIp V P                  P                  R4      R8w  d   Rs R# ^ RIHp ^ RIHp  V! \        R,          R4      pV! \        R	,          R
4      pV! \        R,          R4      pT! \        ;'       g    / TTTR7       Rs R#   \         d    Rs  R# i ; i)u   Run validate_cross_config once at first model_profiles load.

CROSS_CONFIG_VALIDATION_DEFERRED — gated on RECOIL_ENFORCE_CROSS_CONFIG=1
until live-config drift is resolved (see Phase 6 report).
NRECOIL_ENFORCE_CROSS_CONFIG1T)validate_cross_configr$   provider_strategy.jsonprovider_strategyzpipeline_config.jsonpipeline_configmodel_roles.jsonmodel_roles)r&   r3   r4   r6   )_CROSS_VALIDATEDosenvironget#recoil.core.model_profiles_validater1   r(   r%   r   FileNotFoundErrorr'   )r8   r1   r%   r3   r4   r6   s         r   r*   r*   R   s     	zz~~34;I;-113F
 ,//1B
 (++]
  B+'	   s   <B B/.B/c                0    V ^8  d   QhR\         R\        /# r   model_idr
   )r   r   )r   s   "r   r   r   |   s     
 
# 
$ 
r   c           	         \        4       pW9  d/   \        RV  RRP                  VP                  4       4       24      hW,          # )zOGet the full profile dict for a model ID.

Raises KeyError if model not found.
zUnknown model: z. Available: , )_loadKeyErrorjoinkeys)r?   r	   s   & r   get_profilerF   |   sI    
 wHhZ}TYYx}}5O4PQ
 	
 r   allow_missingc                <    V ^8  d   QhR\         R\        R\        /# )r   r?   rG   r
   )r   boolfloat)r   s   "r   r   r      s!      s d u r   c                   \        V 4      pVP                  RVP                  R4      4      pVf2   V'       d   \        P                  RV 4       R# \	        V RV  R2R7      h\        V4      # )u  Get cost per image (or per second for video models).

Fails loud (``CostMissingError``) when the profile carries neither
``cost_per_image`` nor ``cost_per_second`` — a silent ``0.0`` here
undercounts spend (REC-216). An explicit ``cost_*: 0.0`` is a legitimate
billing-zero and is returned as-is; only an ABSENT cost raises.

Telemetry/display callers that tolerate a missing cost pass
``allow_missing=True`` (returns ``0.0`` with a WARNING).
cost_per_imagecost_per_secondzNget_cost(%s): no cost_per_image/cost_per_second; returning 0.0 (allow_missing)        zmodel_profiles.get_cost())	result_idsource)rF   r:   loggerwarningr   rJ   )r?   rG   pcosts   &$  r   get_costrV      sr     	HA55!155):#;<D|NN0
 )A(1'M
 	
 ;r   c                t    V ^8  d   QhR\         R\        \         ,          R\        \         ,          R\        /# )r   r?   provider_idtierr
   )r   r   rJ   )r   s   "r   r   r      s>     C0 C0C0#C0 3-C0 	C0r   c                   ^ RI Hp \        R,          pV! VR4      pVf%   VP                  V / 4      pVP                  RR4      pVf.   VP                  V / 4      pVP                  R4      ;'       g    Rp\	        V 4      pVP                  R4      pV'       g   \        VP                  RR	4      4      # W9  d,   \        R
V  RV R\        VP                  4       4       24      hW,          p	V	P                  R/ 4      p
W*9  d/   \        R
V  RV RV R\        V
P                  4       4       24      hW,          pRV9  d   \        R
V  RV RV R24      h\        VR,          4      # )a  Get the per-second cost for a model via its active provider and tier.

Resolves provider and tier using the same primary/primary_tier path that
the runtime registry uses (mirroring registry.resolve_adapter):
  - provider: ``provider_id`` arg if given, else
    ``provider_strategy[model_id]["primary"]``.
  - tier:     ``tier`` arg if given, else
    ``provider_strategy[model_id].get("primary_tier") or "default"``.

Returns the ``cost_per_second`` from
``profile["providers"][provider]["tiers"][tier]``.

Fallback: if the model profile has no ``providers`` block, falls back to
the top-level ``cost_per_second`` (legacy flat field).

Raises:
    KeyError: if provider or tier is absent from the providers block.
    ValueError: if the resolved tier entry has no ``cost_per_second``.
r$   r2   r3   primarydefaultprimary_tier	providersrM   rN   Model 'z-' providers block has no entry for provider ''. Available: tiersz' provider 'z' has no tier 'z' tier 'z!' has no 'cost_per_second' field.)
r(   r%   r   r:   rF   rJ   rC   r   rE   
ValueError)r?   rX   rY   r%   strategy_pathr   strategy_entryprofileproviders_blockprovider_entryra   
tier_entrys   &&&         r   get_provider_cost_per_secondri      s   0 < !99M 0CDH!h3$((I>|!h3!!.1>>Y(#Gkk+.O W[[!2C899)hZ  }N40D0D0F+G*HJ
 	

 %1Nw+EhZ|K= 9v^D$6#79
 	

 J
*hZ|K= G. /
 	

 -.//r   c                0    V ^8  d   QhR\         R\        /# r>   r   int)r   s   "r   r   r      s     @ @3 @3 @r   c                8    \        V 4      P                  R^ 4      # )z'Get maximum reference images supported.max_reference_imagesrF   r:   r?   s   &r   get_max_refsrq          x $$%;Q??r   c                F    V ^8  d   QhR\         R\        \         ,          /# r>   )r   r   )r   s   "r   r   r      s"     D D DS	 Dr   c                8    \        V 4      P                  R. 4      # )z(Get supported aspect ratios for a model.supported_aspect_ratiosro   rp   s   &r   get_aspect_ratiosrv      s    x $$%>CCr   c                0    V ^8  d   QhR\         R\        /# r>   r   rI   )r   s   "r   r   r      s     D D3 D4 Dr   c                8    \        V 4      P                  RR4      # )zACheck if model supports inline reference images (Gemini pattern).supports_inline_refsFro   rp   s   &r   rz   rz      s    x $$%;UCCr   c                0    V ^8  d   QhR\         R\         /# r>   r   )r   s   "r   r   r      s     E Ec Ec Er   c                8    \        V 4      P                  RR4      # )zDGet the API integration pattern ('genai_inline' or 'upload_bundle').api_patternupload_bundlero   rp   s   &r   get_api_patternr      s    x $$]ODDr   c                :    V ^8  d   QhR\         \        ,          /# r!   )r   r   )r   s   "r   r   r      s     # #T#Y #r   c                 (    \        \        4       4      # )z=List all available model IDs (skips top-level metadata keys).)r   rB   r-   r   r   list_modelsr      s    %'""r   c                0    V ^8  d   QhR\         R\         /# r>   r|   )r   s   "r   r   r     s     : :3 :3 :r   c                8    \        V 4      P                  RR4      # )zReturns 'image' or 'video'.modalityimagero   rp   s   &r   get_modalityr     s    x $$Z99r   c                0    V ^8  d   QhR\         R\        /# r>   rk   )r   s   "r   r   r     s     @ @3 @3 @r   c                8    \        V 4      P                  R^ 4      # )z>Default video duration in seconds. Returns 0 for image models.max_duration_secondsro   rp   s   &r   get_default_durationr     rr   r   c                F    V ^8  d   QhR\         R\        \         ,          /# r>   r   r   )r   s   "r   r   r     s     7 7 7# 7r   c                6    \        V 4      P                  R4      # )z0Returns fallback model ID if defined, else None.fallback_modelro   rp   s   &r   get_fallback_modelr     s    x $$%566r   standardc          
      T    V ^8  d   QhR\         R\         R\         R\         R\         /# )r   r?   critic_nameparamr\   r
   r|   )r   s   "r   r   r     s:     * *** * 	*
 	*r   c                     \        V 4      pTP                  R/ 4      pTP                  T/ 4      pTP                  Y#4      #   \         d    Tu # i ; i)a  Get a per-model critic override parameter.

Args:
    model_id: Model ID (e.g. "seedream-v4.5").
    critic_name: Critic name (e.g. "anatomy", "identity_drift").
    param: Parameter name within the override dict (default: "strictness").
    default: Default value if no override exists.

Returns:
    The override value, or `default` if no override is configured
    for this model+critic+param combination.
critic_overrides)rF   rC   r:   )r?   r   r   r\   re   	overrides
critic_cfgs   &&&&   r   get_critic_overrider     sY    $h' .3I{B/J>>%))	  s   A AAstrictrelaxedc                0    V ^8  d   QhR\         R\        /# r>   rx   )r   s   "r   r   r   6  s     > >S >T >r   c                8    \        V 4      P                  RR4      # )z)Check if model supports audio generation.supports_audioFro   rp   s   &r   r   r   6  s    x $$%5u==r   c                0    V ^8  d   QhR\         R\        /# r>   rx   )r   s   "r   r   r   ;  s     C C# C$ Cr   c                8    \        V 4      P                  RR4      # )z2Check if model supports multi-shot scene batching.supports_multi_shotFro   rp   s   &r   r   r   ;  s    x $$%:EBBr   c                0    V ^8  d   QhR\         R\        /# r>   rx   )r   s   "r   r   r   @  s     W Ws Wt Wr   c                p    \        V 4      pVP                  RR4      ;'       d    VP                  RR4      # )z4Check if model supports start+end frame I2V control.supports_start_frameFsupports_end_framero   )r?   rT   s   & r   supports_start_end_framer   @  s3    HA55'/VVAEE:NPU4VVr   c                H    V ^8  d   QhR\         R\         R\        R\         /# )r   r?   	pass_typehas_start_framer
   rx   )r   s   "r   r   r   F  s0     . ... . 		.r   c                    VR8X  d   V'       d   RMRpMRp\        V 4      pVP                  R4      pV'       g   \        RV  R24      hW59  d   \        RV  RV R	24      hWS,          # )
u5  Look up the preferred generation mode for a coverage pass.

Reads model_profiles.json `<model>.coverage_mode_preferences` — each
video model declares its own mode per pass-type scenario. This keeps
mode-selection out of coverage_planner.py and lets a model swap
automatically carry the right modes with it.

Args:
    model_id: Bible model key (e.g. "seeddance-2.0").
    pass_type: "character" or "env".
    has_start_frame: True when a previs start frame exists for the pass.

Returns:
    Mode string ("i2v", "r2v", or "t2v") declared by the model profile.

Raises:
    KeyError: If the model has no `coverage_mode_preferences` block, or
        the resolved key is missing. Add the field to model_profiles.json
        for any video model referenced in pipeline_config.json
        coverage_strategy.model_routing.
envenv_with_frameenv_without_framecharacter_passcoverage_mode_preferencesr_   z' has no `coverage_mode_preferences` block in model_profiles.json. Required for any video model referenced in pipeline_config.json coverage_strategy.model_routing. Add: {"character_pass": "...", "env_with_frame": "...", "env_without_frame": "..."}.z,' coverage_mode_preferences is missing key 'z!'. Add it to model_profiles.json.)rF   r:   rC   )r?   r   r   keyre   prefss   &&&   r   get_coverage_moder   F  s    4 E"17J(#GKK34EhZ  , -
 	
 hZ  u57
 	
 :r   c                R    V ^8  d   QhR\         R\        \        \        3,          /# )r   
model_namer
   )r   tuplerJ   )r   s   "r   r   r   z  s#     ( (C (E%,4G (r   c                   \        V 4      pVP                  R4      pVP                  RR4      pVf9   V \        9  d,   \        P	                  RV 4       \        P                  V 4       Rp\        V4      \        V4      3# )zReturn (min_duration_seconds, max_duration_seconds) from the model profile.

If min_duration_seconds is missing from the profile, logs a WARNING once
per model and falls back to 4.0. Missing max falls back to 15.0.
min_duration_secondsr   g      .@zCModel profile for %r has no min_duration_seconds; defaulting to 4.0g      @)rF   r:   _warned_missing_min_durationrR   rS   addrJ   )r   re   min_dmax_ds   &   r   get_segment_duration_boundsr   z  sv     *%GKK./EKK.5E}99NNU ),,Z8%L%,''r   c                 F    Rs \        P                  4        \        4        R# )z>Force reload profiles from disk (useful after config changes).N)r'   r   clearrB   r-   r   r   reloadr     s     I &&(	Gr   r5   c                $    V ^8  d   QhR\         /# r!   r"   )r   s   "r   r   r     s      T r   c                 D    \         f   ^ RIHp  V ! \        R4      s \         # )uE   Load role→model mapping from config file (cached after first load).r$   r6   )_rolesr(   r%   _ROLES_PATHr$   s    r   _load_rolesr     s     ~?";>Mr   c                ^    V ^8  d   QhR\         R\        \         ,          R\         R\         /# )r   rolecategoryproject_dirr
   r   )r   s   "r   r   r     s0     = =
=!#=<?==r   c                   V'       d   \        V4      R,          pVP                  4       '       d    \        P                  ! VP	                  RR7      4      pV'       d@   W9   d:   \        WA,          \        4      '       d   WV,          9   d   WA,          V ,          # M@VP                  4        F+  p\        V\        4      '       g   K  W9   g   K#  WP,          u # 	   \        4       pV'       d]   W9   dW   Wa,          p\        V\        4      '       d   W9   d	   Wp,          # \        RV  RV RRP                  R V 4       4       24      hVP                  4        FF  w  rVP                  R	4      '       d   K  \        V\        4      '       g   K6  W9   g   K>  WP,          u # 	  \        RV  R
RP                  R V 4       4       24      h  \        P                  \        3 d     ELi ; i)a  Look up a model ID by its role name.

Args:
    role: The role key (e.g. "production", "flash", "gate_image").
    category: Optional category to search in (e.g. "image", "text", "qc").
              If omitted, searches all categories for the role.
    project_dir: Optional project directory path. If provided, checks for
                 model_overrides.json in that directory first.

Returns:
    Model ID string.

Raises:
    KeyError: If role not found.
zmodel_overrides.jsonzutf-8)encodingzRole 'z' not found in category 'r`   rA   c              3   V   "   T F  qP                  R 4      '       d   K  Vx  K!  	  R# 5ir   Nr   .0r   s   & r   	<genexpr>get_model.<locals>.<genexpr>  s     #Ls!,,s:KAAs   )
)r   z3' not found in any category. Available categories: c              3   V   "   T F  qP                  R 4      '       d   K  Vx  K!  	  R# 5ir   r   r   s   & r   r   r     s     *Ue<<PSCT11er   )r   existsjsonloads	read_text
isinstancer   valuesJSONDecodeErrorOSErrorr   rC   rD   itemsr   )	r   r   r   override_pathr   cat_datarolescatcat_names	   &&&      r   	get_modelr     s   * [),BB!! JJ}'>'>'>'PQ	 5"9#6== h$77(2488$-$4$4$6%h55$:J#+>1 %7 ME H%oc4  T[9TF3H: >))#Ls#LLMO
 	
 $kkms##h%%$*:>!	 , 
 !!%*Ue*U!U V	X - (('2 s/   -G  =G &G 
G 	G G G-,G-c                $    V ^8  d   QhR\         /# r!   r"   )r   s   "r   r   r     s      t r   c                     \        4       # )u*   Return the full role→model mapping dict.)r   r-   r   r   get_all_rolesr     s
    =r   c                     Rs \        4        R# )z%Force re-read role mapping from disk.N)r   r   r-   r   r   reload_rolesr     s     FMr   __main__zModel profiles loaded: z models
rL   rM   zN/Az  z    Display: display_namez    Provider: providerz    Cost: $z    Max refs: rn   z    Aspects: rA   ru   z	    API: r~   z
Model roles loaded: c              #   h   "   T F(  p\        V\        4      '       g   K  \        V4      x  K*  	  R # 5i)N)r   r   len)r   vs   & r   r   r   )  s#     $[^zRSUYGZVSVV^s   22z roles
r   z  []z    z: c                8   V ^8  d   Qh/ ^ \         9   d   \        \        ,          ;R&   ^\         9   d
   \        ;R&   ^\         9   d   \        \
        ,          ;R&   ^\         9   d   \        \
        ,          ;R&   ^\         9   d   \        \        ,          ;R&   # )r   r'   r7   r   r   r   )__conditional_annotations__r   r   rI   	frozensetr   set)r   s   "r   r   r      sz      & !  8D>  '(  $ ). ? >	# >/b / .c#h .cf   gr   )NN)
strictnessr   )rB   r   r   r   r   rv   rV   ri   r   r   r   r   rq   r   r   rF   r   r   rz   r   r   r   r   r   STRICTNESS_LEVELS)Hr   __doc__r   loggingpathlibr   typingr   recoil.core.exceptionsr   recoil.core.pathsr   	getLogger__name__rR   r)   r'   r7   r   r   r   r   r+   rB   r*   rF   rV   ri   rq   rv   rz   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   __all__r	   printr   r?   re   r:   rU   rD   r   sumr   r   r   r   r   r   r   r   r   )r   s   @r   <module>r     s       3 (			8	$33 	     "+,<+=!> >
( 	'T
U 6C0L@
D
D
E
#
:
@
7
*< aq >
C
W.b *-  .(( -- =@
#L zwH	#CM?)
<="8,8${{+W[[9JE-RS8*ogkk.%@ABCw{{:u=>?@D6"#w{{+A5IJKLdii4Mr(RSTUV	'++mU;<=> - ME	
 $[U\\^$[![ \\de $kkm(s##h%%Cz#$"*.."2hTF"XJ/0 #3G ,) r   