
    $jh|                        U d Z ddlZddlZddlZddlmZmZ ddlmZmZ ddl	m
Z
mZ ddlmZ dd	lmZ dd
lmZ ddlmZ ddlmZ ddlmZ ddlmZmZmZmZ ddlmZmZ  ej         e!          Z"de#de$e#         fdZ%e G d d                      Z&e G d d                      Z'da(edz  e)d<   defdZ*deddfdZ+	 	 	 	 	 d4de#de#de#de$e#         d e,d!e$e#         dz  d"ed#e$e         dz  d$edz  d%edz  de-e#e.e$e         e$e         f         f         fd&Z/	 	 	 	 	 	 d5de#de#de$e#         d(ed)ed*e,d+e0d!e$e#         dz  d"ed#e$e         dz  d$edz  d%edz  de-e#e$e         f         fd,Z1	 	 	 	 	 	 	 	 d6de#d-e#de#de$e#         d.e,d/edz  d0ed1         d2edz  d!e$e#         dz  d"ed#e$e         dz  d$edz  d%edz  de'fd3Z2dS )7a  
Retrieval module for 4-way parallel search.

Implements:
1. Semantic retrieval (vector similarity)
2. BM25 retrieval (keyword/full-text search)
3. Graph retrieval (via pluggable GraphRetriever interface)
4. Temporal retrieval (time-aware search with spreading)
    N)	dataclassfield)UTCdatetime)AnyOptional   )
get_config   )acquire_with_retryfq_table)create_sql_dialect   )GraphRetriever)LinkExpansionRetriever)TagGroup	TagsMatchbuild_tag_groups_where_clausebuild_tags_where_clause_simple)GraphRetrievalTimingsRetrievalResult
query_textreturnc                 v    t          j        dd|                                                                           S )zNormalize query text and split into BM25 tokens.

    Strips punctuation, lowercases, and splits on whitespace.
    Returns an empty list when the query contains no word characters.
    z[^\w\s] )resublowersplit)r   s    m/home/rurouni/.hermes/hermes-agent/venv/lib/python3.11/site-packages/hindsight_api/engine/search/retrieval.pytokenize_queryr"      s0     6*c:#3#3#5#566<<>>>    c                       e Zd ZU dZee         ed<   ee         ed<   ee         ed<   ee         dz  ed<    ee          Z	ee
ef         ed<   dZedz  ed	<    ee          Zee         ed
<   dZeed<   dS )ParallelRetrievalResultz2Result from parallel retrieval across all methods.semanticbm25graphNtemporaldefault_factorytimingstemporal_constraintgraph_timings        max_conn_wait)__name__
__module____qualname____doc__listr   __annotations__r   dictr,   strfloatr-   tupler.   r   r0    r#   r!   r%   r%   '   s         <<?####

    ?#d**** %d ; ; ;GT#u*;;;(,,,,162 2 2M4-.    M5r#   r%   c                   n    e Zd ZU dZeeef         ed<    ee          Z	eee
f         ed<   dZe
ed<   dS )MultiFactTypeRetrievalResultz,Result from retrieval across all fact types.results_by_fact_typer*   r,   r/   r0   N)r1   r2   r3   r4   r7   r8   r%   r6   r   r,   r9   r0   r;   r#   r!   r=   r=   7   sg         66 s$;;<<<< %d ; ; ;GT#u*;;;M5r#   r=   _default_graph_retrieverc                  "   t           t                      } | j                                        }|dk    r)t	                      a t
                              d           n,t
                              d| d           t	                      a t           S )z:Get or create the default graph retriever based on config.Nlink_expansionz#Using LinkExpansion graph retrieverzUnknown graph retriever 'z!', falling back to link_expansion)r?   r
   graph_retrieverr   r   loggerinfowarning)configretriever_types     r!   get_default_graph_retrieverrH   G   s      '/5577---'='?'?$KK=>>>>NNh~hhhiii'='?'?$##r#   	retrieverc                 
    | a dS )z<Set the default graph retriever (for configuration/testing).N)r?   )rI   s    r!   set_default_graph_retrieverrK   V   s      )r#   anyquery_emb_strbank_id
fact_typeslimittags
tags_match
tag_groupscreated_aftercreated_beforec                   )*+,-./012K   d |D             }t          |          }t          |dz  d          0d)t          d          1t                      }t	          t          | dd                    +t          |          }|rdnd}t          |||	          2||rd
ndz   }t          ||          \  /}}|t          |          z   }d*g }|	"|
                    |	           *d| z  *|d
z  }|
"|
                    |
           *d| z  *|d
z  })*+/012fd|D             }|rg|j        }+                    |||          }t          |          D ]8\  }}|
                    +                    1)|ddd2/||*                     9d                    |          }||g}|r*|
                    |           |
                    |           |r|
                    |           |                    |           |                    |           	  | j        |g|R   d{V }nA# t$          $ r3}t'          |          }|rd|v s	d|v sd|v r	t(                              d|dd                    d}t          |||	          .||rd
ndz   } t          ||           \  -}}| t          |          z   }!d,|	,d|! z  ,|!d
z  }!|
,d|! z  ,|!d
z  }!)+,-.01fd|D             }"d                    |"          }#||g}$|r|$
                    |           |$                    |           |$                    |            | j        |#g|$R   d{V }n Y d}~nd}~ww xY wd |D             }%|D ]}&t-          |&          }'|'                    d          }(|'                    d          }||vr@|(d k    rP|%|         |k     rC||         d         
                    t3          j        |'                     |%|xx         d
z  cc<   ||         d
         
                    t3          j        |'                     |S )!um  
    Combined semantic + BM25 retrieval for multiple fact types in a single query.

    Uses UNION ALL of per-fact_type subqueries so that each arm has its own
    ORDER BY ... LIMIT, enabling the partial HNSW indexes per fact_type instead
    of forcing a full sequential scan (which the previous window-function approach
    caused by using PARTITION BY inside ROW_NUMBER()).

    Requires partial HNSW indexes per fact_type (idx_mu_emb_world,
    idx_mu_emb_observation, idx_mu_emb_experience), created automatically by
    Alembic migration a3b4c5d6e7f8_add_partial_hnsw_indexes.py.

    HNSW is approximate — semantic arms over-fetch by 5x (min 100) and trim to
    limit in Python to compensate.  ef_search=200 is set globally on pool
    connections at init time (see memory_engine.py) to improve recall on sparse
    graphs.

    fact_type values are inlined as literals (safe: they come from a controlled
    internal enum, never from user input).

    Args:
        conn: Database connection
        query_emb_str: Query embedding as string
        query_text: Query text for BM25
        bank_id: Bank ID
        fact_types: List of fact types to retrieve
        limit: Maximum results per method per fact type
        tags: Optional tags to filter by
        tags_match: Tag matching mode

    Returns:
        Dict mapping fact_type -> (semantic_results, bm25_results)
    c                     i | ]}|g g fS r;   r;   .0fts     r!   
<dictcomp>z3retrieve_semantic_bm25_combined.<locals>.<dictcomp>   s#    RuRuRudfSUXZ\^W_RuRuRur#      d   zid, text, context, event_date, occurred_start, occurred_end, mentioned_at, fact_type, document_id, chunk_id, tags, metadata, proof_countmemory_unitsbackend_type
postgresqlr	   matchr   r    N AND updated_at > $ AND updated_at < $c                 L    g | ] }                     |d d	  	        !S z$1$2)	tablecols	fact_typeembedding_parambank_id_paramfetch_limittags_clausegroups_clauseextra_wherebuild_semantic_arm)	rY   rZ   rj   created_range_clausedialectrp   
hnsw_fetchri   ro   s	     r!   
<listcomp>z3retrieve_semantic_bm25_combined.<locals>.<listcomp>   s[         	"" "#', 	# 
	
 
	
  r#   )text_search_extensionrh   z$3z$4)ri   rj   rk   rm   limit_param
text_paramro   rp   	arm_indexrx   rq   z
UNION ALL
z	DRG-10599z	ORA-30600z	ORA-29902zFOracle Text CONTAINS failed (%s), falling back to semantic-only searchx   c                 L    g | ] }                     |d d	  	        !S rg   rr   )	rY   rZ   rj   ru   fb_created_clausefb_groups_clausefb_tags_clauserv   ri   s	     r!   rw   z3retrieve_semantic_bm25_combined.<locals>.<listcomp>  s[         ** $("& * ."2 1 + 
 
  r#   c                     i | ]}|d S )r   r;   rX   s     r!   r[   z3retrieve_semantic_bm25_combined.<locals>.<dictcomp>$  s    !=!=!=B"a!=!=!=r#   sourcerk   r&   )r"   maxr   r
   r   getattrboolr   r   lenappendrx   prepare_bm25_text	enumeratebuild_bm25_armjoinextendfetch	Exceptionr8   rC   rE   r7   popgetr   from_db_row)3connrM   r   rN   rO   rP   rQ   rR   rS   rT   rU   result_dicttokensrF   _include_bm25tags_param_idxtag_groups_param_startgroups_params_	_next_idxcreated_range_paramsarmstext_extbm25_text_paramirZ   queryparamsrowseerr_strfb_tags_idxfb_groups_startfb_next_idxfb_armsfb_query	fb_params
sem_countsrrowr   rj   rt   ru   r~   r   r   rp   rv   ri   ro   s3                                            @@@@@@@@@@r!   retrieve_semantic_bm25_combinedr   \   s     \ SvRujtRuRuRuKJ''F UQY$$J	H 	 ^$$E\\F !~|!L!LMMG LLM'.QQQN0~ZXXXK ,D/?qqa@&CJPf&g&g#M=!
 ']););;I&( ##M222 Ai A AAQ	!##N333 Ai A AAQ	
            D   /&88ck8llz** 	 	EArKK&& "& $# +"/*2 4 '         &&E!7+F 'eo&&& d
MM-   
MM&'''.TZ////////// , , , a&& %	kW44w8N8NR]ahRhRhNNcelmqnqmqersssK;D+U_```N)$-=QQA>O%B:%_%_"a)C,>,>>K "(!%H;%H%HH!q )!%H;%H%HH!q           %  G '++G44H,g6I '  &&&]+++1222#H9y999999999DD DDDDU,^ ">!=*!=!=!=J H H1gg""WW[!![  Z"~%%B"))/*Ec*J*JKKK2!#OA%%o&A#&F&FGGGGs   H4 4M2?D)M--M2皙?
start_dateend_datebudgetsemantic_thresholdc                   K   ddl m} |j        |                    t                    }|j        |                    t                    }t          |d|	          }d|rdndz   }t          |
|          \  }}}|t          |          z   }d	}g }|"|                    |           |d
| z  }|dz  }|"|                    |           |d| z  }|dz  }||||||g}|r|                    |           |	                    |           |	                    |            | j
        d |d           d| d| d| d |d           dg|R   d{V }|sd |D             S d |D             }|D ])}|d         }||v r||                             |           *||z
                                  dz  }|||z
  dz  z   }i }|D ]A}|                    |g           }|sg ||<   !g }t                      } i }!|D ]-}t          |d                   }"|                     |"           d}#|d         &|d         |d         |d         |d         z
  dz  z   }#n2|d         	|d         }#n!|d         	|d         }#n|d         |d         }#|#rIt!          |#|z
                                  dz            }$|dk    rdt#          |$|dz  z  d          z
  nd}%nd}%t%          j        t)          |                    }&|%|&_        |%|&_        |                    |&           |d         df|!|"<   /t/          |!                                          }'|t          |          z
  }(d})d}*d}+d},t          |dd |	!          }-d|rdndz   }.t          |
|.d "          \  }/}0}|'rh|(dk    ra|,|+k     rZ|,dz  },|'d|)         }1|'|)d         }'||1|||*|g}2|r|2                    |           |2	                    |0            | j
        d# |d$           d% |d           d&|- d'|/ d(	g|2R   d{V }3|3D ]}4t          |4d                   }5|5| v r|                     |5           |(dz  }(t          |4d)                   }6|!                    |6d*          \  }}7d}8|4d         &|4d         |4d         |4d         |4d         z
  dz  z   }8n2|4d         	|4d         }8n!|4d         	|4d         }8n|4d         |4d         }8|8rIt!          |8|z
                                  dz            }$|dk    rdt#          |$|dz  z  d          z
  nd}9nd+}9|4d,         }:|:d-v rd.};n	|:d/v rd0};nd};|7|4d1         z  |;z  d2z  }<t3          |9|<          }=t%          j        t)          |4                    }>|=|>_        |9|>_        |                    |>           |(dk    r(|=d3k    r"|4d         |=f|!|5<   |'                    |5           |(dk    r n|'r|(dk    r|,|+k     Z|||<   C|S )4a  
    Temporal retrieval for multiple fact types in a single query.

    Batches the entry point query using window functions to get top-N per fact type,
    then runs spreading for each fact type.

    Args:
        conn: Database connection
        query_emb_str: Query embedding as string
        bank_id: Bank ID
        fact_types: List of fact types to retrieve
        start_date: Start of time range
        end_date: End of time range
        budget: Node budget for spreading per fact type
        semantic_threshold: Minimum semantic similarity to include

    Returns:
        Dict mapping fact_type -> list of RetrievalResult
    r   r   N)tzinfo   ra   r   r   rc   rd   re   a4  
        WITH date_ranked AS MATERIALIZED (
            SELECT id, fact_type,
                   ROW_NUMBER() OVER (
                       PARTITION BY fact_type
                       ORDER BY COALESCE(occurred_start, mentioned_at, occurred_end) DESC NULLS LAST
                   ) AS rn
            FROM r^   ab  
            WHERE bank_id = $2
              AND fact_type = ANY($3)
              AND embedding IS NOT NULL
              AND (
                  (occurred_start IS NOT NULL AND occurred_end IS NOT NULL
                   AND occurred_start <= $5 AND occurred_end >= $4)
                  OR
                  (mentioned_at IS NOT NULL AND mentioned_at BETWEEN $4 AND $5)
                  OR
                  (occurred_start IS NOT NULL AND occurred_start BETWEEN $4 AND $5)
                  OR
                  (occurred_end IS NOT NULL AND occurred_end BETWEEN $4 AND $5)
              )
              z
              a  
        ),
        sim_ranked AS (
            SELECT mu.id, mu.text, mu.context, mu.event_date, mu.occurred_start, mu.occurred_end, mu.mentioned_at, mu.fact_type, mu.proof_count, mu.document_id, mu.chunk_id, mu.tags, mu.metadata,
                   1 - (mu.embedding <=> $1::vector) AS similarity,
                   ROW_NUMBER() OVER (PARTITION BY mu.fact_type ORDER BY mu.embedding <=> $1::vector) AS sim_rn
            FROM date_ranked dr
            JOIN aX   mu ON mu.id = dr.id
            WHERE dr.rn <= 50
              AND (1 - (mu.embedding <=> $1::vector)) >= $6
        )
        SELECT id, text, context, event_date, occurred_start, occurred_end, mentioned_at, fact_type, proof_count, document_id, chunk_id, tags, metadata, similarity
        FROM sim_ranked
        WHERE sim_rn <= 10
        c                     i | ]}|g S r;   r;   rX   s     r!   r[   z.retrieve_temporal_combined.<locals>.<dictcomp>  s    ,,,2B,,,r#   c                     i | ]}|g S r;   r;   rX   s     r!   r[   z.retrieve_temporal_combined.<locals>.<dictcomp>  s    %B%B%Bb"%B%B%Br#   rk   iQ idoccurred_startoccurred_endmentioned_atg      ?      ?
similarity   
   r\   zmu.)table_aliasrb   )r   a  
                SELECT src.from_unit_id, mu.id, mu.text, mu.context, mu.event_date, mu.occurred_start, mu.occurred_end, mu.mentioned_at, mu.fact_type, mu.document_id, mu.chunk_id, mu.tags, mu.metadata,
                       l.weight, l.link_type,
                       1 - (mu.embedding <=> $1::vector) AS similarity
                FROM unnest($2::uuid[]) AS src(from_unit_id)
                CROSS JOIN LATERAL (
                    SELECT ml.to_unit_id, ml.weight, ml.link_type
                    FROM memory_linksaC   ml
                    WHERE ml.from_unit_id = src.from_unit_id
                      AND ml.link_type IN ('temporal', 'causes', 'caused_by', 'enables', 'prevents')
                      AND ml.weight >= 0.1
                    ORDER BY ml.weight DESC
                    LIMIT $5
                ) l
                JOIN z mu ON mu.id = l.to_unit_id
                WHERE mu.bank_id = $6
                  AND mu.fact_type = $3
                  AND mu.embedding IS NOT NULL
                  AND (1 - (mu.embedding <=> $1::vector)) >= $4
                  z
                  z
                from_unit_id)r   r   g333333?	link_type)causes	caused_byg       @)enablespreventsg      ?weightgffffff?g?)memory_enginer   r   replacer   r   r   r   r   r   r   total_secondsr   setr8   addabsminr   r   r7   temporal_scoretemporal_proximityr5   keysr   )?r   rM   rN   rO   r   r   r   r   rQ   rR   rS   rT   rU   r   ro   r   rp   r   r   r   rt   r   r   entry_pointsentries_by_fteprZ   
total_daysmid_dateresults_by_ftft_entry_pointsresultsvisitednode_scoresunit_id	best_datedays_from_midr   	ep_resultfrontierbudget_remaining
batch_sizeper_source_limitmax_iterations	iterationspreading_tags_clausespreading_groups_param_startspreading_groups_clausespreading_groups_params	batch_idsspreading_params	neighborsnneighbor_id	parent_idparent_temporal_scoreneighbor_best_dateneighbor_temporal_proximityr   causal_boostpropagated_temporalcombined_temporalneighbor_results?                                                                  r!   retrieve_temporal_combinedr   5  s     D )(((((  ''s'33
##3#// 1q
KKKKt"2!!3&CJPf&g&g#M=! ']););;I&( ##M222 Ai A AAQ	!##N333 Ai A AAQ	!7J
HN`aF d
MM-   
MM&''' $%	 (>**%	 %	* +%	 %	, -%	 %	. $/%	 %	< (>**=%	 %	 %	(N 
O( ( ( ( ( ( ( ( (LT  -,,,,,, &C%Bz%B%B%BM ) )_"$$R((( Z'66885@JX
2a77H 79M V$ V$'++B33 	 "M"%% " 	; 	;B"T(mmGKK    I"#/B~4F4R/0B~4FL\I]4]ab3bb		$%1/0		N#/~.		N#/~.	 ) #Y%9$H$H$J$JU$R S SYcfgYgYgS3}
Q/OQT+U+U%U%Ump""%("'3DHH==I'9I$+=I(NN9%%%$&|$4c#:K   ((**++!C$8$88
 	 !?tQTYak l l l'(,<AA1'=$>[4%?
 ?
 ?
;!8!  [	+a//I4N4NNI *-I
,H !.y">PRbdkl . ''---##$;<<< )dj #(>22  h~.. & )' ( +)  . "/        I4  2 2!!D'll'))K((( A% . 122	+6??9j+Q+Q((%)"%&2q7H7T)*+;)<.@QTUVfTg@gkl?l)l&&'(4)*+;)<&&~&2)*>):&&~&2)*>):&% 6$');h)F(U(U(W(WZ_(_$`$`MLVYZNNc-:>"BCHHHH`c 0/ 36/kN	 777#&LL"999#&LL#&L&;ak&IL&X[^&^#$'(CEX$Y$Y!"1"=d1gg"F"F1B.5P2///#a'',=,C,C01,AR/SK,OOK000#q((E )u  [	+a//I4N4Nz $br#   query_embedding_strthinking_budgetquestion_datequery_analyzerQueryAnalyzerrB   c                    	
,-.K   ddl .|pt                      ,.                                 }i }.                                 }ddlm}  |||          }.                                 |z
  }||d<   .                                 }i }d}t	                     4 d{V }.                                 |z
  }t          ||	
           d{V }.                                 |z
  }|rR|\  }}.                                 }t          ||||d		

           d{V }.                                 |z
  }	 ddd          d{V  n# 1 d{V swxY w Y   ||d<   ||d<   dt          dt          t          t          t                   t          t          dz  f         f ,	
.fd--fd|D             }t          j        |  d{V }i } |}!g }"|D ]}#|                    |#g g f          \  }$}%g }&d}'d}(|D ]?})|)d         |#k    r1|)d         }&|)d         }'|)d         }(|(r|"                    |(            n@|r|                    |#          nd}*|*t#          |*          dk    rd}*t%          |$|%|&|*|dz  |dz  |'||d||(r|(gng |!          | |#<   .                                 |z
  }+|+|d<   t'          | ||!          S )a  
    Optimized retrieval for multiple fact types using batched queries.

    This reduces database round-trips by:
    1. Combining semantic + BM25 into one CTE query for ALL fact types (1 query instead of 2N)
    2. Running graph retrieval per fact type in parallel (N parallel tasks)
    3. Running temporal retrieval per fact type in parallel (N parallel tasks)

    Args:
        pool: Database connection pool
        query_text: Query text
        query_embedding_str: Query embedding as string
        bank_id: Bank ID
        fact_types: List of fact types to retrieve
        thinking_budget: Budget for graph traversal and retrieval limits
        question_date: Optional date when question was asked (for temporal filtering)
        query_analyzer: Query analyzer to use (defaults to TransformerQueryAnalyzer)
        graph_retriever: Graph retrieval strategy (defaults to configured retriever)

    Returns:
        MultiFactTypeRetrievalResult with results organized by fact type
    r   Nr   )extract_temporal_constraint)reference_dateanalyzertemporal_extractionr/   )rQ   rR   rS   rT   rU   r   )r   r   rQ   rR   rS   rT   rU   semantic_bm25_combinedtemporal_combinedrZ   r   c                    K                                    }
                    | 	d d            d {V \  }}| |                                 |z
  |fS )N)poolr   rN   rk   r   r   semantic_seedstemporal_seedsrQ   rR   rS   rT   rU   )timeretrieve)rZ   graph_startr   graph_timingrN   rT   rU   r  r   r   rI   rS   rQ   rR   r   r
  s       r!   run_graph_for_fact_typezAretrieve_all_fact_types_parallel.<locals>.run_graph_for_fact_type  s       iikk&/&8&8 3"!!!') '9 '
 '
 !
 !
 !
 !
 !
 !
 7DIIKK+5|CCr#   c                 &    g | ]} |          S r;   r;   )rY   rZ   r  s     r!   rw   z4retrieve_all_fact_types_parallel.<locals>.<listcomp>  s%    DDD2**2..DDDr#   r   r	   )r&   r'   r(   r)   r  )r&   r'   r(   r)   r,   r-   r.   r0   total)r>   r,   r0   )r
  rH   r  r   r   r   r   r8   r:   r5   r   r9   r   asynciogatherr   r   r   r%   r=   )/r  r   r   rN   rO   r   r   r   rB   rQ   rR   rS   rT   rU   
start_timer,   temporal_extraction_startr   r-   temporal_extraction_timesemantic_bm25_starttemporal_results_by_fttemporal_timer   	conn_waitsemantic_bm25_resultssemantic_bm25_timetc_starttc_endtemporal_startgraph_tasksgraph_results_listr>   r0   all_graph_timingsrZ   semantic_resultsbm25_resultsgraph_results
graph_timer  grtemporal_results
total_timerI   r  r
  s/   ```` `   `````                              @@@r!    retrieve_all_fact_types_parallelr)  T  s     L KKK@#>#@#@IJ "G !%		@@@@@@55jQ^iwxxx#yy{{-FF%=G!" ))++?AM!$'' &9 &9 &9 &9 &9 &9 &94IIKK"55	 'F!!')'
 '
 '
 !
 !
 !
 !
 !
 !
 "YY[[+>>  	92Hf!YY[[N+E#&#&%%+-, , , & & & & & &" !IIKK.8M8M&9 &9 &9 &9 &9 &9 &9 &9 &9 &9 &9 &9 &9 &9 &9 &9 &9 &9 &9 &9 &9 &9 &9 &9 &9 &9 &9P );G$%#0G DD	sD)52G$2NN	OD D D D D D D D D D D D D D D D D, EDDDDDDK&~{;;;;;;; @BM57 %
 %
)>)B)B2Bx)P)P&, 
$ 	 	B!u{{ "1U
!!u ;%,,\:::  >QZ155b999VZ'C0@,A,AQ,F,F##:%%.2*Q.#)'?  !4,8@<..b'$
 $
 $
R  " z)J!GG'1#   s   +B#E!!
E+.E+)NrL   NNN)r   NrL   NNN)NNNNrL   NNN)3r4   r  loggingr   dataclassesr   r   r   r   typingr   r   rF   r
   db_utilsr   r   r   sqlr   graph_retrievalr   link_expansion_retrievalr   rQ   r   r   r   r   typesr   r   	getLoggerr1   rC   r8   r5   r"   r%   r=   r?   r6   rH   rK   intr7   r:   r   r9   r   r)  r;   r#   r!   <module>r4     s       				 ( ( ( ( ( ( ( ( " " " " " " " "                             ) ) ) ) ) ) $ $ $ $ $ $ $ $ $ $ $ $ + + + + + + < < < < < < d d d d d d d d d d d d 9 9 9 9 9 9 9 9		8	$	$?s ?tCy ? ? ? ?                 37 .4/ 6 6 6$^ $ $ $ $)> )d ) ) ) ) "!(,%)&*V VV V 	V
 S	V V s)d
V V X%V d?V tOV 
#uT/*D,AAB
BCV V V VB !$!!(,%)&*\ \\ \ S		\
 \ \ \ \ s)d
\ \ X%\ d?\ tO\ 
#tO$
$%\ \ \ \L	 &*04-1!!(,%)&*s ss s 	s
 S	s s d?s _-s $d*s s)d
s s X%s d?s tOs "s s s s s sr#   