
    gjo              "       l   U d 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	m
Z
  ej                  e      ZdZ	 ddlZdZdedefd	Z G d
 d      Z G d d      ZdZdeeef   fdZde
e   deeef   defdZde
eeef      de
e   deeef   fdZdedeeef   fdZdddddddddd	dedededededeeef   dede
e   d e
e	e      d!e
e   d"ed#e
e   d$e
e   de
eeef      de
e   de
e   f d%Zddddd&ded$e
e   d#e
e   fd'Zdae
e   e d(<    ejB                         Z"defd)Z#y# e$ r Y w xY w)*ue  이미지 생성 Opik 트레이서 — 메타데이터만 기록 (이미지 바이트 절대 첨부 안 함).

모든 이미지 생성 호출(Gemini T2I/I2I, fal.ai)을 감싸서
프롬프트, 참조 이미지 UUID, 생성 이미지 UUID, 파라미터, 에러를 Opik에 기록.
thread-local context(StepRunner)가 있으면 자동으로 session 그룹핑.
    N)contextmanager)AnyDictListOptionalFTmodelreturnc                     | xs dj                         }d|v sd|v ry|j                  d      sd|v s|j                  d      ryd|v sd	|v sd
|v ryy)u   모델 이름 → provider. 모르는 이름을 fal.ai 로 몰지 않는다.

    ★전에는 gemini/imagen 이 아니면 **전부 fal.ai** 였다. gpt-image 계열이
    Opik 에서 fal.ai 로 잘못 적혔다 (Codex 리뷰 지적, 2026-08-07).
     geminiimagen	google_aiz	gpt-imagezdall-eopenaiqwenfluxfalzfal.aiunknown)lower
startswith)r   ms     R/Users/manta/Documents/Projects/TheRoad-I1/backend/app/modules/llm/image_tracer.py_provider_ofr      s`     
"A1}A||K K1$4X8N{fkUaZ    c                   6   e Zd ZdZd Zdee   ddfdZdefdZdefdZ		 	 	 	 	 	 	 	 	 	 dd	e
d
e
de
deee
      dee
   de
dee
   dedeee
ef      deee
ef      dee
   deee
ef      dee   ddfdZe	 	 dd	e
d
e
de
deee
      deee
ef      f
d       Zy)ImageTraceru  이미지 생성 호출 래퍼 — Opik span 자동 기록.

    Usage:
        tracer = ImageTracer()

        with tracer.span("ref_image_gen", model="imagen-3.0-generate-002",
                         prompt=prompt, ref_image_ids=["abc123"]) as s:
            img_bytes, elapsed = gemini_client.generate_image(...)
            s.set_output(image_id="def456", duration_ms=elapsed)

        # 또는 단순 호출
        tracer.log("fal_angle", model="qwen-image-edit-2511",
                   prompt="H=15 V=-10 Z=1.0",
                   params={"horizontal": 15, "vertical": -10, "zoom": 1.0},
                   ref_image_ids=["abc123"], output_image_id="def456",
                   duration_ms=320)
    c                    d | _         d | _        t        r	 d }	 dd l}ddlm} t        |dd       }t        |dd      rL|j                  j                  d|j                         |j                  j                  d|j                         |rt        j                  |      | _         y t        j                         | _         y y # t        $ r d }Y Fw xY w# t        $ r }t        j                  d	|       Y d }~y d }~ww xY w)
Nr   settingsopik_project_nameopik_url_overrider   OPIK_URL_OVERRIDEOPIK_WORKSPACE)project_nameu6   Opik client init failed — image tracing disabled: %s)_client_explicit_context_opik_availableosapp.core.configr   getattrenviron
setdefaultr    opik_workspace	ExceptionopikOpikloggerwarning)selfproject_osr   excs        r   __init__zImageTracer.__init__<   s    15^ #$8%h0CTJG x)<bA../1K1KM..,h.E.EG DK		w ?%)YY[ / * ! #"G#  ^WY\]]^sA   C A0C 	C 'C CC CC 	C<C77C<ctxr	   Nc                     || _         y)u   명시적 context 설정 — thread pool worker에서 사용.

        StepRunner 메인 스레드에서 context를 캡처한 후
        worker thread에 전달할 때 사용.
        N)r%   )r2   r7   s     r   set_contextzImageTracer.set_context[   s     "%r   c                 f    	 ddl m}  |       }|r|S 	 | j                  xs i S # t        $ r Y w xY w)un  context 조회 — **thread-local 우선**, 없으면 명시적, 없으면 빈 dict.

        ★순서를 뒤집었다 (Codex 리뷰 2026-08-07). `_explicit_context` 는
        process 하나에 공유되고 **해제되지 않는다**(`image_steps` 가 설정만
        한다). 그것을 먼저 읽으면, 스텝 이름은 맞게 나와도 Opik 의
        thread_id·tags·프로젝트 묶음이 **직전 스텝 것으로** 붙는다.
        thread-local 이 있으면 그것이 지금 이 실행의 진실이고, worker
        thread 처럼 thread-local 이 빈 자리에서만 명시적 값이 쓰인다.
        r   _get_thread_opik_meta)app.modules.llm.llm_clientr<   r-   r%   )r2   r<   thread_metas      r   _get_contextzImageTracer._get_contextc   sG    	H/1K""  %%++  		s   $ 	00c                 "    | j                         S )u   현재 context 를 밖에서 읽는다 (gpt-image primitive 가 step 이름을
        끌어올 때 사용). Opik client 가 없어도 context 는 있을 수 있으므로
        `_client` 유무와 무관하게 돌려준다.)r?   r2   s    r   get_contextzImageTracer.get_contextv   s       ""r   stepr   promptref_image_idsoutput_image_idstatuserrorduration_msparamsextra_metadataproviderusage
total_costc                     | j                   sy	 | j                         }|j                  d      xs |j                  d      }||g |j                  d      r j                   fd|d   D               d|i}|r||d<   |	r|	|d<   d	|i}|r||d
<   d}|r|dd |d<   d|dd dd}|||d}|
r|j	                  |
       d|i|	xs i }|
r|j	                  |
       d}ddlm} t        |dd      rddlm	}m
} |	xs i j                  d      xs |} |||||xs t        |      |dk(  rdn|      } |       }|] | j                   j                  d!|j                  | d| d||xs t        |      ||||d	t        ||      }|j                          y| |}| j                   j!                  | d| ||| ||      }|d||xs t        |      |||d}|||d<   |j	                  t        ||              |j                  d!i |}|j                          |j                          y# t"        $ r }t$        j'                  d |       Y d}~yd}~ww xY w)"u  이미지 생성 결과를 Opik trace로 기록.

        Args:
            extra_metadata (Phase 4 iter 7 review I1): trace_meta (project_id /
                episode_id / scene_index / shot_index / still_id / entity_id 등)
                를 trace + span metadata 에 병합. DB log 와 별도로 Opik trace
                에서도 fan-out 단위 추적이 가능하도록.
            usage / total_cost (2026-08-27, 감사 2-B·0-D):
                ★**litellm 을 우회하는 호출은 여기서 안 넘기면 토큰·비용이
                 통째로 사라진다.** 실측: `still_recipe_fix_rejudge_
                 openrouter:xai/grok4.6` 등 137 span 이 `usage={}` ·
                 `total_estimated_cost=None` 이었고, 그 때문에
                 ① 비용 집계에서 빠지고(0-D 잔여)
                 ② 캐시 적중률을 못 재서 감사표가 「캐시 0%」로 읽었다.
                 「0」이 「없다」가 아니라 **재는 칸이 비어 있던 것**이다.
        N	thread_id
session_idtagsc              3   ,   K   | ]  }|vs|  y wN ).0trR   s     r   	<genexpr>z"ImageTracer.log.<locals>.<genexpr>   s     D{!atmA{s   	rD   rE   rJ   rG   image_id  rH   ImageGenErrorr   )exception_typemessage	traceback)r   rI   rC   rI   r   r   opik_trace_v2_enabledF)build_axis_tagscurrent_tracerolesuccess)rC   opr   rL   rG   u    · llm)	trace_idnametyper   rL   inputoutputmetadatarR   /)rg   ri   rj   rk   rR   rP   
error_info)rg   rh   r   rL   ri   rj   rk   z&ImageTracer.log failed (non-fatal): %srU   )r$   r?   getextendupdater(   r   r)   app.modules.llm.opik_tracer`   ra   r   spanuid_usage_kwargsendtracer-   r0   debug)!r2   rC   r   rD   rE   rF   rG   rH   rI   rJ   rK   rL   rM   rN   r7   rQ   trace_inputtrace_outputrm   trace_metadataspan_metadata	span_tagsr   r`   ra   rd   axisparentsrv   span_kwargsr5   rR   s!                                   @r   logzImageTracer.log|   s   @ ||g	H##%C
 -F1FJ%=DwwvDs6{DD#V,K/<O,(.H%,4f+=L+:Z(J(-dsW%0?ERVSVKfhi
 {DN %%n5 +KJFLbJM$$^4 .2I 1x!8%@4 l''/74&"(*Il56I#)Y#64F
 '%))) !' "t4w/"%!)!@\%-@),!.T (z:A EEG  !	LL&&vQug&!#'$% ' E $;U(;$&)+K $&/F#}UJ?@

)[)AEEGIIK 	HLLA3GG	Hs   F
H2 BH2 2	I;IIc              #     K   t               }t        j                         }	 | 	 |j                  s(t        t        j                         |z
  dz        |_        | j                  |||||j                  |j                  |j
                  |j                  |	       y# t        $ r}t	        |      |_        d|_         d}~ww xY w# |j                  s(t        t        j                         |z
  dz        |_        | j                  |||||j                  |j                  |j
                  |j                  |	       w xY ww)u   Context manager — 생성 후 결과를 set_output으로 기록.

        with tracer.span("scene_image_gen", ...) as s:
            img, elapsed = generate(...)
            s.set_output(image_id="xxx", duration_ms=elapsed)
        rH   N  )	rC   r   rD   rE   rF   rG   rH   rI   rJ   )_SpanContexttime	monotonicr-   str_error_status_duration_msintr   	_image_id)	r2   rC   r   rD   rE   rJ   r7   t0r5   s	            r   rr   zImageTracer.span  s     n^^	I ###&(82(='E#F HHv+ #{{jj,,    	SCJ!CK	
 ###&(82(='E#F HHv+ #{{jj,,  s5   EB A7E	C'B??CC A8D??E)
NNrc   Nr   NNNNN)NN)__name__
__module____qualname____doc__r6   r   r   r9   r?   rB   r   r   r   r   floatr   r   rr   rU   r   r   r   r   )   s   $^>%x~ %$ %,d ,&#T # .2)-#+/37"&*.&*JHJH JH 	JH
  S	*JH "#JH JH }JH JH c3h(JH !c3h0JH 3-JH S#X'JH UOJH 
JHX  .2+/!! ! 	!
  S	*! c3h(! !r   r   c            
       D    e Zd ZdZdZd Z	 	 d
dee   dededee   fd	Z	y)r   u5   span() context manager 내부에서 결과 설정용.r   r   r   r   c                 <    d | _         d| _        d | _        d| _        y )Nrc   r   r   rA   s    r   r6   z_SpanContext.__init__2  s    (,%%)!"r   NrY   rI   rG   rH   c                 <    || _         || _        || _        || _        y rT   )r   r   r   r   )r2   rY   rI   rG   rH   s        r   
set_outputz_SpanContext.set_output8  s    !'r   )Nr   rc   N)
r   r   r   r   	__slots__r6   r   r   r   r   rU   r   r   r   r   -  sD    ?BI# MNCG8C= c 3;C=r   r   
project_id
episode_idc                     i 	 ddl m}   |        }|dD ]  }t        ||d      }|||<    t        fdt        D              sL	 ddlm	}  |       xs i }t        D ].  }j                  |      r|j                  |      s'||   |<   0 	 S S # t        $ r }t        j                  d|       Y d}~d}~ww xY w# t        $ r!}t        j                  d|       Y d}~S d}~ww xY w)	u;  기록용 신원 메타를 **주변 scope 에서 직접 읽는다**.

    근거 두 겹을 이 순서로 본다:

    1. **이미지 포착 scope** — `still_id`·`shot_index` 까지 아는 가장 정확한
       근거. 이미지를 만드는 자리에만 열려 있다.
    2. **스텝 컨텍스트**(StepRunner 의 thread-local) — 신원 두 칸만 메운다.
       검색처럼 이미지를 포착하지 않는 호출은 1번 scope 를 열 이유가 없어,
       그것만 보면 `project_id`/`episode_id` 가 빈 채로 남는다. 실제로 검색
       호출 기록이 그래서 비었고, **프로젝트 단위로 "이 검색이 살아 있나"를
       물을 수 없었다.**

    ★인자로 받지 않는 이유 (2026-08-07): 이미지 저수준 함수는 호출 사이트가
    여럿이라, 인자로 만들면 배선을 빠뜨린 사이트가 **조용히 기록 없이** 돈다.
    실제로 gpt-image 경로 전체가 Opik·llm_call_log 양쪽에 한 줄도 안 남아
    "작업이 살아 있나"를 볼 근거가 없었다. StepRunner 는 **모든 스텝**에서
    컨텍스트를 설정하므로, 호출 사이트를 하나씩 배선하는 것과 달리 2번은
    빠뜨릴 자리가 없다.

    ★2번의 한계: thread-local 이라 **worker thread 에는 자동 전파되지 않는다.**
    지금 검색 호출자는 한 곳이고 병렬이 아니라 닿지만, 나중에 ThreadPool 로
    나누면 신원이 다시 빈다. 그때는 1번처럼 명시 전파가 필요하다
    (`bind_current_generation_context` 가 그 자리의 본보기다).
    r   )current_contextN)r   r   still_idscene_index
shot_index	entity_idstageu#   ambient_call_meta 조회 실패: %sc              3   @   K   | ]  }j                  |        y wrT   )rn   )rV   keymetas     r   rX   z$ambient_call_meta.<locals>.<genexpr>m  s     7txx}s   r;   u7   ambient_call_meta 스텝 컨텍스트 조회 실패: %s)"app.services.image_capture.contextr   r)   r-   r0   rw   all_IDENTITY_KEYSr=   r<   rn   )r   gctxr   valuer5   r<   	step_metar   s          @r   ambient_call_metar   F  s    2 DAF Kc40$ %DI	K 777	YH-/52I%xx}s); )#DI &
 K4K  A:C@@A  	YLLRTWXXK	Ys@   #B B 	+C 5C 
C 	B?B::B?	C,C''C,fallbackambientc                    |j                  d      }|rt        |      S 	 ddlm} ddlm}  |       xs i j                  d      xs g }|D ]A  }t        |      }|j                  |      s |t        |      d j                         }|s?|c S  	 | xs dS # t        $ r }	t        j                  d|	       Y d}	~	*d}	~	ww xY w)	u'  스텝 이름 — capture scope 의 stage > thread-local Opik 태그 > fallback.

    ★ImageTracer 의 explicit context 는 **쓰지 않는다**. 그것은 process 하나에
    공유되고 지워지지 않아서, 그 mixin 을 쓰지 않는 스텝의 호출이 **직전 스텝
    이름으로** 기록된다 (Codex 리뷰 2026-08-07).

    ★2026-08-28 실측: thread 태그를 `tags[0]` 로 **그대로** 집어서 두 군데가
    깨졌다. 그 태그는 이미 축 접두사가 붙은 `step:shot_conti_light` 라서

      - DB `llm_call_log.step_name` 이 `step:shot_conti_light` 가 되고
        (다른 행은 `scene_image_pipeline` 처럼 맨 이름이라 **같은 스텝이
        두 이름으로 쪼개진다**)
      - `build_axis_tags(step=...)` 가 접두사를 한 번 더 붙여
        **`step:step:shot_conti_light`** 가 된다

    이번 완주 주행 Opik 실측 = 이중 접두사 span **6건** / 세 자리
    (`shot_conti_light` 3 · `background_render` 2 · `floor_plan_render` 1).

    그래서 **`step:` 축 태그를 골라 접두사를 벗겨서** 돌려준다. 순서에 기대지
    않는다 — 종전에는 `_AXIS_ORDER` 가 step 을 맨 앞에 두는 덕에 우연히 맞고
    있었고, 축 순서가 바뀌면 모델명·제공자명이 스텝 이름이 됐다.
    r   r   r;   )STEP_TAG_PREFIXrR   Nu#   resolve_step_name 조회 실패: %s	image_gen)rn   r   r=   r<   rq   r   r   lenstripr-   r0   rw   )
r   r   r   r<   r   rR   tagtextbarer5   s
             r   resolve_step_namer   z  s    . KK E5zAD>%'-2226:@bCs8D/C01288:K  "{"  A:C@@As*   AB ,B B B 	C  B;;C rM   rN   c           	      >   i }t        | t              rP| j                         D ci c].  \  }}t        |t        t        f      rt        |t
              s||0 }}}|r||d<   t        |t        t        f      rt        |t
              st	        |      |d<   |S c c}}w )u  Opik span 이 받는 `usage` / `total_cost` 칸 — **있는 것만 넣는다.**

    ★0 을 채우지 않는다. provider 가 안 알려 준 값을 0 으로 적으면
     「토큰을 안 썼다」로 읽히고, 그 0 이 합산에 섞이면 비용이 실제보다
     작게 나온다. 없으면 **칸 자체를 안 만든다** — 그래야 나중에
     「못 잰 것」과 「0 이었던 것」을 가를 수 있다.

    ★키 이름은 litellm 이 쓰는 것과 맞춘다(`prompt_tokens` /
     `completion_tokens` / `total_tokens`). 이름이 다르면 감사 도구가
     같은 칸으로 못 읽고, 지금 openrouter 137 span 이 겪는 것과 같은
     「기록은 있는데 안 세어지는」 자리가 또 생긴다.
    rM   rN   )
isinstancedictitemsr   r   bool)rM   rN   outkvkeeps         r   rt   rt     s      C%!& LAa#u.z!T7J 1 LCL*sEl+J5!*-LJLs   3Brespc                 ^   t        | dd      }|i S i }dD ]<  }t        ||d      }t        |t        t        f      s't        |t              r8|||<   > t        |dd      }|t        |dd      nd}|t        |dd      }t        |t        t        f      rt        |t              s||d<   |S )u  OpenAI 계열 SDK 응답에서 토큰 칸을 꺼낸다 — **캐시 칸까지**.

    ★캐시 적중이 여기 들어 있는데 지금까지 아무도 안 꺼냈다. 그래서
     감사표가 판정 루프 캐시를 「0%」로 적었고, 나는 그것을 「캐시가 안
     붙는다」로 읽을 뻔했다. 실측하면 grok 은 2,304/2,326 = 99% 적중이다.

    없는 칸은 **안 만든다**(`_usage_kwargs` 와 같은 규칙).
    rM   N)prompt_tokenscompletion_tokenstotal_tokensprompt_tokens_detailscached_tokens)r)   r   r   r   r   )r   ur   rg   r   detcacheds          r   usage_of_openai_responser     s     	gt$Ay	CFAtT"a#u&z!T/BCI G
 !,d
3C47OWS/40F~OT2&3,'
640H%OJr   )		ref_count	operationrE   rH   to_dboutput_textrL   rM   rN   rC   rD   rG   rI   r   r   r   rE   rH   r   r   rL   c                 v   d}|
r	 ddl m}  ||||dk(  r|nd|||j                  d      |j                  d      || |rt        |      nd|xs i j                  d      |xs i j                  d      |	|j	                         D ci c]  \  }}|d	vr|| c}}xs d
      }	 t               j                  | |||rt        |      nd||	|||d||||       |S c c}}w # t
        $ r }t        j                  d|       Y d}~bd}~ww xY w# t
        $ r!}t        j                  d|       Y d}~|S d}~ww xY w)u  provider 호출 하나를 DB(llm_call_log)+Opik 양쪽에 남기고 **호출 ID 를 돌려준다**.

    이미지·검색·텍스트 어느 쪽이든 **litellm 을 우회하는 직접 호출**은 여기를
    거쳐야 Opik 에 남는다(litellm 경로는 콜백으로 자동이다).

    성공도 실패도 남는다. 어느 쪽 기록이 터져도 본 작업은 계속되어야 하므로
    전부 삼킨다. 돌려준 ID 는 capture 의 ``generation_call_id`` 로 넘겨 **DB
    호출 기록과 포착 자산을 묶는다**.

    ``to_db=False`` — 이미 자기 자리에서 ``log_llm_call`` 을 부르는 호출자용
    (Opik 만 보탠다). 두 번 세지 않기 위한 것이다.

    ``output_text`` — 성공 기록의 산출 표시. ★기본값을 이미지로 두지 않는다
    (Codex 리뷰 2026-08-07): 전에는 모든 성공에 ``"[image generated]"`` 를
    박아서 **검색 호출까지 DB 에서 이미지 생성으로 읽혔다.** 이미지 호출자가
    자기 표식을 넘긴다.

    ``provider`` — Opik span 의 provider. 모델 이름으로 못 가르는 경우
    (검색 orchestrator 는 ``gpt-5.6`` 이라 이미지 규칙에 안 걸린다) 호출자가
    명시한다.
    Nr   )log_llm_callrc   r   r   r   r   r   )
model_nameuser_promptr   rG   rI   r   r   operation_type	step_namereference_image_idsinput_tokensoutput_tokenserror_messagerk   u@   record_provider_call: llm_call_log 기록 실패 (non-fatal): %s)r   rb   )rC   r   rD   rE   rG   rH   rI   rJ   rK   rL   rM   rN   u,   record_provider_call: Opik 기록 실패: %s)app.modules.llm.llm_loggerr   rn   listr   r-   r0   r1   get_image_tracerr   rw   )rC   r   rD   rG   rI   r   r   r   rE   rH   r   r   rL   rM   rN   call_idr   r   r   r5   s                       r   record_provider_callr     sw   N "G)	?" "+1Y+>KD'88L188L1(;HD$7d $kr..?${//0CD#+/::< D<41a&BB Q$< D LGK7GNJ1>$}-D#!*I>! 	 	
  N=D  		 NNR 		2  JCSIINJsB   B C" C$C" %5D C" "	D+DD	D8D33D8)rL   model_of	prompt_ofr   c                 .     ddl  fd}|S )u.  litellm 을 우회하는 provider 호출 함수를 감싸 **성공·실패를 다** 남긴다.

    urllib 로 provider 를 직접 치는 자리(vision 검증·변형 추천·PDF 검증·
    legacy 구조화 출력)는 호출 흐름이 저마다 달라 본문 안에 기록을 심으면
    빠뜨리기 쉽다. 감싸면 return 경로가 몇 개든 한 번만 남는다.

    ``model_of`` / ``prompt_of`` 는 감싼 함수의 인자에서 모델명·프롬프트를
    꺼내는 함수다(사이트마다 인자 모양이 달라 호출자가 준다).
    r   Nc                 F     j                          fd       }|S )Nc                     t               }t        |      }	 
rt         
| i |      nd}	 rt         | i |      nd}t	        j
                         }	  	| i |}t        |||dt        t	        j
                         |z
  dz        |	       |S # t        $ r d}Y ww xY w# t        $ r d}Y rw xY w# t        $ rG}t        |||dt        t	        j
                         |z
  dz        |t        |      d d 	        d }~ww xY w)	Nr   r   rH   r   rZ   )	rC   r   rD   rG   rI   r   r   rL   rH   rc   )	rC   r   rD   rG   rI   r   r   rL   r   )r   r   r   r-   r   r   r   r   )argskwargsr   rC   r   rD   startedresultr5   fnr   r   r   r   rL   s            r   wrapperz*traced_call.<locals>.deco.<locals>.wrapperV  s/   $&D$Y5D":BHd5f56	<EY7782 nn&GT,V, !vi!1G!;t CDY'	)
 M+  "!"  
  $U6' #T^^%5%?4$G HXc(4C.	*
 s;   B B) B: B&%B&)B76B7:	D
ADD
)wraps)r   r   	functoolsr   r   r   r   rL   s   ` r   decoztraced_call.<locals>.decoU  s*    			 	 
	8 r   )r   )r   rL   r   r   r   r   r   s   ````` @r   traced_callr   B  s    "  @ Kr   _tracerc                      t         %t        5  t         
t               a ddd       t         S t         S # 1 sw Y   t         S xY w)u+   싱글턴 ImageTracer 반환 (thread-safe).N)r   _tracer_lockr   rU   r   r   r   r   }  s5     %-  N7N  Ns	   2A )$r   logging	threadingr   
contextlibr   typingr   r   r   r   	getLoggerr   r0   r&   r.   ImportErrorr   r   r   r   r   r   r   r   rt   r   r   r   r   r   r   __annotations__Lockr   r   rU   r   r   <module>r      s      % , ,			8	$	O   A AH , .14S> 1h'# '#S#X '#3 '#TDcN# 
#s(^83 4S> F #)-!%"&*"&!d
d d 	d
 d d sCx.d d }d DI&d C=d d #d smd DcN#d  !d" c]#dT #!%33 sm3 #3n "&+	 %y~~+ S  		s   D+ +D32D3