
    IBi                        d 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  ee          Z G d d	          Zd
S )u  Graph query service — Neo4j-backed helpers for entity search and sub-graph retrieval.

Used by:
  - ResearchEngine  (entity-based doc discovery)
  - Graph API       (frontend knowledge-graph explorer)

知识图谱查询服务模块。
提供面向业务的图谱只读查询接口，包括：
- 实体全文搜索和邻域子图获取
- 通过实体发现关联文档
- 文档推荐（同机构/同主题/同区域）
- 政策依据链和修订历史追溯
- 事项知识卡片（条件/材料/时限/办理机构）
    )annotations)Any)settings)
get_schema)Neo4jClient)
get_loggerc                  f   e Zd ZdZdIdZe	 	 dJdKd            ZedLd            ZdLdZdMdZ	dNdZ
dddddOdZdddPd!Zd"dd#dQd&ZdddRd(Zd)dd*dSd.ZdddTd1Zd2d3dd4dUd7Zddd8ddd9dVd;Zddd#dWd<Zd=dd>dXd@Zd=dd>dXdAZddd#dYdCZdddZdDZddd[dFZddd[dGZddd#d\dHZdS )]GraphQueryServiceu   Query the Neo4j knowledge graph for entities and document relationships.

    知识图谱查询服务，封装了实体搜索、文档发现、推荐、
    政策链追溯和事项卡片等只读查询操作。
    neo4j_clientr   returnNonec                    || _         d S )N)_neo4j)selfr   s     6D:\work\zm-rag\backend\app\core\graph_query_service.py__init__zGraphQueryService.__init__#   s    "    Nparamsdict[str, Any] | None
acl_tokenslist[str] | Nonedict[str, Any]c                6    t          | pi           }|||d<   |S )Nr   dict)r   r   query_paramss      r   _query_paramszGraphQueryService._query_params&   s+    
 FLb))!)3L&r   aliasstrc                D    d|  d}||S |s	d| d|  dS d| d|  d|  dS )	Nz	coalesce(z.is_placeholder, false) = false(z AND size(coalesce(z.acl_ids, [])) = 0)z AND (size(coalesce(z,.acl_ids, [])) = 0 OR any(token IN coalesce(z+.acl_ids, []) WHERE token IN $acl_tokens))) )r   r   bases      r   _document_visibility_clausez-GraphQueryService._document_visibility_clause0   s    
 B5AAAK 	KJtJJJJJJ[ [ [#[ [(-[ [ [	
r   c                <    d| d|                      ||           dS )Nz(NOT z:Document OR )r$   )r   r   r   s      r   _path_node_visibility_clausez.GraphQueryService._path_node_visibility_clause@   s/    
 buaa4+K+KES]+^+^aaaar   
node_aliasc                <    d| d|                      d|           dS )NzEXISTS { MATCH (z")-[]-(visible_doc:Document) WHERE visible_doc }r'   )r   r)   r   s      r   _visible_doc_exists_clausez,GraphQueryService._visible_doc_exists_clauseG   s@    V
 V V55mZPPV V V	
r   matter_aliasc                <    d| d|                      d|           dS )Nz3EXISTS { MATCH (visible_doc:Document)-[:GOVERNS]->(z) WHERE r+   r,   r'   )r   r.   r   s      r   $_visible_governing_doc_exists_clausez6GraphQueryService._visible_governing_doc_exists_clauseQ   s@    V< V V55mZPPV V V	
r      )labellimitr   namer2   
str | Noner3   intlist[dict[str, Any]]c          
     @  K   |rFt                      }|                                }||vrg S d| d|                     d|           d}nkt                      }t          |                                          }d                    d |D                       }	d|	 d|                     d|           d}| j        j                            t          j	        	          4 d
{V 	 }
 |
j
        |fi |                     ||d|           d
{V }|                                 d
{V }d
d
d
          d
{V  n# 1 d
{V swxY w Y   g }|D ]I}|                    |d         t          |d                   t          |d         pi           d           J|S )av  Fulltext-search entity nodes by name.

        Parameters
        ----------
        name:
            Search term (partial match via CONTAINS).
        label:
            Optional node label to restrict the search.
        limit:
            Maximum number of results.

        Returns
        -------
        list of dicts with ``id``, ``labels``, ``properties``.
        z	MATCH (n:z") WHERE n.name CONTAINS $name AND nzR RETURN elementId(n) AS eid, labels(n) AS lbs, properties(n) AS props LIMIT $limit OR c              3      K   | ]	}d | V  
dS )zn:Nr"   .0ens     r   	<genexpr>z4GraphQueryService.search_entities.<locals>.<genexpr>   s(      #E#E"III#E#E#E#E#E#Er   zMATCH (n) WHERE (z ) AND n.name CONTAINS $name AND databaseNr4   r3   eidlbspropsidlabels
properties)r   entity_type_namesr-   sortedjoinr   driversessionr   neo4j_databaserunr   dataappendlistr   )r   r4   r2   r3   r   schemavalid_labelscypherentity_names	or_clauserN   resultrecordsentitiesrecs                  r   search_entitiesz!GraphQueryService.search_entities_   s     .  	\\F!3355LL((	E  66sJGG   F  \\F!&":":"<"<==L#E#E#E#E#EEEI#  66sJGG    ;%--, . 
 
 	* 	* 	* 	* 	* 	* 	* 	*&7; $$dU%C%CZPP       F #KKMM))))))G	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	*  	 	COOe*"3u:.."&s7|'9r":":     s   'AD??
E	E	)r   	entity_idc          	       K   d|                      d|           d}| j        j                            t          j                  4 d{V 	 } |j        |fi |                     d|i|           d{V }|                                 d{V }ddd          d{V  n# 1 d{V swxY w Y   |sdS |d         t          |d                   t          |d         pi           d	S )
zJReturn a single entity node only when it is attached to visible documents.z;MATCH (n) WHERE elementId(n) = $eid AND NOT n:Document AND r9   zM RETURN elementId(n) AS eid, labels(n) AS lbs, properties(n) AS props LIMIT 1r@   NrC   rD   rE   rF   )r-   r   rM   rN   r   rO   rP   r   singlerS   r   )r   r^   r   rV   rN   rY   records          r   
get_entityzGraphQueryService.get_entity   s      223
CC   	 ;%--, . 
 
 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+&7; $$eY%7DD       F "==??******F	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+  	4-6%=))vg4"55
 
 	
   AB%%
B/2B/   )r3   r   entity_nameentity_labelc          
     \  K   |dk    r2d}|                      d|           d|                      d|           }nDt                      }|                                }||vrg S d| d}|                      d|          }d| d	| d
}	| j        j                            t          j                  4 d{V 	 }
 |
j        |	fi | 	                    ||d|           d{V }|
                                 d{V }ddd          d{V  n# 1 d{V swxY w Y   d |D             S )zReturn all documents connected to an entity (any relationship).

        Returns
        -------
        list of dicts with ``doc_id``, ``title``, ``doc_number``,
        ``rel_type`` (relationship type that connects them).
        Documentz(e:Document {doc_id: $name})ez AND dz(e:z {name: $name})zMATCH z-[r]-(d:Document) WHERE z RETURN d.doc_id AS doc_id, d.title AS title, coalesce(d.doc_code, d.doc_number, '') AS doc_number, type(r) AS rel_type ORDER BY d.publish_date DESC LIMIT $limitr@   NrB   c                d    g | ]-}|d          
|d          |d         pd|d         pd|d         d.S )doc_idtitle 
doc_numberrel_type)rl   rm   ro   rp   r"   r=   rs     r   
<listcomp>z8GraphQueryService.get_docs_by_entity.<locals>.<listcomp>   sa     	
 	
 	
 {	
H+7)ro3jM	 	
 	
 	
r   )r$   r   rJ   r   rM   rN   r   rO   rP   r   rQ   )r   re   rf   r3   r   match_clausewhere_clauserT   rU   rV   rN   rY   rZ   s                r   get_docs_by_entityz$GraphQueryService.get_docs_by_entity   sM      :%%9L33CDD K K77ZHHK K L  \\F!3355L<//	@@@@L;;CLLL\  !   	 ;%--, . 
 
 	* 	* 	* 	* 	* 	* 	* 	*&7; $$kE%J%JJWW       F #KKMM))))))G	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	*	
 	
 	
 	
 	
 		
s   9AD
DDdoc_codec          	       K   |                                 }|sdS d}|                    d|                     d|                    }| j        j                            t          j                  4 d{V 	 } |j        |fi | 	                    d|i|           d{V }|
                                 d{V }ddd          d{V  n# 1 d{V swxY w Y   |r|                    d          r|d         ndS )z9Resolve a document code to its doc_id for query planning.Na  
        MATCH (d:Document)
        WHERE (
            coalesce(d.doc_code, '') CONTAINS $code
            OR coalesce(d.doc_number, '') CONTAINS $code
        )
          AND __DOC_VISIBILITY__
        RETURN d.doc_id AS doc_id
        ORDER BY CASE WHEN coalesce(d.doc_code, d.doc_number, '') = $code THEN 0 ELSE 1 END,
                 d.publish_date DESC
        LIMIT 1
        __DOC_VISIBILITY__rj   r@   coderl   )stripreplacer$   r   rM   rN   r   rO   rP   r   r`   get)r   rw   r   rz   rV   rN   rY   ra   s           r   find_doc_by_codez"GraphQueryService.find_doc_by_code  s      ~~ 	4  ,,S*==
 

 ;%--, . 
 
 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+&7; $$fd^Z@@       F "==??******F	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ $*Lfjj.B.BLvhLs   8AC
CC
   )limit_per_keywordr   keywords	list[str]r   c          
       K   |sg S t                      }t          |                                          }d                    d |D                       }d| d|                     d|           d}|t          |          z  }| j        j                            t          j
                  4 d{V 	 }	 |	j        |fi |                     ||d	|           d{V }
|
                                 d{V }ddd          d{V  n# 1 d{V swxY w Y   d
 |D             }t          t                              |                    S )a?  Find doc_ids connected to any entity whose name contains a keyword.

        Parameters
        ----------
        keywords:
            List of search terms.
        limit_per_keyword:
            Maximum documents per keyword.

        Returns
        -------
        Deduplicated list of ``doc_id`` strings.
        r:   c              3      K   | ]	}d | V  
dS )ze:Nr"   r<   s     r   r?   z:GraphQueryService.search_graph_for_docs.<locals>.<genexpr>D  s(      AAb	R		AAAAAAr   zR
        UNWIND $keywords AS kw
        MATCH (e)-[r]-(d:Document)
        WHERE (z;)
          AND e.name CONTAINS kw
                    AND rj   zI
        RETURN DISTINCT d.doc_id AS doc_id
        LIMIT $limit
        r@   N)r   r3   c                H    g | ]}|                     d           |d           S )rl   r}   rq   s     r   rs   z;GraphQueryService.search_graph_for_docs.<locals>.<listcomp>\  s+    CCC1155??C1X;CCCr   )r   rK   rJ   rL   r$   lenr   rM   rN   r   rO   rP   r   rQ   rS   r   fromkeys)r   r   r   r   rT   rW   rX   rV   total_limitrN   rY   rZ   doc_idss                r   search_graph_for_docsz'GraphQueryService.search_graph_for_docs*  s-     (  	I f668899KKAALAAAAA	  
 99#zJJ   (#h--7;%--, . 
 
 
	* 
	* 
	* 
	* 
	* 
	* 
	* 
	*&7; $$!)K@@        F #KKMM))))))G
	* 
	* 
	* 
	* 
	* 
	* 
	* 
	* 
	* 
	* 
	* 
	* 
	* 
	* 
	* 
	* 
	* 
	* 
	* 
	* 
	* 
	* 
	* 
	* 
	* 
	* 
	* DCCCCDMM'**+++s   5AD
DDrl   dict[str, list[dict[str, Any]]]c          	     8  K   d|                      d|           d}| j        j                            t          j                  4 d{V 	 } |j        |fi |                     d|i|           d{V }|                                 d{V }ddd          d{V  n# 1 d{V swxY w Y   i }|D ]_}|d         r|d         d         nd	}	|	|vrg ||	<   ||	         	                    |d
         |d         pd|d         |d         pi d           `|S )zReturn all entities directly connected to a document, grouped by label.

        Returns
        -------
        dict mapping entity label -> list of entity dicts (id, name, properties).
        zD
        MATCH (d:Document {doc_id: $doc_id})-[r]-(e)
        WHERE rj   z
          AND NOT e:Document
        RETURN labels(e) AS labels, e.name AS name, elementId(e) AS eid,
               type(r) AS rel_type, properties(e) AS props
        ORDER BY labels(e)[0], e.name
        r@   Nrl   rH   r   UnknownrC   r4   rn   rp   rE   )rG   r4   rp   rI   )
r$   r   rM   rN   r   rO   rP   r   rQ   rR   )
r   rl   r   rV   rN   rY   rZ   groupedr\   r2   s
             r   get_document_entitiesz'GraphQueryService.get_document_entitiesd  s     //Z@@   ;%--, . 
 
 	* 	* 	* 	* 	* 	* 	* 	*&7; $$h%7DD       F #KKMM))))))G	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 46 	 	C(+HDCM!$$9EG##!#EN!!e*K-2 #J"%g,"4"	     rc      d   )depth	max_nodesr   r   r   c          
     N  K   t          dt          t          |          d                    }t          dt          |                    }d| d|                     d|           d| d}| j        j                            t          j                  4 d	{V 	 } |j	        |fi | 
                    d
|i|           d	{V }	|	                                 d	{V }
d	d	d	          d	{V  n# 1 d	{V swxY w Y   |
sg g dS t                      }g }|
d         pg D ]n}|d
         |vrb|                    |d
                    |                    |d
         t          |d                   t!          |d         pi           d           ot                      }g }|
d         pg D ]s}|d         |d         |d         f}||vrV|                    |           |                    |d         |d         |d         t!          |d         pi           d           t||dS )az  Return the subgraph neighbourhood of an entity node.

        Parameters
        ----------
        entity_id:
            Neo4j element ID of the start node.
        depth:
            BFS depth from the start node.
        max_nodes:
            Hard limit on the number of nodes returned.

        Returns
        -------
        ``{"nodes": [...], "edges": [...]}``
              zMATCH path = (start)-[*1..zM]-(neighbor) WHERE elementId(start) = $eid AND all(node IN nodes(path) WHERE nodea  ) WITH path LIMIT 5000 UNWIND relationships(path) AS rel WITH DISTINCT rel WITH   collect(DISTINCT {eid: elementId(startNode(rel)), lbs: labels(startNode(rel)), props: properties(startNode(rel))})   + collect(DISTINCT {eid: elementId(endNode(rel)),   lbs: labels(endNode(rel)),   props: properties(endNode(rel))})   AS all_nodes,   collect(DISTINCT {src: elementId(startNode(rel)), tgt: elementId(endNode(rel)),                     typ: type(rel), props: properties(rel)}) AS all_rels RETURN all_nodes[..z] AS nodes, all_rels AS relsr@   NrC   )nodesedgesr   rD   rE   rF   relssrctgttyp)sourcetargettyperI   )maxminr6   r(   r   rM   rN   r   rO   rP   r   r`   setaddrR   rS   r   )r   r^   r   r   r   
depth_safemax_nodes_saferV   rN   rY   ra   
seen_nodesr   r9   
seen_edgesr   rr   keys                     r   get_entity_neighborhoodz)GraphQueryService.get_entity_neighborhood  sC     6 CE

A..//
QI//O O O151R1RSY[e1f1fO O #1O O O 	 ;%--, . 
 
 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+&7; $$eY%7DD       F "==??******F	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+  	."---  #uu
/'R 	 	Axz))qx(((E("1U8nn"&qz'7R"8"8     "%
.&B 		 		AU8QuXqx0C*$$s###hheH"&qz'7R"8"8	     ///s   AC33
C= C=   )r2   re   r3   cursorr   r   c          	     6	  K   |rt          |          nd}|r|rt                      }|                                }||v r|nt          t	          |          d          }d| d|                     d|           d}	d| d|                     d|           d}
|                     |||d	|          }|                     d
|i|          }ncd|                     d|           d}	d|                     d|           d}
|                     ||d|          }|                     i |          }| j        j        	                    t          j                  4 d{V 	 } |j        |	fi | d{V }|                                 d{V } |j        |
fi | d{V }|                                 d{V }ddd          d{V  n# 1 d{V swxY w Y   |r|d         nd}t                      }t                      }g }g }|D ]h}|                    d          }|rp||vrl|                    |           |                    |t'          |d         pg           t)          |d         pi           |                    d          d           |                    d          }|r\||vrX|                    |           |                    |t'          |d         pg           t)          |d         pi           d           |                    d          }|                    d          }|                    d          }|r;|r9|r7|||f}||vr.|                    |           |                    |||d           |                    d          }|r\||vrX|                    |           |                    |t'          |d          pg           t)          |d!         pi           d           |                    d"          }|                    d#          } |                    d$          }!|r;| r9|!r7|| |!f}||vr.|                    |           |                    || |!d           j||z   }"|"|k     rt+          |"          nd}#|||#|d%S )&u*  Return a graph overview – documents + their direct entity neighbours.

        When ``entity_name`` + ``label`` are provided, shows documents connected
        to that specific entity, plus all entities adjacent to those documents.
        Otherwise returns the most-connected documents and their entity neighbours.

        Supports offset-based cursor pagination for progressive loading.

        Returns
        -------
        ``{"nodes": [...], "edges": [...], "next_cursor": "...", "total_count": N}``
        suitable for the G6 canvas.
        r   OrganizationzMATCH (anchor:z( {name: $name})-[r0]-(d:Document) WHERE rj   aI   WITH anchor, d, r0 ORDER BY d.publish_date DESC SKIP $skip LIMIT $limit OPTIONAL MATCH (d)-[r1]-(e) WHERE NOT e:Document RETURN   elementId(anchor) AS anchor_eid, labels(anchor) AS anchor_lbs, properties(anchor) AS anchor_props,   elementId(d) AS doc_eid, labels(d) AS doc_lbs, properties(d) AS doc_props,   elementId(startNode(r0)) AS r0_src, elementId(endNode(r0)) AS r0_tgt, type(r0) AS r0_type,   elementId(e)            AS ent_eid, labels(e)   AS ent_lbs,   properties(e)   AS ent_props,   elementId(startNode(r1)) AS r1_src, elementId(endNode(r1)) AS r1_tgt, type(r1) AS r1_typez& {name: $name})-[]-(d:Document) WHERE z" RETURN count(DISTINCT d) AS total)r4   r3   skipr4   z!MATCH (d:Document)-[r0]-() WHERE a   WITH d, count(r0) AS degree ORDER BY degree DESC SKIP $skip LIMIT $limit OPTIONAL MATCH (d)-[r1]-(e) WHERE NOT e:Document RETURN   elementId(d) AS doc_eid, labels(d) AS doc_lbs, properties(d) AS doc_props, degree,   elementId(e)            AS ent_eid, labels(e)   AS ent_lbs,   properties(e)   AS ent_props,   elementId(startNode(r1)) AS r1_src, elementId(endNode(r1)) AS r1_tgt, type(r1) AS r1_typez5 WITH d, count(r0) AS degree RETURN count(d) AS total)r3   r   r@   Ntotaldoc_eiddoc_lbs	doc_propsdegree)rG   rH   rI   r   
anchor_eid
anchor_lbsanchor_propsrF   r0_srcr0_tgtr0_type)r   r   r   ent_eident_lbs	ent_propsr1_srcr1_tgtr1_type)r   r   next_cursortotal_count)r6   r   rJ   nextiterr$   r   r   rM   rN   r   rO   rP   rQ   r`   r   r}   r   rR   rS   r   r   )$r   r2   re   r3   r   r   r   rT   rU   rV   count_cypherr   count_paramsrN   rY   rZ   count_resultcount_recordr   r   r   r   r   r\   r   r   r   r   r   ekeyr   r   r   r   next_offsetr   s$                                       r   get_docs_overviewz#GraphQueryService.get_docs_overview  sq     , %+s6{{{! 8	>5 8	>\\F!3355L"l22EET,=O=OQ_8`8`En n n99#zJJn n n  4 4 499#zJJ4 4 4 
 &*%7%7$udCC& &F ,0+=+=%, ,LL	n99#zJJ	n 	n 	n +99#zJJ+ + +  ''%(F(F
SSF--b*==L;%--, . 
 
 	7 	7 	7 	7 	7 	7 	7 	7&7;v8888888888F"KKMM))))))G!,\!J!J\!J!JJJJJJJL!-!4!4!6!6666666L	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 0<Bl7++"uu
03
&(&( 2	X 2	XCggi((G 7*44w'''!"3y>#7R88"&s;'7'=2">">!ggh//	     ..J j
::z***$"3|#4#:;;"&s>':'@b"A"A     WWX&&FWWX&&Fggi((G X& XW X.4fg-Fz))NN4(((LLFfg!V!VWWW ggi((G 7*44w'''!"3y>#7R88"&s;'7'=2">">     WWX&&FWWX&&Fggi((G X& XW X0z))NN4(((LLFfg!V!VWWW Ul*5*C*Cc+&&& &&	
 
 	
s    AG
GGc               h   K   ddl }d                     d|           d                     d|           d}d| d	}d
| d}d| d}                     ||d|          ddd fd}	|                     |	|           |	|           |	|                     d{V \  }
}}|
||dS )u  Return documents related to the given document through shared entities.

        Finds documents that share Organization, Region, or PolicyTheme
        with the given document.

        Returns
        -------
        dict with ``same_org``, ``same_theme``, ``same_region`` lists,
        each containing ``doc_id``, ``title``, ``doc_code``, ``publish_date``,
        ``shared_entity``.

        使用 asyncio.gather 并行执行三条 Cypher 查询，减少总耗时。
        r   NzWHERE rj   z! AND other.doc_id <> $doc_id AND otheru.    AND coalesce(other.status, '') <> '已废止'zv
        MATCH (d:Document {doc_id: $doc_id})-[:ISSUED_BY]->(org:Organization)<-[:ISSUED_BY]-(other:Document)
        a  
        RETURN other.doc_id AS doc_id, other.title AS title,
               other.doc_code AS doc_code, other.publish_date AS publish_date,
               org.name AS shared_entity, other.status AS status
        ORDER BY other.publish_date DESC
        LIMIT $limit
        z
        MATCH (d:Document {doc_id: $doc_id})-[:BELONGS_TO_THEME]->(t:PolicyTheme)<-[:BELONGS_TO_THEME]-(other:Document)
        a  
        RETURN other.doc_id AS doc_id, other.title AS title,
               other.doc_code AS doc_code, other.publish_date AS publish_date,
               t.name AS shared_entity, other.status AS status
        ORDER BY other.publish_date DESC
        LIMIT $limit
        z~
        MATCH (d:Document {doc_id: $doc_id})-[:APPLIES_TO_REGION]->(r:Region)<-[:APPLIES_TO_REGION]-(other:Document)
        a  
        RETURN other.doc_id AS doc_id, other.title AS title,
               other.doc_code AS doc_code, other.publish_date AS publish_date,
               r.name AS shared_entity, other.status AS status
        ORDER BY other.publish_date DESC
        LIMIT $limit
        rl   r3   rZ   
list[dict]r   r7   c                    d | D             S )u8   将 Cypher 查询结果解析为标准化字典列表。c           
         g | ]N}|d          
|d          |d         pd|d         pd|d         pd|d         pd|                     d          pddOS )rl   rm   rn   rw   publish_dateshared_entitystatus)rl   rm   rw   r   r   r   r   rq   s     r   rs   zZGraphQueryService.get_document_recommendations.<locals>._parse_records.<locals>.<listcomp>  s        X;kwZ-2 !* 3$%n$5$;%&%7%=2eeHoo3   r   r"   )rZ   s    r   _parse_recordszFGraphQueryService.get_document_recommendations.<locals>._parse_records  s%      !   r   rV   r   c                (  K   j         j                            t          j                  4 d{V 	 } |j        | fi  d{V }|                                 d{V } |          cddd          d{V  S # 1 d{V swxY w Y   dS )u/   在独立会话中执行单条 Cypher 查询。r@   N)r   rM   rN   r   rO   rP   rQ   )rV   rN   resrZ   r   r   r   s       r   
_run_queryzBGraphQueryService.get_document_recommendations.<locals>._run_query  sE     {)11!0 2   / / / / / / / /'GK99&99999999 #

******%~g../ / / / / / / / / / / / / / / / / / / / / / / / / / / / / /s   9B
BB)same_org
same_themesame_region)rZ   r   r   r7   )rV   r   r   r7   )asyncior$   r   gather)r   rl   r3   r   r   _filter
cypher_orgcypher_themecypher_regionr   org_resultstheme_resultsregion_resultsr   r   s   `            @@r   get_document_recommendationsz.GraphQueryService.get_document_recommendations  s~     ( 	=T55c:FF = =44WjII= = = 		  
	  	   ##v$F$F
SS	 	 	 		/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ <C>>Jz""J|$$J}%%<
 <
 6
 6
 6
 6
 6
 6
2]N $')
 
 	
r   r   )	max_depthr   r   c          	       K   t          dt          t          |          d                    }d| d|                     d|           d}d| d|                     d|           d}| j        j                            t          j                  4 d	{V 	 } |j	        |fi | 
                    d
|i|           d	{V }|                                 d	{V }	 |j	        |fi | 
                    d
|i|           d	{V }
|
                                 d	{V }d	d	d	          d	{V  n# 1 d	{V swxY w Y   d |	D             }d |D             }||dS )zTraverse BASED_ON relationships to find the policy dependency chain.

        Returns
        -------
        dict with ``chain`` (list of document dicts in dependency order)
        and ``edges`` (list of relationship dicts).
        r   r   zC
        MATCH path = (d:Document {doc_id: $doc_id})-[:BASED_ON*1..zC]->(ancestor:Document)
        WHERE all(node IN nodes(path) WHERE r   9  )
        UNWIND nodes(path) AS n
        WITH DISTINCT n
        RETURN n.doc_id AS doc_id, n.title AS title, n.doc_code AS doc_code,
               n.publish_date AS publish_date, n.status AS status,
               coalesce(n.is_placeholder, false) AS is_placeholder
        ORDER BY n.publish_date ASC
        )
        UNWIND relationships(path) AS rel
        WITH DISTINCT rel
        RETURN startNode(rel).doc_id AS from_doc_id,
               endNode(rel).doc_id AS to_doc_id,
               type(rel) AS rel_type
        r@   Nrl   c           	         g | ]?}|d          
|d          |d         pd|d         pd|d         pd|d         pd|d         d@S rl   rm   rn   rw   r   r   is_placeholder)rl   rm   rw   r   r   r   r"   rq   s     r   rs   z6GraphQueryService.get_policy_chain.<locals>.<listcomp>%  s|     
 
 
 {
H+7)rjM/R !. 1 7RH++"#$4"5 
 
 
r   c                ^    g | ]*}|d          
|d         |d          |d         |d         d+S from_doc_id	to_doc_idrp   )r   r   rp   r"   rq   s     r   rs   z6GraphQueryService.get_policy_chain.<locals>.<listcomp>2  `     
 
 
 
 %&kN
 /{^jM 
 
 
r   )chainr   r   r   r6   r$   r   rM   rN   r   rO   rP   r   rQ   )r   rl   r   r   r   rV   cypher_edgesrN   r   chain_recordsres2edge_recordsr   r   s                 r   get_policy_chainz"GraphQueryService.get_policy_chain  s      CI3344
	EO	 	-1-M-MfV`-a-a	 	 	EO -1-M-MfV`-a-a   ;%--, . 
 
 	- 	- 	- 	- 	- 	- 	- 	-# $$h%7DD       C #&((**,,,,,,M$ $$h%7DD       D "&,,,,,,L	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	-
 
 #
 
 

 
 "
 
 
 ///   B	D44
D>D>c          	       K   t          dt          t          |          d                    }d| d|                     d|           d}d| d|                     d|           d}| j        j                            t          j                  4 d	{V 	 } |j	        |fi | 
                    d
|i|           d	{V }|                                 d	{V }	 |j	        |fi | 
                    d
|i|           d	{V }
|
                                 d	{V }d	d	d	          d	{V  n# 1 d	{V swxY w Y   d |	D             }d |D             }||dS )aE  Find the amendment and repeal history of a document.

        Traverses AMENDS and REPEALS relationships in both directions
        to construct the full revision timeline.

        Returns
        -------
        dict with ``documents`` (list) and ``edges`` (list with ``rel_type``
        being AMENDS or REPEALS).
        r   r   zI
        MATCH path = (d:Document {doc_id: $doc_id})-[:AMENDS|REPEALS*1..zA]-(related:Document)
        WHERE all(node IN nodes(path) WHERE r   r   r   r@   Nrl   c           	         g | ]?}|d          
|d          |d         pd|d         pd|d         pd|d         pd|d         d@S r   r"   rq   s     r   rs   z:GraphQueryService.get_revision_history.<locals>.<listcomp>u  s|     
 
 
 {
H+7)rjM/R !. 1 7RH++"#$4"5 
 
 
r   c                ^    g | ]*}|d          
|d         |d          |d         |d         d+S r   r"   rq   s     r   rs   z:GraphQueryService.get_revision_history.<locals>.<listcomp>  r   r   )	documentsr   r   )r   rl   r   r   r   rV   r   rN   r   doc_recordsr   r   r   r   s                 r   get_revision_historyz&GraphQueryService.get_revision_history>  s     " CI3344
	KU	 	-1-M-MfV`-a-a	 	 	KU -1-M-MfV`-a-a   ;%--, . 
 
 	- 	- 	- 	- 	- 	- 	- 	-# $$h%7DD       C !$

******K$ $$h%7DD       D "&,,,,,,L	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	-
 
 !
 
 
	
 
 "
 
 
 '777r   queryc          
       K   d}|                     d|                     d|                    }| j        j                            t
          j                  4 d{V 	 } |j        |fi |                     ||d|           d{V }|	                                 d{V }ddd          d{V  n# 1 d{V swxY w Y   d |D             S )zSearch Matter nodes by name (CONTAINS), returning summary results.

        Results are sorted by name ASC for stable ordering.
        Each result includes ``matter_id`` for detail navigation.
        a^  
        MATCH (m:Matter)
        WHERE m.name CONTAINS $search_term
        OPTIONAL MATCH (m)<-[:GOVERNS]-(d:Document)
                WHERE __DOC_VISIBILITY__
        WITH m, collect(DISTINCT d.doc_id)[..3] AS sample_ids
                WHERE size(sample_ids) > 0
        RETURN
          coalesce(m.matter_id, '') AS matter_id,
          m.name AS name,
          coalesce(m.description, '') AS description,
          coalesce(m.matter_type, '') AS matter_type,
          coalesce(m.status, '') AS status,
          sample_ids AS sample_doc_ids
        ORDER BY m.name ASC
        LIMIT $limit
        ry   rj   r@   N)search_termr3   c           
         g | ]E}|d          
|d         |d          pd|d         |d         |d         d |d         pg D             dFS )	r4   	matter_idrn   descriptionmatter_typer   c                    g | ]}||S r"   r"   )r=   xs     r   rs   z?GraphQueryService.search_matters.<locals>.<listcomp>.<listcomp>  s    "O"O"OQ"O1"O"O"Or   sample_doc_ids)r  r4   r  r  r   r
  r"   rq   s     r   rs   z4GraphQueryService.search_matters.<locals>.<listcomp>  s     
 
 
 y
{^&	R / /H+"O"Oq1A/B/Hb"O"O"O 
 
 
r   
r|   r$   r   rM   rN   r   rO   rP   r   rQ   )r   r  r3   r   rV   rN   rY   rZ   s           r   search_mattersz GraphQueryService.search_matters  s     "  ,,S*==
 

 ;%--, . 
 
 	* 	* 	* 	* 	* 	* 	* 	*&7; $$UU%K%KZXX       F #KKMM))))))G	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	*
 
 
 
 
 	
s    AB88
CCc          	       K   |sg S d|                      d|           d}| j        j                            t          j                  4 d{V 	 } |j        |fi |                     d|i|           d{V }|                                 d{V }ddd          d{V  n# 1 d{V swxY w Y   d |D             S )z4Return Matter nodes directly governed by a document.zBMATCH (d:Document {doc_id: $doc_id})-[:GOVERNS]->(m:Matter) WHERE rj   z` RETURN coalesce(m.matter_id, '') AS matter_id, coalesce(m.name, '') AS name ORDER BY m.name ASCr@   Nrl   c                    g | ]?}|                     d           s|                     d          ,|d          |d         pdd@S )r  r4   rn   r  r4   r   rq   s     r   rs   z:GraphQueryService.get_governed_matters.<locals>.<listcomp>  si     
 
 

 uu[!!
 &'UU6]]
{^&	R 
 
 
r   )	r$   r   rM   rN   r   rO   rP   r   rQ   )r   rl   r   rV   rN   rY   rZ   s          r   get_governed_mattersz&GraphQueryService.get_governed_matters  s       	I"55c:FF" " " 	 ;%--, . 
 
 	* 	* 	* 	* 	* 	* 	* 	*&7; $$h%7DD       F #KKMM))))))G	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	*
 

 
 
 
 	
s   AB))
B36B3r  c          	       K   | j         j                            t          j                  4 d{V 	 } |j        d|                     d|           dfi |                     d|i|           d{V }|                                 d{V }|s	 ddd          d{V  dS |d         |d         |d	         |d
         |d         d}|                    d|           d{V }d |	                                 d{V D             |d<   |                    d|           d{V }d |	                                 d{V D             |d<   |                    d|           d{V }d |	                                 d{V D             |d<   |                    d|           d{V }d |	                                 d{V D             |d<   |                    d|           d{V }d |	                                 d{V D             |d<    |j        d| 
                    d|           dfi |                     d|i|           d{V }d  |	                                 d{V D             |d!<   |                    d"|           d{V }d# |	                                 d{V D             |d$<   ddd          d{V  n# 1 d{V swxY w Y   |S )%zzReturn a complete matter knowledge card using split queries.

        Returns None if the matter_id is not found.
        r@   N)MATCH (m:Matter {matter_id: $mid}) WHERE mz RETURN m.matter_id AS matter_id, m.name AS name, coalesce(m.description, '') AS description, coalesce(m.matter_type, '') AS matter_type, coalesce(m.status, '') AS statusmidr  r4   r  r  r   )r  r4   r  r  r   pMATCH (m:Matter {matter_id: $mid})-[:HAS_CONDITION]->(c:Condition) RETURN properties(c) AS props ORDER BY c.namer  c                8    g | ]}t          |d                    S rE   r   rq   s     r   rs   z5GraphQueryService.get_matter_card.<locals>.<listcomp>  $    !M!M!Mq$qz"2"2!M!M!Mr   
conditionsyMATCH (m:Matter {matter_id: $mid})-[:REQUIRES_MATERIAL]->(mat:Material) RETURN properties(mat) AS props ORDER BY mat.namec                8    g | ]}t          |d                    S r  r   rq   s     r   rs   z5GraphQueryService.get_matter_card.<locals>.<listcomp>  s$     L L Laaj!1!1 L L Lr   	materialstMATCH (m:Matter {matter_id: $mid})-[:HAS_TIME_LIMIT]->(tl:TimeLimit) RETURN properties(tl) AS props ORDER BY tl.namec                8    g | ]}t          |d                    S r  r   rq   s     r   rs   z5GraphQueryService.get_matter_card.<locals>.<listcomp>'  $    "N"N"N4'
#3#3"N"N"Nr   time_limitszyMATCH (m:Matter {matter_id: $mid})-[:APPLIES_TO_TARGET]->(tg:TargetGroup) RETURN properties(tg) AS props ORDER BY tg.namec                8    g | ]}t          |d                    S r  r   rq   s     r   rs   z5GraphQueryService.get_matter_card.<locals>.<listcomp>/  $    $P$P$P!T!G*%5%5$P$P$Pr   target_groupszvMATCH (m:Matter {matter_id: $mid})-[:HANDLED_BY]->(org:Organization) RETURN properties(org) AS props ORDER BY org.namec                8    g | ]}t          |d                    S r  r   rq   s     r   rs   z5GraphQueryService.get_matter_card.<locals>.<listcomp>7  r  r   
handled_byzBMATCH (d:Document)-[:GOVERNS]->(m:Matter {matter_id: $mid}) WHERE rj   u   RETURN d.doc_id AS doc_id, coalesce(d.title, '') AS title, coalesce(d.doc_code, '') AS doc_code, coalesce(d.publish_date, '') AS publish_date, coalesce(d.status, '') AS status ORDER BY   CASE WHEN d.status IN ['有效', '部分失效'] THEN 0 ELSE 1 END,   d.publish_date DESC, d.titlec                ,    g | ]}t          |          S r"   r   rq   s     r   rs   z5GraphQueryService.get_matter_card.<locals>.<listcomp>F  s    %H%H%H!d1gg%H%H%Hr   governing_docszqMATCH (m:Matter {matter_id: $mid})-[:APPLIES_TO_REGION]->(r:Region) RETURN properties(r) AS props ORDER BY r.namec                8    g | ]}t          |d                    S r  r   rq   s     r   rs   z5GraphQueryService.get_matter_card.<locals>.<listcomp>N  s$    JJJAtAgJ//JJJr   regions)r   rM   rN   r   rO   rP   r0   r   r`   rQ   r$   )r   r  r   rN   r   
matter_reccards          r   get_matter_cardz!GraphQueryService.get_matter_card  s      ;%--, . 
 
 V	K V	K V	K V	K V	K V	K V	K V	K#3BB3
SS3 3 3  $$eY%7DD       C  #zz||++++++J V	K V	K V	K V	K V	K V	K V	K V	K V	K V	K V	K V	K V	K V	K$ (4"6*)-8)-8$X.$ $D  @ $        C
 "N!M#((**<L<L<L<L<L<L!M!M!MD  D $        C
 !M L;K;K;K;K;K;K L L LD  B $        C
 #O"N388::=M=M=M=M=M=M"N"N"ND  B $        C
 %Q$PSXXZZ?O?O?O?O?O?O$P$P$PD!  D $        C
 "N!M#((**<L<L<L<L<L<L!M!M!MD $199#zJJ1 1 1  $$eY%7DD       C &I%HSXXZZ7G7G7G7G7G7G%H%H%HD!"  @ $        C
 KJsxxzz9I9I9I9I9I9IJJJDOmV	K V	K V	K V	K V	K V	K V	K V	K V	K V	K V	K V	K V	K V	K V	K V	K V	K V	K V	K V	K V	K V	K V	K V	K V	K V	K V	Kp s   A K 'H'K  
K*-K*c          	     R  K   | j         j                            t          j                  4 d{V 	 } |j        d|                     d|           dfi |                     d|i|           d{V }|                                 d{V }|s	 ddd          d{V  dS |d         |d         d	}|                    d
|           d{V }d |	                                 d{V D             |d<   |                    d|           d{V }d |	                                 d{V D             |d<   |                    d|           d{V }d |	                                 d{V D             |d<   ddd          d{V  n# 1 d{V swxY w Y   |S )zReturn conditions, materials, and time limits for a matter.

        Returns None if the matter_id is not found.
        Results are sorted by name for stable ordering.
        r@   Nr  r  z0 RETURN m.matter_id AS matter_id, m.name AS namer  r  r4   r  r  r  c                8    g | ]}t          |d                    S r  r   rq   s     r   rs   z=GraphQueryService.get_matter_requirements.<locals>.<listcomp>v  s$    #O#O#OD7$4$4#O#O#Or   r  r  c                8    g | ]}t          |d                    S r  r   rq   s     r   rs   z=GraphQueryService.get_matter_requirements.<locals>.<listcomp>~  r   r   r  r  c                8    g | ]}t          |d                    S r  r   rq   s     r   rs   z=GraphQueryService.get_matter_requirements.<locals>.<listcomp>  r#  r   r!  )
r   rM   rN   r   rO   rP   r0   r   r`   rQ   )r   r  r   rN   r   r+  rY   s          r   get_matter_requirementsz)GraphQueryService.get_matter_requirementsR  s      ;%--, . 
 
 )	Q )	Q )	Q )	Q )	Q )	Q )	Q )	Q#BBB3
SSB B B  $$eY%7DD	       C  #zz||++++++J )	Q )	Q )	Q )	Q )	Q )	Q )	Q )	Q )	Q )	Q )	Q )	Q )	Q )	Q (4"6*& &F  @ $        C
 $P#OCHHJJ>N>N>N>N>N>N#O#O#OF<   D $        C
 #O"N388::=M=M=M=M=M=M"N"N"NF;  B $        C
 %Q$PSXXZZ?O?O?O?O?O?O$P$P$PF=!S)	Q )	Q )	Q )	Q )	Q )	Q )	Q )	Q )	Q )	Q )	Q )	Q )	Q )	Q )	Q )	Q )	Q )	Q )	Q )	Q )	Q )	Q )	Q )	Q )	Q )	Q )	QV s   A F'CF
F #F c          
       K   d}|                     d|                     d|                                         d|                     d|                    }| j        j                            t
          j                  4 d{V 	 } |j        |fi |                     ||d|           d{V }|	                                 d{V }ddd          d{V  n# 1 d{V swxY w Y   d	 |D             S )
zReturn documents that share the same PolicyTheme as the given document.

        Returns
        -------
        list of dicts with ``doc_id``, ``title``, ``doc_code``,
        ``publish_date``, ``theme_name``.
        u/  
        MATCH (d:Document {doc_id: $doc_id})-[:BELONGS_TO_THEME]->(t:PolicyTheme)<-[:BELONGS_TO_THEME]-(other:Document)
        WHERE __DOC_VISIBILITY_D__
          AND other.doc_id <> $doc_id
          AND __DOC_VISIBILITY_OTHER__
          AND coalesce(other.status, '') <> '已废止'
        RETURN other.doc_id AS doc_id, other.title AS title,
               other.doc_code AS doc_code, other.publish_date AS publish_date,
               t.name AS theme_name, other.status AS status
        ORDER BY other.publish_date DESC
        LIMIT $limit
        __DOC_VISIBILITY_D__rj   __DOC_VISIBILITY_OTHER__r   r@   Nr   c           
         g | ]N}|d          
|d          |d         pd|d         pd|d         pd|d         pd|                     d          pddOS )rl   rm   rn   rw   r   
theme_namer   )rl   rm   rw   r   r7  r   r   rq   s     r   rs   z>GraphQueryService.get_same_theme_documents.<locals>.<listcomp>  s     
 
 
 {
H+7)rjM/R !. 1 7Ro3%%///R 
 
 
r   r  )r   rl   r3   r   rV   rN   r   rZ   s           r   get_same_theme_documentsz*GraphQueryService.get_same_theme_documents  s      ",,S*==
 
 '&,,WjAA
 
 	 ;%--, . 
 
 	' 	' 	' 	' 	' 	' 	' 	'# $$%G%GTT       C  HHJJ&&&&&&G	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	'
 
 
 
 
 	
s   AC  
C*-C*)r   r   r   r   )NN)r   r   r   r   r   r   )r   r   r   r   r   r   )r)   r   r   r   r   r   )r.   r   r   r   r   r   )
r4   r   r2   r5   r3   r6   r   r   r   r7   )r^   r   r   r   r   r   )
re   r   rf   r   r3   r6   r   r   r   r7   )rw   r   r   r   r   r5   )r   r   r   r6   r   r   r   r   )rl   r   r   r   r   r   )
r^   r   r   r6   r   r6   r   r   r   r   )r2   r5   re   r5   r3   r6   r   r5   r   r   r   r   )rl   r   r3   r6   r   r   r   r   )rl   r   r   r6   r   r   r   r   )r  r   r3   r6   r   r   r   r7   )rl   r   r   r   r   r7   )r  r   r   r   r   r   )rl   r   r3   r6   r   r   r   r7   )__name__
__module____qualname____doc__r   staticmethodr   r$   r(   r-   r0   r]   rb   rv   r~   r   r   r   r   r   r   r   r  r  r-  r2  r8  r"   r   r   r
   r
      sY        # # # # (,'+    \ 
 
 
 \
b b b b
 
 
 

 
 
 
$ !'+D D D D D DT (,	
 
 
 
 
 
R '+8
 8
 8
 8
 8
 8
| (,	%M %M %M %M %M %MV "$'+4, 4, 4, 4, 4, 4,| (,	* * * * * *h '+P0 P0 P0 P0 P0 P0j !"&!'+_
 _
 _
 _
 _
 _
R '+d
 d
 d
 d
 d
 d
T '+K0 K0 K0 K0 K0 K0b '+N8 N8 N8 N8 N8 N8p '+6
 6
 6
 6
 6
 6
x (,	"
 "
 "
 "
 "
 "
P (,	b b b b b bP (,	6 6 6 6 6 6x '+6
 6
 6
 6
 6
 6
 6
 6
r   r
   N)r<  
__future__r   typingr   
app.configr   app.core.graph_schema_loaderr   app.infrastructure.neo4j_clientr   app.utils.loggerr   r9  loggerr
   r"   r   r   <module>rE     s     # " " " " "             3 3 3 3 3 3 7 7 7 7 7 7 ' ' ' ' ' '	H		d
 d
 d
 d
 d
 d
 d
 d
 d
 d
r   