
    ,j                       U 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
mZmZ ddlmZ  ej        e          Zdaded	<    ed
dh          Z ej        d          Z e            ZdMdZdNdZe G d d                      ZdZdZdZded<   dZ e G d d                      Z!dOdPd!Z"dd"dQd(Z#	 dOdRd-Z$ e%d.d/d0d1d2d3d4d5d
d67
  
         e%d8d9d8d9d2d3d
d6:           e%d;d<d;d<d2d3d
d6:           e%d=d>d=d>d2d3d
d6:           e%d4d5dd?d2d3d4d5d
d67
  
         e%dd?dd?dd?dd@A           e%dd?dBdCd2d3dd?d
dd@D          dEZ&dFedG<   dSdHZ'dSdIZ(dTdKZ)dUdLZ*dS )VuO  Credits tracking for Nous inference API responses.

Parses x-nous-credits-* (and optional x-nous-tool-pool-*) headers from
inference responses into a validated CreditsState dataclass.  Provides
depletion detection (paid_access), subscription-cap used_fraction, and
warn-once schema-version gating.  This is the hardened parser used by all
live consumers (run_agent, tui_gateway) — not a dev-only shim.

Header schema (x-nous-credits-* family):
    x-nous-credits-version                    contract/schema version
    x-nous-credits-remaining-micros           total remaining balance (micros)
    x-nous-credits-remaining-usd              same, formatted USD string
    x-nous-credits-subscription-micros        subscription balance (SIGNED; may be negative/debt)
    x-nous-credits-subscription-usd           same, formatted USD string
    x-nous-credits-subscription-limit-micros  subscription cap (PAIRED/optional)
    x-nous-credits-subscription-limit-usd     same, formatted USD string (PAIRED/optional)
    x-nous-credits-rollover-micros            rolled-over balance (micros)
    x-nous-credits-purchased-micros           purchased balance (micros)
    x-nous-credits-purchased-usd              same, formatted USD string
    x-nous-credits-denominator-kind           "subscription_cap" | "none"
    x-nous-credits-paid-access                "true" | "false" (STRING!)
    x-nous-credits-disabled-reason            reason string (header omitted when null)
    x-nous-credits-as-of-ms                   server-side timestamp (ms epoch)

Tool-pool headers use a SEPARATE prefix:
    x-nous-tool-pool-micros                   tool-pool balance (micros)
    x-nous-tool-pool-gated-off                "true" | "false" (STRING!)

Money is handled as micros ints only; *_usd values are preserved verbatim as
the raw strings the server sent (never re-parsed to float).
    )annotationsN)	dataclass)AnyMappingOptional)is_truthy_valueFbool_version_warning_emittedsubscription_capnonez^-?\d+\.\d{2}$valuer   returnc                    | t           S 	 t          t          |                     S # t          t          f$ r
 t           cY S w xY w)u;  Parse a header value to an exact int (money-safe).

    The contract guarantees every ``*_micros`` field is an integer string —
    we parse with ``int()`` directly, NOT ``int(float(...))``, to avoid float-
    precision loss above 2**53 that would silently corrupt large money values.

    Returns the parsed int, or ``_SENTINEL`` if the value is not a valid integer
    string (including float-shaped strings like "1.5").  The sentinel lets callers
    detect the failure and return None from the overall parse (fail-hard-on-bad-
    input, not silently coerce).
    )	_SENTINELintstr	TypeError
ValueErrorr   s    :/home/ubuntu/.hermes/hermes-agent/agent/credits_tracker.py	_safe_intr   >   sR     }3u::z"   s   ' AAOptional[str]c                X    | dS t          t                              |                     S )zCReturn True iff value is a non-None string matching ^-?\d+\.\d{2}$.NF)r	   _USD_REmatchr   s    r   _validate_usdr   S   s&    }ue$$%%%    c                  p   e Zd ZU dZdZded<   dZded<   dZded<   dZded	<   dZ	ded
<   dZ
ded<   dZded<   dZded<   dZded<   dZded<   dZded<   dZded<   dZded<   dZded<   dZded<   dZded<   dZded<   dZded <   ed'd"            Zed(d#            Zed'd$            Zed)d&            ZdS )*CreditsStatezAFull credits state parsed from x-nous-credits-* response headers.r   r   versionremaining_micros r   remaining_usdsubscription_microssubscription_usdNOptional[int]subscription_limit_microsr   subscription_limit_usdrollover_microspurchased_microspurchased_usdtool_pool_microsFr	   tool_pool_gated_offr   denominator_kindTpaid_accessdisabled_reasonas_of_ms        floatcaptured_atfrom_headerr   c                    | j         dk    S Nr   )r4   selfs    r   has_datazCreditsState.has_datat   s    !##r   c                d    | j         st          d          S t          j                    | j        z
  S )Ninf)r:   r3   timer4   r8   s    r   age_secondszCreditsState.age_secondsx   s,    } 	 <<y{{T---r   c                    | j          S )u  True when the account has lost paid access.

        Keyed off ``paid_access == False`` ONLY — never ``remaining_micros == 0``,
        which would give a false positive whenever the balance is zero but access
        is still live (e.g. subscription renewal pending).
        )r/   r8   s    r   depletedzCreditsState.depleted~   s     ###r   Optional[float]c                    t          | j        t                    sdS | j        dk    rdS | j        | j        z
  }t	          dt          d|| j        z                      S )u  Fraction of the subscription cap consumed, in [0.0, 1.0].

        Computable only when ``subscription_limit_micros`` is a truthy (non-zero,
        non-None) int.  Guarded on the LIMIT FIELD, not ``denominator_kind`` —
        the limit field is the real denominator; ``denominator_kind`` is metadata.
        Returns None when there is no computable denominator (no limit, or limit==0).
        Nr   r2         ?)
isinstancer'   r   r$   maxmin)r9   useds     r   used_fractionzCreditsState.used_fraction   sc     $8#>> 	4)Q..4-0HH3C(F!FGGHHHr   r   r	   )r   r3   )r   rA   )__name__
__module____qualname____doc__r    __annotations__r!   r#   r$   r%   r'   r(   r)   r*   r+   r,   r-   r.   r/   r0   r1   r4   r5   propertyr:   r>   r@   rH    r   r   r   r   ]   s        KKGM     /33333,00000OM %%%%%"""""K%)O))))HKK$ $ $ X$ . . . X.
 $ $ $ X$ I I I XI I Ir   r   stickyi@  ))g      ?info2   )g      ?warnK   )g?rT   Z   z"tuple[tuple[float, str, int], ...]CREDITS_USAGE_BANDSzcredits.usagec                  d    e Zd ZU dZded<   dZded<   dZded<   dZd	ed
<   dZded<   dZ	ded<   dS )AgentNoticeu  A structured, driver-agnostic out-of-band notice.

    The agent fires these via ``AIAgent.notice_callback`` (and clears them via
    ``notice_clear_callback``); each driver renders it its own way — the TUI as a
    status-bar override, the CLI as a console line, etc. v1 credits notices are all
    ``kind="sticky"``; ``kind``/``ttl_ms`` are kept fully expressive so a future
    config/slash-command can switch them to TTL without touching the policy (a
    single default seam — see L4).
    r   textrR   levelrQ   kindNr&   ttl_msr   keyid)
rJ   rK   rL   rM   rN   r[   r\   r]   r^   r_   rP   r   r   rY   rY      s|           IIIED F    CBr   rY   r"   modelr   base_urlc                L   | sdS |                      d          rdS |sdS 	 ddlm}m} |                    d          }|                     d          r|dd	                             d          }|                    |          }|sdS  || |          S # t          $ r Y dS w xY w)
u  Return True when *model* is a Nous free-tier model, using ONLY local data.

    Two signals, both zero-network:

    1. The ``:free`` suffix — the canonical Nous free SKU marker (e.g.
       ``nvidia/nemotron-3-ultra:free``). Free by construction on the API side
       (spend is forced to 0 for ``:free`` ids).
    2. A peek into the in-process pricing cache in ``hermes_cli.models``
       (populated when the model picker fetched ``/v1/models`` pricing for
       *base_url*). PEEK ONLY — a cache miss never triggers a fetch. This is
       CLI/TUI-session best-effort: gateway sessions never run the picker's
       pricing fetch, so suppression there rests entirely on the ``:free``
       suffix (which all Nous free SKUs carry).

    Fail-open to False (the depleted notice still shows) on any error: wrongly
    showing the warning is recoverable noise; wrongly hiding it on a paid model
    would mask a real billing block.
    Fz:freeTr   )_is_model_free_pricing_cache/z/v1N)endswithhermes_cli.modelsrc   rd   rstripget	Exception)r`   ra   rc   rd   r^   pricings         r   is_free_tier_modelrm      s    &  u~~g t uDDDDDDDD
 ooc""<< 	'crc(//#&&C $$S)) 	5~eW---   uus   A&B 	B 
B#"B#)model_is_freestatelatchdictrn   #tuple[list[AgentNotice], list[str]]c          
     6   g }g }| j         }t          d         d         }|||k     rd|d<   |d         }d}|t          D ]}	||	d         k    r|	}| j        dk    rd}| j        dk    o|duo|dk    o
| j        dk    }
| j         }|                    d          }|r|d         r|d	         nd}||k    rt          |v r4|                    t                     |                    t                     |r| j	        pd
}|d         }|                    t          |dk    rdnd d| d| d|t          t          t                               |                    t                     ||d<   |
rOd|vrK|                    t          d| j         ddt          dd                     |                    d           n0d|v r,|
s*|                    d           |                    d           |o| }|rFd|vrB|                    t          ddt          dd                     |                    d           n_d|v r[|sY|                    d           |                    d           |s-|                    t          dddt          dd                     ||fS )u  Reconcile credits notices against the latch. Mutates ``latch`` IN PLACE.

    latch = {"active": set[str], "seen_below_90": bool, "usage_band": Optional[int]}.

    ``model_is_free``: True when the session's active model is a Nous free-tier
    model (see :func:`is_free_tier_model`). Suppresses the ``credits.depleted``
    notice — a depleted account on a free model can keep inferencing, so the
    error banner is noise (and confuses free-tier users who never had credits).
    Suppression does NOT emit the "restored" success notice; that fires only on
    a genuine ``paid_access`` flip back to True.

    Returns ``(to_show: list[AgentNotice], to_clear: list[str])``.
    Caller emits to_clear FIRST, then to_show.

    Pure function — no I/O, no agent/run_agent imports.
    r   NTseen_below_90activer   rC   
usage_band   ?   rT   u   ⚠u   •z	 Credits u   % used · $z cap)rZ   r[   r\   r^   r_   zcredits.grant_spentu   • Grant spent · $z top-up leftrR   zcredits.depletedu2   ✕ Credit access paused · run /credits to top uperroru   ✓ Credit access restoredsuccessttlzcredits.restored)rZ   r[   r\   r]   r^   r_   )rH   rW   r*   r.   r/   rj   CREDITS_USAGE_KEYappenddiscardr(   rY   CREDITS_NOTICE_KINDaddr+   CREDITS_RESTORED_TTL_MS)ro   rp   rn   to_showto_clearuf_lowest_bandru   current_bandband
grant_conddepleted_cond
shown_bandtarget_band_cap_usd_levelshow_depleteds                    r   evaluate_credits_noticesr      s|   , "$GH		B 'q)!,L	~"|++!%o8_F 6:L	~' 	$ 	$DT!W}}# !!"44 	'dN	'#I	' "Q&	  ))M <((J&2Xu_7MX,q//TXKj  &&OO-...NN,---" 3:sH!!_FNN%+v%5%5EE5qq;qqckqqq ,)(     JJ())))l  .+699ME,?MMM()(  	
 	
 	
 	

())))	&	(	(	(-...,--- "7-&7M +699I(&%  	
 	
 	
 	

%&&&&	v	%	%m	%*+++)*** 	 NN5#2*)  	 	 	 Xr   headersMapping[str, str]providerOptional[CreditsState]c                   	 t          d | D                       sdS d |                                 D                                 d          }|dS t          |          }|t          u rdS |dk    r,|dk    r$t
          sdat                              d|           dS d7fd}d7fd} |d          }|t          u rdS  |d          }|t          u rdS  |d          }|t          u rdS  |d          }	|	t          u rdS                     d          }
|
d}n"t          |
          }|t          u s|dk     rdS |} |d          }|t          u rdS                     dd          }t          |          sdS                     dd          }t          |          sdS                     dd          }t          |          sdS                     d          }                    d          }d}d}|9|7t          |          }|t          u rdS |dk     rdS t          |          sdS |}|}                    dd          }|t          vrdS dv r9d         
                                                                }|dvrdS |dk    }nd}d v r9d          
                                                                }|dvrdS |dk    }nd!}                    d"          }t          d8i d#|d$|d%|d&|d'|d(|d)|d*|d+|	d,|d-|d.|d/|d0|d1|d2|d3t          j                    d4dS # t          $ r  t                              d5d6           Y dS w xY w)9a-  Parse x-nous-credits-* (and x-nous-tool-pool-*) headers into a CreditsState.

    Returns None (miss) on ANY of:
    - No ``x-nous-credits-version`` header present.
    - Version != 1 (> 1 also emits a one-time logger.warning).
    - Any ``*_micros`` field is non-integer, or negative for a non-subscription field.
    - Any ``*_usd`` field doesn't match ``^-?\d+\.\d{2}$``.
    - ``denominator_kind`` is not in {"subscription_cap", "none"}.
    - ``paid_access`` / ``tool_pool_gated_off`` is not exactly "true"/"false".
    - ``as_of_ms`` is not a valid integer.
    - Any unexpected exception.

    Fail-open on the subscription_limit pair: a half-pair (only -micros or only
    -usd present) is treated as both-absent; the overall parse STILL SUCCEEDS
    but with subscription_limit_micros/usd both None.
    c              3  F   K   | ]}|                                 d k    V  dS )x-nous-credits-versionNlower).0ks     r   	<genexpr>z(parse_credits_headers.<locals>.<genexpr>  s0      JJQ17799 88JJJJJJr   Nc                >    i | ]\  }}|                                 |S rP   r   )r   r   vs      r   
<dictcomp>z)parse_credits_headers.<locals>.<dictcomp>  s&    <<<DAq17799a<<<r   r   ry   TuA   credits header version %d unsupported, ignoring — update Hermesr^   r   r   r   c                                         |           }t          |          }|t          u rt          S |dk     rt          S |S r7   rj   r   r   r^   rawvallowereds      r   _req_nonnegz*parse_credits_headers.<locals>._req_nonneg  sD    ++c""CC..Ci  Qww  Jr   c                p                         |           }t          |          }|t          u rt          S |S )Nr   r   s      r   _req_intz'parse_credits_headers.<locals>._req_int  s5    ++c""CC..Ci  Jr   zx-nous-credits-remaining-microsz"x-nous-credits-subscription-microszx-nous-credits-rollover-microszx-nous-credits-purchased-microszx-nous-tool-pool-microsr   zx-nous-credits-as-of-mszx-nous-credits-remaining-usdr"   zx-nous-credits-subscription-usdzx-nous-credits-purchased-usdz(x-nous-credits-subscription-limit-microsz%x-nous-credits-subscription-limit-usdzx-nous-credits-denominator-kindr   zx-nous-credits-paid-access)truefalser   zx-nous-tool-pool-gated-offFzx-nous-credits-disabled-reasonr    r!   r#   r$   r%   r'   r(   r)   r*   r+   r,   r-   r.   r/   r0   r1   r4   r5   u9   credits ▸ parse_credits_headers raised (fail-open miss)exc_info)r^   r   r   r   rP   )anyitemsrj   r   r   r
   loggerwarningr   _VALID_DENOMINATOR_KINDSstripr   r   r=   rk   debug)r   r   version_rawversion_valr   r   r!   r$   r)   r*   _tp_rawr,   _tp_valr1   r#   r%   r+   sub_limit_micros_rawsub_limit_usd_rawr'   r(   lmr.   pa_rawr/   tpgo_rawr-   r0   r   s                               @r   parse_credits_headersr     s   ,g JJ'JJJJJ 	4 =<GMMOO<<< kk":;;4,,)##4!Q'?+/(W   4	 	 	 	 	 		 	 	 	 	 	 ';'HIIy((4&h'KLL)++4%+&FGGi''4&;'HIIy((4 ++788? ((G)##w{{t&;899y  4  $BBGG]++ 	4";;'H"MM-.. 	4$BBGG]++ 	4
  '{{+UVV#KK(OPP37!04+0A0M/00BYtAvvt !233 t(*%%6" #;;'H&QQ#;;;4 (7229:@@BBHHJJF...t F*KKK'722;<BBDDJJLLH000t"*f"4"' "++&FGG 
 
 
K
--
 (-
 !4 3	

 .-
 '@&?
 $:#9
 ,O
 .-
 (-
 .-
 !4 3
 .-
 $
 ,O
  X!
" 	#
$ %
 	
*     	P[_```tts   L: 5L: L: /0L: ! L: L: L: /L: 8L: ?L: %L: >%L: %%L: A
L: L:  L: 1#L: 4L: =L: A.L: :&M$#M$iz30.34iz18.00i -1z20.00i K z12.34T)
r!   r#   r$   r%   r'   r(   r*   r+   r.   r/   i z10.00)r!   r#   r$   r%   r'   r(   r.   r/   i@KL z5.00i z2.000.00out_of_credits)r!   r#   r$   r%   r*   r+   r/   r0   iz-5.00)r!   r#   r$   r%   r'   r(   r*   r+   r.   r/   r0   )healthy	sub_50pct	sub_75pct	sub_90pctgrant_exhaustedr@   debtzdict[str, dict]_DEV_FIXTURESc                 t   t          t          j                            d                    sdS t          j                            dd                                          } | sdS | }t          j        j        | v sd| v rc	 t          | dd          5 }|                                                                }ddd           n# 1 swxY w Y   n# t          $ r Y dS w xY wt                              |                                          }|sdS d	d
d|}t          di |dt          j                    dS )ub  Return a fixture CreditsState for HERMES_DEV_CREDITS_FIXTURE, or None.

    The env value is a state name, OR a path to a file whose contents are a state
    name (re-read each call → flip states live without a restart). Unknown name /
    "clear" / "none" / unset → None (normal behaviour). Throwaway test scaffolding.

    Hard prod-leak guard: a fixture applies ONLY when the dev flag HERMES_DEV_CREDITS
    is also on, so a stray HERMES_DEV_CREDITS_FIXTURE (leaked into a shell profile, a
    container env, a launch plist, …) can never surface fabricated balances/notices
    on a real account.
    HERMES_DEV_CREDITSNHERMES_DEV_CREDITS_FIXTUREr"   re   rzutf-8)encodingry   r   )r    r+   T)r5   r4   rP   )r   osenvironrj   r   pathsepopenreadOSErrorr   r   r   r=   )r   namefhspecmergeds        r   dev_fixture_credits_stater     s|    2:>>*>??@@ t
*..5r
:
:
@
@
B
BC tD	w{cSCZZ	c3111 )Rwwyy(() ) ) ) ) ) ) ) ) ) ) ) ) ) ) 	 	 	44	TZZ\\**D t V<<t<FLL&Ld	LLLLLs6   ?C 'C8C CC CC 
CCc                J   	 t          | dd          }t          | dd          }d }d }t          |dd          }t          |t          t          f          o|dk    }t          | dd          }t	           |t          |d	d                     |t          |d	d                     |t          |d
d                     |t          |d
d                    |r ||          nd|r ||          nd |t          |dd                     |t          |dd                     |t          |dd                    |rdndt          |t
                    r|nddt          j                              S # t          $ r  t          	                    dd           Y dS w xY w)u4  Map a NousPortalAccountInfo into a header-shaped CreditsState for the seed.

    Float account dollars → micros (plus a DISPLAY *_usd string — allowed, since
    we're formatting account floats, NOT parsing a server-provided *_usd). Returns
    None if the account can't yield a usable state (fail-open).paid_service_access_infoNsubscriptionc                |    t          | t          t          f          rt          t          | dz                      ndS )Ni@B r   )rD   r   r3   rounddollarss    r   
_to_microsz/_credits_state_from_account.<locals>._to_micros  s7    6@3PU,6W6W^3uWy011222]^^r   c                F    t          | t          t          f          r| dndS )Nz.2fr"   )rD   r   r3   r   s    r   _to_usdz,_credits_state_from_account.<locals>._to_usd  s(     (2'C<'H'HPg###bPr   monthly_creditsr   paid_service_accesstotal_usable_creditssubscription_credits_remainingpurchased_credits_remainingrollover_creditsr   r   TF)r!   r#   r$   r%   r'   r(   r*   r+   r)   r.   r/   r5   r4   u/   credits ▸ seed account→state mapping failedr   )
getattrrD   r   r3   r   r	   r=   rk   r   r   )rR   _acc_subr   r   _monthly_has_cap_paids           r   _credits_state_from_accountr     s    t7>>t^T22	_ 	_ 	_	Q 	Q 	Q
 4!2D99he55F(Q,3T::'Z6Ld(S(STT!''$0F"M"MNN *
749Y[_+`+` a a$WWT3SUY%Z%Z[[>F&Pjj&:&:&:D8@#J778#4#4#4d'Z6SUY(Z(Z[[!''$0Mt"T"TUU&Jwt5G'N'NOO3;G//!+E4!8!8Bd	
 
 
 	
    FQUVVVtts   E5E8 8&F"!F"Nonec                
   || _         t          | dd          |j        | _        t          | dd          }t	          |t
                    r|j        d|d<   t          | dd          }t          |          r |             dS dS )u  Install a seed CreditsState on the agent and fire the notice policy once.

    Sets _credits_state, latches session-start remaining, and primes the crossing
    gate (the cold-start snapshot IS the first observation, so a session that opens
    already in a band warns immediately — the live header path keeps true crossing
    semantics), then emits. Safe to call from a worker thread: emit already runs
    off-thread in the TUI build path._credits_session_start_microsN_credits_latchTrt   _emit_credits_notices)_credits_stater   r!   r   rD   rq   rH   callable)agentro   _latchemits       r   _hydrate_seed_stater     s     !Eu5t<<D.3.D+U,d33F&$ 'E$7$C"&51488D~~  r   c                    	 t           dd          dk    rdS t           dd          dS d}	 t                      }n# t          $ r d}Y nw xY w|t           |           dS ddl}d fd}|                    |dd                                           dS # t          $ r  t                              dd           Y dS w xY w)u}  Hydrate agent._credits_state from /api/oauth/account (or a dev fixture) and
    fire the notice policy, so depletion / usage-band warnings show at session OPEN.

    Shared by (a) the TUI/desktop agent build (fires at "ready", before any message)
    and (b) the first-turn conversation setup (fallback for plain CLI / when the
    build path didn't seed). Idempotent: a second call is a no-op once a seed or a
    real header has already populated _credits_state.

    Returns True if it seeded this call, False otherwise (not nous / already seeded /
    fail-open error). Never raises — credits must never block session startup.
    r   r"   nousFr   NTr   r   r   c                     	 ddl m}   | d          }t          dd           d S t          |          }|t	          |           d S d S # t
          $ r  t                              dd           Y d S w xY w)Nr   )get_nous_portal_account_infoT)force_freshr   u2   credits ▸ session-start seed (background) failedr   )hermes_cli.nous_accountr   r   r   r   rk   r   r   )r   rR   ro   r   s      r   _bg_seedz/seed_credits_at_session_start.<locals>._bg_seed  s    	bPPPPPP33EEE5"2D99EF3D99$'u55555 %$ b b bQ\`aaaaaabs   #A !A &A76A7zcredits-seed)targetr   daemonu1   credits ▸ session-start seed failed (fail-open)r   r   r   )	r   r   rk   r   	threadingThreadstartr   r   )r   fixturer  r   s   `   r   seed_credits_at_session_startr    s0   )5*b))V3355*D11=5	/11GG 	 	 	GGG	  w///4
 	
	b 
	b 
	b 
	b 
	b 
	b 	~dKKQQSSSt    	HSWXXXuusA   B B B ? B AB AB %4B &CC)r   r   r   r   )r   r   r   r	   )r"   )r`   r   ra   r   r   r	   )ro   r   rp   rq   rn   r	   r   rr   )r   r   r   r   r   r   )r   r   r  rI   )+rM   
__future__r   loggingr   rer=   dataclassesr   typingr   r   r   utilsr   	getLoggerrJ   r   r
   rN   	frozensetr   compiler   objectr   r   r   r   r   r   rW   r}   rY   rm   r   r   rq   r   r   r   r   r  rP   r   r   <module>r     s    @ # " " " " "  				 				  ! ! ! ! ! ! ) ) ) ) ) ) ) ) ) ) ! ! ! ! ! !		8	$	$ "'  & & & & %9&8&%ABB  "*&
'
' FHH	   *& & & & 8I 8I 8I 8I 8I 8I 8I 8I@   ;     
 $         ,' ' ' ' 'b  	K K K K K Kf } } } } }\ t#7&",W#7+   #7&",W+	   "&%",W+	   "&%",W+	   t#7",W#7+   &&+;	   D&&",W&+(  O/" /" / / / /d!M !M !M !MH& & & &R   &5 5 5 5 5 5r   