
    8kiT                       d Z ddlmZ ddl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mZmZmZmZ dd	lmZ dd
lmZ  ee          Z G d dee          Z ed           G d d                      Z ed           G d d                      Z ej        d          Z ej        d          Z ej        d          Z  ej        d          gZ! ej        d          gZ" ej        d          gZ# ej        d          gZ$ ej        d          gZ% ej        d          gZ& ej        d          gZ' ej        d          gZ( G d d          Z)dS ) uu  Rule-first graph query planner for Research mode.

Uses the already extracted question keywords to route questions to specific
GraphQueryService methods, then formats the returned graph data into concise
Chinese evidence blocks for the LLM context.

基于规则的图谱查询规划器模块。
在 Research 模式中，根据用户问题的关键词和句式特征，将问题分类为
不同的查询意图（政策链追溯/修订历史/实体文档/事项详情/主题探索），
然后调用对应的 GraphQueryService 方法获取图谱证据，
并将结果格式化为简洁的中文证据文本注入 LLM 上下文。
    )annotationsN)	dataclassfield)Enum)Any)GraphQueryService)ENTITY_DOCS_TEMPLATEMATTER_CARD_TEMPLATEPOLICY_CHAIN_TEMPLATEREVISION_HISTORY_TEMPLATETHEME_DOCS_TEMPLATEextract_first_doc_code)
get_loggerc                  *    e Zd ZdZdZdZdZdZdZdZ	dS )	QueryIntentuX   Supported rule-based graph query intents.

    支持的图谱查询意图枚举。
    policy_chainrevision_historyentity_docsmatter_detailtheme_exploregeneralN)
__name__
__module____qualname____doc__POLICY_CHAINREVISION_HISTORYENTITY_DOCSMATTER_DETAILTHEME_EXPLOREGENERAL     6D:\work\zm-rag\backend\app\core\graph_query_planner.pyr   r   #   s9         
 "L)K#M#MGGGr$   r   T)slotsc                  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 )	QueryPlanz-Planner output for graph evidence collection.r   intentN
str | Nonedoc_iddoc_codeentity_nameentity_labelmatter_query)
r   r   r   r   __annotations__r+   r,   r-   r.   r/   r#   r$   r%   r(   r(   1   sy         77FH"K""""#L#####L######r$   r(   c                  D    e Zd ZU dZdZded<    ee          Zded<   dS )	GraphEvidencez7Structured graph evidence plus supporting document IDs. strtext)default_factory	list[str]doc_idsN)	r   r   r   r   r5   r0   r   listr8   r#   r$   r%   r2   r2   =   sC         AADNNNNt444G444444r$   r2   uR   (政府|局|委|办|厅|部|院|署|中心|办公室|党组|协会|集团|银行)$u   (省|市|县|区|镇|乡|村)$u.   ^(\d{4}年|近\d+年|近年来|目前|当前)$u7   依据|上位法|法律基础|制定依据|法规依据u6   修订|修改|废止|历史沿革|变更|替代|取代u5   (发布|出台|制定|印发).*(文件|政策|规定)uZ   办理|申请|材料|条件|时限|流程|需要什么|需要哪些|怎么办|如何办理u(   相关政策|同类|主题|领域|专项u   (?P<name>[\u4e00-\u9fffA-Za-z0-9]{2,40}?)(?:需要什么材料|需要哪些材料|需要什么条件|需要哪些条件|办理条件|办理时限|办理流程|申请材料|申请条件|怎么办|如何办理|流程)ub   (?P<name>[\u4e00-\u9fffA-Za-z0-9]{2,40}?)(?:发布|出台|制定|印发).*(?:文件|政策|规定)u   (?P<name>[\u4e00-\u9fffA-Za-z0-9]{2,40}?)(?:相关政策|同类政策|主题相关文件|主题政策|领域政策|专项政策|主题|领域|专项)c                      e Zd ZdZd@dZd	d
dAdZd	d
dAdZd	d
dAdZd	d
dAdZd	d
dAdZ	d	d
dAdZ
d	d
dBdZd	d
dCdZd	d
dDdZdEd"ZdFd$ZdGd)ZdHd*ZdId+ZdJd,ZdJd-ZdJd.ZdJd/ZdKd0ZdLd3ZdMd5ZdNd7ZdOd:ZdPd;ZdQd<ZdRd?Zd	S )SGraphQueryPlanneru  Rule-first graph intent classifier and evidence collector.

    规则优先的图谱查询规划器。
    通过正则模式匹配将自然语言问题分类为查询意图，
    再执行对应的图谱查询并格式化为 LLM 可用的证据文本。
    questionr4   keywordsr7   returnr(   c                    |                                 }|                     |          }|                     |t                    rt	          t
          j        |          S |                     |t                    rt	          t
          j        |          S |                     |t                    rc| 
                    |t                    }|p|                     |          }|                     |          }t	          t
          j        ||          S |                     |t                    rM| 
                    |t                     }|p|                     |          }t	          t
          j        |          S |                     |t&                    rO| 
                    |t(                    }|p|                     |          }t	          t
          j        ||d          S t	          t
          j                  S )u   Classify the question into a graph query plan.

        按优先级依次匹配：政策链 > 修订历史 > 实体文档 > 事项详情 > 主题探索 > 通用。
        )r)   r,   )r)   r-   r.   )r)   r/   PolicyTheme)r)   r,   r-   r.   )r)   )strip_extract_doc_code_matches_any_POLICY_CHAIN_PATTERNSr(   r   r   _REVISION_HISTORY_PATTERNSr   _ENTITY_DOCS_PATTERNS_extract_named_segment_ENTITY_CAPTURE_PATTERNS_pick_entity_candidate_infer_entity_labelr   _MATTER_DETAIL_PATTERNS_MATTER_CAPTURE_PATTERNS_pick_matter_candidater    _THEME_EXPLORE_PATTERNS_THEME_CAPTURE_PATTERNS_pick_theme_candidater!   r"   )selfr<   r=   r5   r,   r-   r.   r/   s           r%   planzGraphQueryPlanner.planw   s   
 ~~))$//T#9:: 	QK$<xPPPPT#=>> 	UK$@8TTTTT#899 	55d<TUUK%N)D)DX)N)NK33K@@L".')    T#:;; 	66t=UVVL'P4+F+Fx+P+PL"0)   
 T#:;; 	55d<STTK%M)C)CH)M)MK"0!'*	     34444r$   N
acl_tokensrR   graph_servicer   rT   list[str] | Noner2   c                 K   	 |j         t          j        k    r|                     |||           d{V S |j         t          j        k    r|                     |||           d{V S |j         t          j        k    r|                     |||           d{V S |j         t          j        k    r| 	                    |||           d{V S |j         t          j
        k    r|                     |||           d{V S nK# t          $ r>}t                              d|j         j        t!          |                     Y d}~nd}~ww xY wt#                      S )z;Execute the plan and format graph evidence for LLM context.rS   Ngraph_evidence_collect_failed)r)   error)r)   r   r   _collect_policy_chainr   _collect_revision_historyr   _collect_entity_docsr    _collect_matter_detailr!   _collect_theme_docs	Exceptionloggerwarningvaluer4   r2   )rQ   rR   rU   rT   excs        r%   collect_evidencez"GraphQueryPlanner.collect_evidence   s     	{k666!77mXb7ccccccccc{k:::!;;D-\f;ggggggggg{k555!66t]Wa6bbbbbbbbb{k777!88}Yc8ddddddddd{k777!55dMV`5aaaaaaaaa 8 	 	 	NN/{(#hh         	 s.   2D 2D *2D 2D 2D 
E4EEc                 K   |                      |||           d {V }|st                      S |                     |j        |d|           d {V }|                    d          pg }|                    d          pg }|st                      S t          j        |                     ||                    }t          ||                     |                    S )NrS      	max_depthrT   chainedges)ri   r5   r8   )	_resolve_doc_idr2   _call_graphget_policy_chaingetr   format_format_policy_chain_doc_ids_from_items)	rQ   rR   rU   rT   r+   datari   rj   r5   s	            r%   rZ   z'GraphQueryPlanner._collect_policy_chain   s      ++D-J+WWWWWWWW 	# ??"%%*!	 & 
 
 
 
 
 
 
 
 !!'R!!'R 	# ??"$+++E599
 
 
 $0H0H0O0OPPPPr$   c                 K   |                      |||           d {V }|st                      S |                     |j        |d|           d {V }|                    d          pg }|                    d          pg }|st                      S t          j        |                     ||                    }t          ||                     |                    S )NrS   rf   rg   	documentsrj   )historyrk   )	rl   r2   rm   get_revision_historyro   r   rp   _format_revision_historyrr   )	rQ   rR   rU   rT   r+   rs   ru   rj   r5   s	            r%   r[   z+GraphQueryPlanner._collect_revision_history   s      ++D-J+WWWWWWWW 	# ??"%%.!	 & 
 
 
 
 
 
 
 
 HH[))/R	!!'R 	# ??"(/11)UCC
 
 
 $0H0H0S0STTTTr$   c                 K   |j         st                      S |                     ||j         |j        |           d {V }|st                      S |\  }}|                     |j        ||d|           d {V }|st                      S t          j        ||                     |                    }t          || 	                    |                    S )NrS      limitrT   )r-   docsrk   )
r-   r2   _resolve_entityr.   rm   get_docs_by_entityr	   rp   _format_docs_listrr   )	rQ   rR   rU   rT   resolvedr-   r.   r}   r5   s	            r%   r\   z&GraphQueryPlanner._collect_entity_docs   s1       	# ??"--!	 . 
 
 
 
 
 
 
 
  	# ??"$,!\%%,! & 
 
 
 
 
 
 
 
  	# ??"#*#''--
 
 
 $0H0H0N0NOOOOr$   c                 K   |j         st                      S |                     |j        |j         d|           d {V }|                     ||j                   }|st                      S |                     |j        |d         |           d {V }|st                      S t          j        |                    d          p|j         | 	                    |                    d          d          | 	                    |                    d	          d
          | 	                    |                    d          d          | 	                    |                    d          d          | 
                    |                    d                              }t          ||                     |                    d          pg                     S )Nrf   r{   	matter_idrS   name
conditionsu   未提取到明确条件)empty	materialsu   未提取到明确材料time_limitsu   未提取到明确时限
handled_byu   未提取到明确办理机构governing_docs)r   r   r   r   r   r   rk   )r/   r2   rm   search_matters_pick_best_named_itemget_matter_cardr
   rp   ro   _format_prop_names_format_gov_docsrr   )rQ   rR   rU   rT   mattersmatchcardr5   s           r%   r]   z(GraphQueryPlanner._collect_matter_detail  s        	# ??"(((!	 ) 
 
 
 
 
 
 
 
 **7D4EFF 	# ??"%%)+! & 
 
 
 
 
 
 
 

  	# ??"#*&!!6T%6..txx/E/EMg.hh--dhh{.C.CKe-ff//0G0GOi/jj..txx/E/EMm.nn00:J1K1KLL
 
 
 ,,TXX6F-G-G-M2NN
 
 
 	
r$   c                 K   |                      |||           d {V }|r|                     |j        |d|           d {V }|rq|d                             d          p|j        pd}t          j        ||                     |                    }t          || 	                    |                    S |j        st                      S | 
                    ||j        d	|           d {V }|st                      S |\  }}	|                     |j        |d	d|           d {V }|st                      S t          j        ||                     |                    }t          || 	                    |                    S )
NrS   rz   r{   r   
theme_nameu	   同主题)themer}   rk   r@   )rl   rm   get_same_theme_documentsro   r-   r   rp   r   r2   rr   r~   r   )
rQ   rR   rU   rT   r+   r}   r   r5   r   _s
             r%   r^   z%GraphQueryPlanner._collect_theme_docsF  s%      ++D-J+WWWWWWWW 	X))6%	 *        D  X!!W[[66[4;K;Z{
*1$//55   %$8P8PQU8V8VWWWW 	# ??"--!	 . 
 
 
 
 
 
 
 
  	# ??" 
A%%,! & 
 
 
 
 
 
 
 
  	# ??"")''--
 
 
 $0H0H0N0NOOOOr$   r*   c                  K   |j         r|j         S |j        r(|                     |j        |j        |           d {V S d S )NrS   )r+   r,   rm   find_doc_by_code)rQ   rR   rU   rT   s       r%   rl   z!GraphQueryPlanner._resolve_doc_idz  st       ; 	;= 	)).% *         
 tr$   r-   r.   tuple[str, str] | Nonec               B  K   |                      |j        ||d|           d {V }|sd S |                     ||          }|sd S |                    d          pg }|                    d          pi }|                    d          p|}	|r|d         n|pd}
|
sd S |	|
fS )Nrf   )labelr|   rT   labels
propertiesr   r   r3   )rm   search_entities_pick_best_entityro   )rQ   rU   r-   r.   rT   entitiesbestr   propsr   r   s              r%   r~   z!GraphQueryPlanner._resolve_entity  s       )))! * 
 
 
 
 
 
 
 
  	4%%h<< 	4(##)r&&,"yy  /K#=q		,*<" 	4U{r$   c               0   K   |||d<    ||i | d {V S )NrT   r#   )rQ   funcrT   argskwargss        r%   rm   zGraphQueryPlanner._call_graph  s?       !#-F< T4*6*********r$   r   list[dict[str, Any]]	candidatedict[str, Any] | Nonec                t    |sd S |                                 fd|D             }|r|d         n|d         S )Nc                p    g | ]2}|                     d           pi                      d          k    0|3S )r   r   ro   ).0entitycandidate_norms     r%   
<listcomp>z7GraphQueryPlanner._pick_best_entity.<locals>.<listcomp>  sM     
 
 


<((.B33F;;~MM MMMr$   r   rA   )rQ   r   r   exactr   s       @r%   r   z#GraphQueryPlanner._pick_best_entity  sb    
  	4"**
 
 
 
"
 
 

 !1uQxxhqk1r$   itemsc                    |sd S |                                 fd|D             }|r|d         S fd|D             }|r|d         n|d         S )Nc                J    g | ]}|                     d           pdk    | S r   r3   r   r   itemr   s     r%   r   z;GraphQueryPlanner._pick_best_named_item.<locals>.<listcomp>  s4    VVV$DHHV,<,<,B~+U+U+U+U+Ur$   r   c                J    g | ]}r|                     d           pdv | S r   r   r   s     r%   r   z;GraphQueryPlanner._pick_best_named_item.<locals>.<listcomp>  sI     
 
 

 #1TXXf5E5E5K"L"L "L"L"Lr$   r   )rQ   r   r   r   
containingr   s        @r%   r   z'GraphQueryPlanner._pick_best_named_item  s    
  	4"**VVVV%VVV 	8O
 
 
 

 
 


 !+8z!}}a8r$   r5   patternslist[re.Pattern[str]]boolc                :    t          fd|D                       S )Nc              3  B   K   | ]}|                               V  d S N)search)r   patternr5   s     r%   	<genexpr>z1GraphQueryPlanner._matches_any.<locals>.<genexpr>  s/      @@G7>>$''@@@@@@r$   )any)rQ   r5   r   s    ` r%   rC   zGraphQueryPlanner._matches_any  s&    @@@@x@@@@@@r$   c                     t          |          S r   r   rQ   r5   s     r%   rB   z#GraphQueryPlanner._extract_doc_code  s    %d+++r$   c                    |D ]G}|                     |          }|r.|                    d                              d          }|r|c S Hd S )Nr   u    ，。？?、)r   grouprA   )rQ   r5   r   r   r   r   s         r%   rG   z(GraphQueryPlanner._extract_named_segment  sc    
   	  	 GNN4((E  {{6**001ABB  KKKtr$   c                      fd|D             }|rt          |t                    S d |D             }|rt          |t                    nd S )Nc                B    g | ]}                     |          d v |S )>   RegionOrganization)rJ   r   kwrQ   s     r%   r   z<GraphQueryPlanner._pick_entity_candidate.<locals>.<listcomp>  s2    ooo"D4L4LR4P4PTn4n4n24n4n4nr$   keyc                F    g | ]}t                               |          |S r#   )_TIME_HINT_REr   )r   r   s     r%   r   z<GraphQueryPlanner._pick_entity_candidate.<locals>.<listcomp>  s+    JJJ21E1Eb1I1IJBJJJr$   maxlen)rQ   r=   entity_keywordsfiltereds   `   r%   rI   z(GraphQueryPlanner._pick_entity_candidate  sg    ooooooo 	1C0000JJJJJ)1;s8%%%%t;r$   c                ,    |                      |          S r   _pick_generic_candidaterQ   r=   s     r%   rM   z(GraphQueryPlanner._pick_matter_candidate      ++H555r$   c                ,    |                      |          S r   r   r   s     r%   rP   z'GraphQueryPlanner._pick_theme_candidate  r   r$   c                T      fd|D             }|rt          |t                    ndS )zIPick the longest non-time, non-org/region keyword as a generic candidate.c                v    g | ]5}|r1t                               |          s                    |          3|6S r   )r   r   rJ   r   s     r%   r   z=GraphQueryPlanner._pick_generic_candidate.<locals>.<listcomp>  sZ     
 
 

 (..r22
 8<7O7OPR7S7S7[ 7[7[7[r$   r   Nr   )rQ   r=   r   s   `  r%   r   z)GraphQueryPlanner._pick_generic_candidate  sJ    
 
 
 

 
 

 *2;s8%%%%t;r$   c                ~    |sd S t                               |          rdS t                              |          rdS d S )Nr   r   )_ORG_HINT_REr   _REGION_HINT_REr   s     r%   rJ   z%GraphQueryPlanner._infer_entity_label  sJ     	4t$$ 	"!>!!$'' 	8tr$   ri   rj   c                    d |D             }g }|D ]}|                     |                     d          d|                     d          i          }|                     |                     d          d|                     d          i          }|                                         |           d                     |                      |s fd|D             }d                    |          S )Nc                J    i | ] }|                     d           |d          |!S r+   r   r   docs     r%   
<dictcomp>z:GraphQueryPlanner._format_policy_chain.<locals>.<dictcomp>  s/    LLL##''(:K:KL3x=#LLLr$   from_doc_idr+   	to_doc_idu   
  → 依据 → c                :    g | ]}                     |          S r#   _format_doc_titler   r   rQ   s     r%   r   z:GraphQueryPlanner._format_policy_chain.<locals>.<listcomp>  s'    BBBST++C00BBBr$   
ro   appendr   join)rQ   ri   rj   doc_maplinesedgesrctgts   `       r%   rq   z&GraphQueryPlanner._format_policy_chain
  s   
 MLLLL 	k 	kD++dhh}55$((=BYBY7Z[[C++dhh{33h@U@U5VWWCLLD22377iiDLbLbcfLgLgiijjjj 	CBBBBEBBBEyyr$   ru   c                ~    d |D             }ddd}g }|D ]}|                     |                     d          |                     d          pd          }|                     |                     d          d|                     d          i          }|                     |                     d	          d|                     d	          i          }	|                    d
                     |           d| d                     |	                      |s fd|D             }d                    |          S )Nc                J    i | ] }|                     d           |d          |!S r   r   r   s     r%   r   z>GraphQueryPlanner._format_revision_history.<locals>.<dictcomp>  s/    PPP#cggh>O>OP3x=#PPPr$   u   修订u   废止)AMENDSREPEALSrel_typeu   关联r   r+   r   - u    → c                @    g | ]}d                      |           S )r   r   r   s     r%   r   z>GraphQueryPlanner._format_revision_history.<locals>.<listcomp>)  s0    MMMC7$005577MMMr$   r   r   )
rQ   ru   rj   r   	rel_namesr   r   rel_namer   r   s
   `         r%   rx   z*GraphQueryPlanner._format_revision_history  sL   
 QPPPP'H==	 	n 	nD }}TXXj%9%9488J;O;O;[S[\\H++dhh}55$((=BYBY7Z[[C++dhh{33h@U@U5VWWCLLld44S99lllltOeOefiOjOjllmmmm 	NMMMM9MMMEyyr$   r}   c                H   g }|d d         D ] }|                     d          pd}|                     d          p|                     d          pd}|                     d          pd}|                     d          pd}g }|r|                    |           |r"|                    t          |                     |r|                    |           |rd	d
                    |           dnd}	|                    d| d|	            d                    |          S )Nrz   title   （无标题）
doc_numberr,   r3   r   publish_dateu   （   ；u   ）u   - 《   》r   )ro   r   r4   r   )
rQ   r}   r   r   r  r  r   r  extrasuffixs
             r%   r   z#GraphQueryPlanner._format_docs_list,  sB   8 	5 	5CGGG$$9(9E..K#''*2E2EKJwwz**0bH77>228bL!E )Z((( 0S../// 'X&&&5:B15::e,,1111FLL3336334444yyr$   list[dict[str, Any]] | Noner   c               l    |s|S d |D             }d |D             }|rd                     |          n|S )Nc                b    g | ],}|                     d           p|                     d          -S )r   	full_namer   r   r   s     r%   r   z8GraphQueryPlanner._format_prop_names.<locals>.<listcomp>F  s5    MMMt&!!:TXXk%:%:MMMr$   c                    g | ]}||S r#   r#   )r   r   s     r%   r   z8GraphQueryPlanner._format_prop_names.<locals>.<listcomp>G  s    000$40000r$   r  )r   )rQ   r   r   namess       r%   r   z$GraphQueryPlanner._format_prop_names>  sS      	LMMuMMM00%000$)4uzz%   u4r$   c           	        |sdS g }|d d         D ]h}|                     d          pd}|                     d          p|                     d          pd}|                    d| d	|rd
| dnd            id                    |          S )Nu   未提取到明确依据文件rf   r  r  r,   r  r3      《r  ()r  )ro   r   r   )rQ   r}   partsr   r  r,   s         r%   r   z"GraphQueryPlanner._format_gov_docsJ  s     	4338 	P 	PCGGG$$9(9Ewwz**Icggl.C.CIrHLLNuNNH)LX"NNOOOOzz%   r$   c                h    d |D             }t          t                              |                    S )Nc                b    g | ],}|                     d           |                     d           -S r   r   r  s     r%   r   z9GraphQueryPlanner._doc_ids_from_items.<locals>.<listcomp>U  s5    NNN$488H;M;MN488H%%NNNr$   )r9   dictfromkeys)rQ   r   ordereds      r%   rr   z%GraphQueryPlanner._doc_ids_from_itemsT  s/    NN%NNNDMM'**+++r$   r   dict[str, Any]c                    |                     d          pd}|                     d          p|                     d          pd}d| d|rd| d	nd S )
Nr  r  r,   r  r3   r  r  r  r  r   )rQ   r   r  r,   s       r%   r   z#GraphQueryPlanner._format_doc_titleX  sg      5$577:&&E#'',*?*?E2DUDD(B8DDDr$   )r<   r4   r=   r7   r>   r(   )rR   r(   rU   r   rT   rV   r>   r2   )rR   r(   rU   r   rT   rV   r>   r*   )
rU   r   r-   r4   r.   r*   rT   rV   r>   r   )rT   rV   )r   r   r   r4   r>   r   )r   r   r   r4   r>   r   )r5   r4   r   r   r>   r   )r5   r4   r>   r*   )r5   r4   r   r   r>   r*   )r=   r7   r>   r*   )r5   r*   r>   r*   )ri   r   rj   r   r>   r4   )ru   r   rj   r   r>   r4   )r}   r   r>   r4   )r   r  r   r4   r>   r4   )r}   r  r>   r4   )r   r   r>   r7   )r   r  r>   r4   )r   r   r   r   rR   rd   rZ   r[   r\   r]   r^   rl   r~   rm   r   r   rC   rB   rG   rI   rM   rP   r   rJ   rq   rx   r   r   r   rr   r   r#   r$   r%   r;   r;   o   s        *5 *5 *5 *5b (,     J (,Q Q Q Q Q Q@ (,U U U U U U@ (,"P "P "P "P "P "PR (,'
 '
 '
 '
 '
 '
\ (,2P 2P 2P 2P 2P 2Pr (,     . (,     D (,		+ 	+ 	+ 	+ 	+ 	+2 2 2 2 9 9 9 9(A A A A, , , ,   < < < <6 6 6 66 6 6 6< < < <                  $       $
5 
5 
5 
5! ! ! !, , , ,E E E E E Er$   r;   )*r   
__future__r   redataclassesr   r   enumr   typingr   app.core.graph_query_servicer   app.prompts.query_planningr	   r
   r   r   r   app.utils.doc_coder   app.utils.loggerr   r   r`   r4   r   r(   r2   compiler   r   r   rD   rE   rF   rK   rN   rL   rH   rO   r;   r#   r$   r%   <module>r(     s    # " " " " " 				 ( ( ( ( ( ( ( (             : : : : : :              6 5 5 5 5 5 ' ' ' ' ' '	H		    #t    $ $ $ $ $ $ $ $ 5 5 5 5 5 5 5 5 rzY  "*>??
LMM BJIJJ  BJHII  BJGHH  BJlmm  BJ:;; 
 BJ	z   BJ	E   BJ	x  lE lE lE lE lE lE lE lE lE lEr$   