
    i                    j   U d Z ddl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 erdd	lmZ  ee          Zd
dddgdddddgddddg ddidiZded<   dej        dddedi dddid ddid!ddid"dd#id$d%d&d'd(d)d*d+d,d-d.d/d0d1d2d3d4ddid5d%d&d'ddd6d7id8d9ddid:ddid;ddid<ddid=ddid>d?d@dAdBddidCddidDddidd#idd#iddiddiddid?dEdAd?dEdAdFidGZdedH<   dIej        dJedi dKddid ddid5d%d&d'ddd6d7id8d9ddid:ddid;ddid<ddid=ddid>d?d@dAd4ddidBddidCddidDddidLddidMddidNd%d&d'ddd6d7id8dOd%d&d'd(dd#idd#iddPdQddiddiddid%dPdQd?dEdAd?dEdAdR	idGZdedS<   dIej        ddTedUi dVddidWddidKddid ddid4ddidXddidYddidZddPdQd[dd\id]ddid^ddid_d?dEdAd`d?dEdAdad%d&d'ddd6d7id8dbd%d&d'ddd6d7id8dcddiddddii deddidfddidgddidhd%d&d'ddd6d7id8diddidjd%d&d'ddd6d7id8dkddidlddidmddidndd\idodd\idpdd\idqdd\idrd%d&d'd(dsdd#idtdd#idudd#ii dvddidwddidxddidyddidzddid{ddid|ddid}ddid~ddidd%d&d'd(ddd\idddidddidddPddddPddddPdddddiddiddPdQdd\iddid%dPdQd%dPdQddddPdddd6d7ddidd6d7dd6d7ddid%dPdQddddPdddPddi dddidd%d&d'ddd6d7id8dddidddiddd#iddd#idddidddiddd\iddd\iddd\idddidddidd%d&d'd(dd%dPdQdddiddd\idd\id%dPdQd%dPdQdddd%d&d'ddd6d7id8dd6d7ddiddiddiddidd\id%dPdQddddd6d7dd6d7dd6d7dd6d7d?ddAd%dPdQddddPdddPdddPdddd6d7dd6d7ddd7dd6d7d%dPdQdd6d7ddddPdddPdddPddddGZded<   dIej        ddTdi dddidKddidddid ddidddidddidddidddidddid5d%ddd6d7iddddiddd#iddd#idd?dEdAdd?dEdAdddidddid%dPdQdd#id?dEdAd?dEdAdidGZded<   dIej        ddTdi dddidddidKddidddidddidddidddiddd#iddd#idddidddidddidOd%ddd6d7iddddidddidddidd%dPdQddiddPdd?dEdAd?dEdAdidGZded<   dIej        ddTdi dddidddidddidKddidddidddidddiddd\idddidd%dPdQdddPddd%dPdQdddidddPdQdddidd?dEdAdd?dEdAidGZded<   dZddddidddidڜdۜigdܜZded<    G dބ dߦ          ZdS )u  OpenSearch client wrapper for gov-doc chunk indexing and hybrid search.

Migrated from Elasticsearch 8.19 to OpenSearch 2.19 for permanent free RRF support.

OpenSearch 客户端封装模块。
负责文档 chunk 的索引管理、混合检索（BM25 + kNN 向量融合）、
文档元数据 CRUD、入库追踪记录查询，以及基于 content_hash 的
内容去重与 ACL 权限重算。
    )annotations)datetimetimezone)TYPE_CHECKINGAny)AsyncOpenSearch)
async_bulk)settings)
get_logger)RedisClientanalysiscustomik_max_word	lowercase)type	tokenizerfilterik_smartgov_synonym)ik_max_indexik_smart_synonymsynonym)u8   发改委,国家发展和改革委员会,发展改革委u   工信部,工业和信息化部u   财政部,财政厅u"   住建部,住房和城乡建设部u    环保,生态环境,环境保护)r   synonyms)analyzerr   dict[str, Any]_IK_ANALYZER_SETTINGS   5sT)number_of_shardsnumber_of_replicasrefresh_intervalz	index.knn
propertieschunk_idr   keywordcontent_hashdoc_idschunk_indexintegercontenttextr   r   )r   r   search_analyzercontent_vector
knn_vectori   hnswcosinesimillucene      )mef_construction)name
space_typeengine
parameters)r   	dimensionmethodacl_idstitlei   )r   ignore_above)r   r   r+   fields
doc_numberissuing_orgdoc_typesubject_wordssignerpublish_datedatez$yyyy-MM-dd||yyyy/MM/dd||epoch_millis)r   formatknowledge_categoryknowledge_category_codedocument_scene_typez'strict_date_optional_time||epoch_millis)page_numberpage_numbersheading_hierarchyelement_type	file_type
created_at
updated_at)r
   mappingsGOV_DOC_CHUNKS_MAPPING   )r   r    doc_idservice_guide_statusguide_profile_idguide_matter_namesummaryF)r   index)	chunk_count
page_count	file_pathrN   statustask_iderrorrO   rP   GOV_DOC_META_MAPPING)r   r    r!   strictschema_version
profile_id
scene_typesource
source_url
is_currentbooleanguide_versionextractor_versionextracted_atrP   matter_namecolloquial_namesmatter_typeimplementation_code
basic_codebusiness_item_codematter_versionimplementing_subjectimplementing_subject_naturedelegated_departmentservice_objectsservice_modesonline_depthhall_requiredexpress_supportedreservation_supportedmust_onsitemust_onsite_reasonvisit_count_to_hallpromised_time_limit_dayslegal_time_limit_dayshandled_org_namesregion_nameslinked_matter_idsmaterial_names	fee_nameswindow_nameslegal_basis_namescross_region_scopecross_region_summaryguide_search_textneeds_reviewcompleteness_scorefloatconfidence_scoredocument_infoobject)r   enabledmatter_identity
basic_infocross_region_servicenested)service_scope_typeregions_summaryregions_detailregions_truncatedrw   notesraw_text)r   r"   )outcome_nameoutcome_typetemplate_namesample_nameelectronic_certificate_statusr   guide_material_idmaterial_namelinked_material_idrequirement_leveloriginal_count
copy_count
form_types
paper_specelectronic_license_linkedexempt_submissionreusable_previous_submissionmaterial_typesource_channelfill_instructionsr   applicable_conditionsblank_form_available)sample_availabledownload_hintraw_row_text)fee_nameamount_textamount_valuecurrencycharging_bodycharging_method	reducibler   z?yyyy-MM-dd||yyyy/MM/dd||strict_date_optional_time||epoch_millis)law_namedocument_no
article_noissuing_bodyeffective_datearticle_content   )window_namelocationoffice_phoneoffice_hours
navigationscope)review_inforesult_infoacceptance_infoprocess_info	materialsfeeslegal_basisrights_and_obligationsremediesconsultation_and_supervisionservice_windowsbindingsqualityraw_sections)dynamicr"   GOV_SERVICE_GUIDES_MAPPINGtrace_idr^   source_typer]   current_stagerN   original_filename)r   r>   operatorattempt_count
latest_seq
started_atfinished_atduration_mslong
error_code)error_messageartifact_countrO   rP   GOV_DOC_INGEST_TRACES_MAPPINGevent_idstage
event_typeseqattemptserviceseverityr   )artifact_refsdetails	timestamprO   GOV_DOC_INGEST_EVENTS_MAPPINGartifact_idartifact_typeretention_levelis_redactedcontent_encodingpreview_textpayload_jsonpayload_textstorage_backendstorage_pathcontent_bytes
expires_atrO    GOV_DOC_INGEST_ARTIFACTS_MAPPINGhybrid_rrf_pipelinez0RRF hybrid search pipeline for BM25 + kNN fusionznormalization-processor	techniquemin_maxrrfrank_constant<   )r   r8   )normalizationcombination)descriptionphase_results_processors_RRF_PIPELINE_BODYc                     e Zd ZdZdZdddcdZeddddd            Zeded            Z	dfdZ
edgd            ZdhdZdidZdfdZdddjd!Zdkd$Zdld'Zdmd+Zdnd-Zdod.Zdd/dpd2Zd3ddd3d4d5dqd;Zdd<drd>Zdd<dsd?Zdd<dsd@ZdAdBddddddddddddCdtdQZdudRZdAdSddddTdvdWZdwdYZdAdZddd[dxd]Zdyd_Zdzd`Z d{daZ!d{dbZ"dS )|ESClientu   Thin async wrapper around :class:`AsyncOpenSearch`.

    对 OpenSearch 异步客户端的薄封装，提供索引管理、批量写入、
    内容去重查询、ACL 重算以及入库追踪等高层接口。
    )illegal_argument_exceptionhybridpipelineNredis_clientesr   r  'RedisClient | None'returnNonec               0    || _         || _        d| _        d S )Nunknown)_es_redis_rrf_status)selfr  r  s      6D:\work\zm-rag\backend\app\infrastructure\es_client.py__init__zESClient.__init__  s"      -9 )    
'ESClient'c               &   ddl }dD ]}|j                            |d           t          j        gt          j        t          j        t          j        d}t          j        rt          j        t          j        f|d<    | t          di ||          S )u  Create an ESClient directly from application settings.

        On Windows with a system proxy configured, aiohttp may route requests
        through the proxy, causing SSL errors on plain-HTTP connections.
        We clear proxy env-vars for this process to avoid that.

        Args:
            redis_client: 可选的 RedisClient，用于 ACL 重算和删除操作的分布式锁。
                          Optional RedisClient for distributed locking on ACL paths.
        r   N)
HTTP_PROXYHTTPS_PROXY	ALL_PROXY
http_proxyhttps_proxy	all_proxy)hoststimeoutverify_certsssl_show_warn	http_authr   )
osenvironpopr
   es_hostes_request_timeoutes_verify_certses_usernamees_passwordr   )clsr  r,  _varkwargss        r  from_settingszESClient.from_settings  s      				? 	' 	'DJNN4&&&& &'2$4%5	"
 "
  	O#+#79M"NF;s?,,V,,<HHHHr  c                    | j         S )z?Expose the underlying ``AsyncOpenSearch`` for advanced queries.)r  r  s    r  rawzESClient.raw)  s     xr  c                H   K   | j                                          d {V  d S N)r  closer9  s    r  r=  zESClient.close.  s0      hnnr  boolc                    | j         dk    S )u   当前是否应尝试 hybrid 检索。仅在明确不支持时返回 False。

        Returns True unless RRF has been definitively determined as unavailable.
        When status is 'unknown', returns True to allow runtime probing.
        unavailable)r  r9  s    r  should_use_hybridzESClient.should_use_hybrid3  s     =00r  err_msg_lowerstrc                D    t          fd| j        D                       S )uS   判断错误信息是否表示 RRF 能力明确不支持（而非瞬时故障）。c              3      K   | ]}|v V  	d S r<  r+  ).0kwrB  s     r  	<genexpr>z4ESClient._is_rrf_capability_error.<locals>.<genexpr>>  s(      UU22&UUUUUUr  )any_RRF_CAPABILITY_ERROR_KEYWORDS)r  rB  s    `r  _is_rrf_capability_errorz!ESClient._is_rrf_capability_error<  s)    UUUU1TUUUUUUr  bodyr   rY   r  "tuple[dict[str, Any] | None, bool]c               \  K   | j         dk    rdS 	 | j                            ||d|i           d{V }| j         dk    r(d| _         t                              d| j         	           |d
fS # t
          $ r}t          |                                          }|                     |          rD| j         }d| _         t          	                    dt          |          || j                    Y d}~dS t          	                    dt          |          | j                    Y d}~dS d}~ww xY w)u  执行 hybrid 搜索，内置 tri-state 能力探测与自动熔断。

        Attempt a hybrid search with built-in capability probing and circuit-breaking.

        返回值:
            (response, ok) — ok=True 表示 hybrid 成功；ok=False 表示调用方应回退到 BM25。

        状态机逻辑:
            - available: 直接执行，成功返回结果；明确能力错误则熔断为 unavailable
            - unknown: 尝试探测，成功提升为 available，能力错误降级为 unavailable，
              其他错误保持 unknown（不永久锁定，下次仍可重试）
            - unavailable: 直接返回 (None, False)，不发起请求
        r@  )NFsearch_pipeline)rY   rL  paramsNr  	availablerrf_capability_confirmed)
rrf_statusTrrf_capability_fused)r_   prev_statusrS  hybrid_search_transient_errorr_   rS  )
r  r  searchloggerinfo	ExceptionrC  lowerrK  warning)r  rL  rY   r  responseexcerr_msgprevs           r  hybrid_searchzESClient.hybrid_search@  sw     ( },,;	!X__)84 -        H 9,,#. 64CSTTTT>! 	 	 	#hhnn&&G,,W55 
#'#0 *c(( $#/	     #{{{{{NN/#hh+    
 ;;;;;'	s   AA- -
D+7A4D&1/D&&D+c                <  K   t           j        t          ft           j        t          ft           j        t          ft           j        t          ft           j	        t          ft           j        t          ffD ]\  }}| j        j                            |           d{V }|sD| j        j                            ||           d{V  t"                              d|           qt"                              d|           	 | j        j                            dt*           t,                     d{V  d| _        t"                              d	t*          | j        
           dS # t0          $ r}t3          |                                          }|                     |          r7d| _        t"                              dt3          |          | j                   n5t"                              dt3          |          | j                   Y d}~dS Y d}~dS d}~ww xY w)u   创建所有必需的索引（幂等），并确保 RRF 混合检索管道已注册。

        Create all required indices if they do not already exist,
        and ensure the RRF search pipeline exists.
        rY   NrY   rL  es_index_createdes_index_existsz/_search/pipeline/)rL  rQ  search_pipeline_created)r  rS  r@  search_pipeline_not_supportedrW  'search_pipeline_create_failed_transient)r
   es_chunk_indexrR   es_meta_indexr`   es_service_guide_indexr   es_trace_indexr   es_event_indexr   es_artifact_indexr   r  indicesexistscreaterY  rZ  httpputHYBRID_RRF_PIPELINEr	  r  r[  rC  r\  rK  r]  )r  
index_namerL  rr  r_  r`  s         r  create_indiceszESClient.create_indicesy  s      $&<=#%9:,.HI$&CD$&CD')IJ!
 	A 	AJ  8+222DDDDDDDDF Ah&--JT-JJJJJJJJJ.jAAAA-Z@@@@	(-##:%8::' $           +DKK),+      
  	 	 	#hhnn&&G,,W55 #0 3c((#/      =c((#/               	s    A"E$ $
H.BHHrd  chunkslist[dict[str, Any]]
str | Nonetuple[int, list[Any]]c                  K   |pt           j        fd}t          | j         |            d           d{V \  }}|r2t                              dt          |          |dd                    ||fS )u   批量索引 chunk 文档列表，返回 (成功数, 错误列表)。

        Bulk-index a list of chunk documents.

        Each dict in *chunks* must contain at least ``chunk_id``.
        Returns ``(success_count, errors)``.
        c               3  2   K   D ]} | d         | dV  d S )Nr#   )_index_id_sourcer+  )chunkry  target_indexs    r  _actionsz,ESClient.bulk_index_chunks.<locals>._actions  sL        * ,$      r  F)raise_on_errorNes_bulk_errors   )countsample)r
   rk  r	   r  rY  r]  len)r  ry  rY   r  successerrorsr  s    `    @r  bulk_index_chunkszESClient.bulk_index_chunks  s       7 7	 	 	 	 	 	 !+48XXZZPU V V VVVVVVV 	SNN+3v;;vbqbzNRRRr  r%   dict[str, Any] | Nonec           	     :  K   | j                             t          j        dddd|iidddiigiidg dd	
           d{V }t	          |t
                    r|n|j        }|                    di                               dg           }|r|d         d         S dS )u   根据内容哈希查找已完成的文档元数据记录（用于入库去重判断）。

        Find a completed meta document by content_hash.

        Returns the first match's ``_source`` or ``None``.
        r>  musttermr%   r]   	completedrS   )rT   rZ   r[   r\   rN   querysizer  re  Nhitsr   r  )r  rX  r
   rl  
isinstancedictrL  get)r  r%   respr:  r  s        r  find_by_content_hashzESClient.find_by_content_hash  s       X__( #nl%CD#h%<=! \\\  % 
 
 
 
 
 
 
 
 !t,,;dd$)wwvr""&&vr22 	&79%%tr  r>   intc           
       K   d |                                 D             }|r|sdS d |D             }|                    d           | j                            t          j        dd|iidd                    |          |t          j        t          j
                                                  d	d
ddd           d{V }t          |t                    r|n|j        }t          |                    dd                    S )z=Patch denormalized metadata on all chunks for a content_hash.c                    i | ]
\  }}|||S r<  r+  )rF  kvs      r  
<dictcomp>z9ESClient.patch_chunks_by_content_hash.<locals>.<dictcomp>  s    GGGtq!ar  r   c                    g | ]
}d | d| S )zctx._source.z = params.fields.r+  )rF  keys     r  
<listcomp>z9ESClient.patch_chunks_by_content_hash.<locals>.<listcomp>  s)    XXXcAcAACAAXXXr  z*ctx._source.updated_at = params.updated_atr  r%   painlessz; )r>   rP   langre   rP  r  scriptTproceedrY   rL  refresh	conflictsNupdated)itemsappendr  update_by_queryr
   rk  joinr   nowr   utc	isoformatr  r  rL  r  r  )r  r%   r>   
doc_fieldsassignmentsr  r:  s          r  patch_chunks_by_content_hashz%ESClient.patch_chunks_by_content_hash  s;      HGv||~~GGG
 	: 	1XXZXXXGHHHX--) ><"@A&"ii44",&.l8<&@&@&J&J&L&L  
 
  . 
 
 
 
 
 
 
 
  !t,,;dd$)3779a(()))r  rT   r;   	list[str]c           
     d  K   |sdS | j                             t          j        dd|iidd|t	          j        t          j                                                  dddd	d
           d{V }t          |t                    r|n|j        }t          |                    dd                    S )z9Synchronize acl_ids on guide documents bound to a doc_id.r   r  rT   r  zPctx._source.acl_ids = params.acl_ids; ctx._source.updated_at = params.updated_at)r;   rP   r  r  Tr  r  Nr  )r  r  r
   rm  r   r  r   r  r  r  r  rL  r  r  )r  rT   r;   r  r:  s        r  sync_service_guide_aclzESClient.sync_service_guide_acl  s       	1X--1 8V"45&p#*&.l8<&@&@&J&J&L&L  
 
  . 
 
 
 
 
 
 
 
  !t,,;dd$)3779a(()))r  
matter_idsc           
       K   |sdS t          t                              d |D                                 }| j                            t
          j        dd|iidd|t          j        t          j
                                                  ddd	d
d           d{V }t          |t                    r|n|j        }t          |                    dd                    S )z=Backfill linked Matter IDs onto guide documents for a doc_id.r   c              3     K   | ]}||V  	d S r<  r+  rF  mids     r  rH  z6ESClient.bind_service_guide_matters.<locals>.<genexpr>)  s'      'I'IS'I'I'I'I'I'I'Ir  r  rT   r  zctx._source.linked_matter_ids = params.matter_ids; if (ctx._source.bindings == null) { ctx._source.bindings = new HashMap(); } ctx._source.bindings.matter_ids = params.matter_ids; ctx._source.updated_at = params.updated_at)r  rP   r  r  Tr  r  Nr  )listr  fromkeysr  r  r
   rm  r   r  r   r  r  r  rL  r  r  )r  rT   r  
unique_idsr  r:  s         r  bind_service_guide_mattersz#ESClient.bind_service_guide_matters   s       	1$--'I'Iz'I'I'IIIJJ
X--1 8V"45&E '1&.l8<&@&@&J&J&L&L     ' . 
 
 
 
 
 
 
 
* !t,,;dd$)3779a(()))r  c                   K   |sdS | j                             t          j        ddd|iiid           d{V }t	          |t
                    r|n|j        }t          |                    dd                    S )	z*Delete guide documents linked to a doc_id.r   r  r  rT   TrY   rL  r  Ndeleted)	r  delete_by_queryr
   rm  r  r  rL  r  r  )r  rT   r  r:  s       r  delete_service_guides_by_doc_idz(ESClient.delete_service_guides_by_doc_idB  s       	1X--1FXv$678 . 
 
 
 
 
 
 
 

 !t,,;dd$)3779a(()))r  )source_fieldsr  list[str] | Nonec               b  K   |sdS dd|iiddddiigd}|||d	<   | j                             t          j        |
           d{V }t	          |t
                    r|n|j        }|                    di                               dg           }|sdS |d                             d	i           S )z;Return the newest service guide document bound to a doc_id.Nr  rT   rS   rP   orderdesc)r  r  sortr  re  r  r   )r  rX  r
   rm  r  r  rL  r  )r  rT   r  rL  r  r:  r  s          r  find_service_guide_by_doc_idz%ESClient.find_service_guide_by_doc_idO  s        	4 x01"Wf$567 
  

 $+DOX__1 % 
 
 
 
 
 
 
 
 !t,,;dd$)wwvr""&&vr22 	4Aw{{9b)))r   r  )
query_textr&   r  rl   r  
acl_filterr  r&   rl   r  c               j  K   t          t                              d |pg D                                 }t          t                              d |pg D                                 }g }	g }
|g}|r|	                    dd|ii           |r|	                    dd|ii           |	r|                    d|	ddi           |r|
                    d	d
|ii           |r|
                    d|g dddi           |	s|
sg S ddddiig|di}|
r|
|d         d<   |	sd|d         d<   | j                            t          j        ||dddddiddddidddiigg dd           d {V }t          |t                    r|n|j	        }|
                    d!i           
                    d!g           S )"z;Search current guide profiles for research and QA evidence.c              3     K   | ]}||V  	d S r<  r+  )rF  rT   s     r  rH  z1ESClient.search_service_guides.<locals>.<genexpr>v  s(      +[+[vTZ+[F+[+[+[+[+[+[r  c              3     K   | ]}||V  	d S r<  r+  r  s     r  rH  z1ESClient.search_service_guides.<locals>.<genexpr>w  s(      .X.XsTW.Xs.X.X.X.X.X.Xr  termsrT   r   r>  rS   )shouldminimum_should_matchmatch_phraserl   multi_match)zmatter_name^4zcolloquial_names^3zimplementing_subject^2zguide_search_text^2best_fields)r  r>   r   r  rg   T)r  r   r  r  _scorer   r  _last)r  missingr   rP   r  )rc   rT   rl   rm   ro   ri   rv   rw   rs   rz   r{   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   rP   )r  r  r  r  re  Nr  )r  r  r  r  r  rX  r
   rm  r  rL  r  )r  r  r  r&   r  rl   r  unique_doc_idsunique_matter_idsanchor_shouldr  filter_clausesr  r  r:  s                  r  search_service_guideszESClient.search_service_guidesk  s      dmm+[+['-R+[+[+[[[\\ .X.Xz?OR.X.X.X!X!XYY.0')0:| 	H  'Hn+E!FGGG 	V  ',?AR+S!TUUU 	!!"/01     	JMM>M;+GHIII 	MM!!+# # # !.	$ 	$    	V 	I  <"678( !
  	:&,E&M(#  :89f45X__1)V+P+PQ'6g)N)NO!GV#45	  & & % )
 )
 )
 )
 )
 )
 )
 )
T !t,,;dd$)wwvr""&&vr222r  exclude_doc_idr  c                 K   dddd|iidddiigii}|rdd|iig|d         d<   d	}| j                             t          j        ||d
dgd           d{V }t	          |t
                    r|n|j        }|                    di                               dg           }t          |          |dz  k    r3t          
                    d|dd         t          |          |           d |D             S )zGet all completed meta docs sharing a content_hash.

        Optionally excludes a single doc_id (for deletion scenarios).
        Returns a list of ``_source`` dicts with ``doc_id`` and ``acl_ids``.
        r>  r  r  r%   r]   r  r  must_noti'  rT   r;   r  re  Nr  g?content_hash_meta_near_limit   )r%   r  limitc                    g | ]
}|d          S r  r+  )rF  hits     r  r  z:ESClient.get_all_metas_by_content_hash.<locals>.<listcomp>  s    ///3I///r  )r  rX  r
   rl  r  r  rL  r  r  rY  r]  )r  r%   r  r  _SIZE_LIMITr  r:  r  s           r  get_all_metas_by_content_hashz&ESClient.get_all_metas_by_content_hash  s_      nl;<h45!
  	L*05.2I)J(KE&M*% X__(#$i0  % 
 
 
 
 
 
 
 
 !t,,;dd$)wwvr""&&vr22 t99c)))NN.)#2#.$ii!	     0/$////r  c          	       K   |                      ||           d{V }t                      }g }|D ]F}|                    |                    dg                      |                    |d                    G| j                            t          j        dd|iiddt          |          |d	d
ddd           d{V  t                              d|dd         t          |          t          |                     dS )u   ACL 重算的内部实现（无锁），由 recompute_chunk_acl 在锁保护下调用。

        Inner implementation of ACL recomputation (no lock).
        Called by recompute_chunk_acl under lock protection.
        r  Nr;   rT   r  r%   r  zJctx._source.acl_ids = params.acl_ids; ctx._source.doc_ids = params.doc_ids)r;   r&   r  r  Tr  r  chunk_acl_recomputedr  )r%   	doc_count	acl_count)r  setupdater  r  r  r  r
   rk  sortedrY  rZ  r  )r  r%   r  metas
merged_aclr&   metas          r  _recompute_chunk_acl_innerz#ESClient._recompute_chunk_acl_inner  sy      88 9 
 
 
 
 
 
 
 
  #uu
 	+ 	+Ddhhy"55666NN4>**** h&&) ><"@A&? $**#5#5#* 
 
    - ' 
 
 	
 	
 	
 	
 	
 	
 	
0 	"%crc*'ll*oo	 	 	
 	
 	
 	
 	
r  c               (  K   | j         kd| }| j                             |d          4 d{V  |                     ||           d{V  ddd          d{V  dS # 1 d{V swxY w Y   dS |                     ||           d{V  dS )u  Recompute acl_ids on all chunks for a content_hash.

        重算指定 content_hash 对应所有 chunk 的 ACL 权限。
        使用 Redis 分布式锁（content_hash 粒度）防止并发重算导致 ACL 不一致。
        若未注入 RedisClient 则直接执行（兼容无锁场景）。

        This is the ONLY method that modifies chunks' ACL — called on ingest,
        deletion, and permission-update to guarantee consistency.
        Uses a Redis distributed lock (content_hash granularity) to prevent
        concurrent recomputation from producing inconsistent ACL.
        Falls back to no-lock execution if RedisClient is not injected.
        Nzm:acl_recompute:r  r'  r  )r  lockr  )r  r%   r  	lock_names       r  recompute_chunk_aclzESClient.recompute_chunk_acl<  s     $ ;" ;L::I{''	2'>>        55  6                                      
 11^ 2           s   A""
A,/A,rS      )page	page_sizerT   r   r^   r]   r   rN   r   	has_error
start_timeend_timer$   r  r  r   r^   r]   r   rN   r   r  bool | Noner  r  r$   c                 K   g }|r|                     dd|ii           |r|                     dd|ii           |r|                     dd|ii           |r|                     dd|ii           |r|                     dd|ii           |r|                     dd|ii           |	r|                     dd|	ii           |
r|                     d	dd
dgii           |s|r)i }|r||d<   |r||d<   |                     dd|ii           |r|                     d|g ddi           |rdd|iindi i}|dz
  |z  }| j                            t          j        |dddiig||d           d{V }t          |t                    r|n|j        }|                    di           }|                    di                               dd          }d  |                    dg           D             }||||d!S )"u   按条件分页查询入库追踪记录，支持多维度过滤和关键词搜索。

        Query ingest traces with filters and pagination.r  rT   r   r^   r]   r   rN   r   r  failedpartial_failedgtelteranger   r  )r<   r   rT   r   )r  r>   r>  r  	match_allrS   r  r  r  r  fromr  re  Nr  totalvaluer   c                    g | ]
}|d          S r  r+  rF  hs     r  r  z0ESClient.query_ingest_traces.<locals>.<listcomp>      >>>A1Y<>>>r  )r  r  r  records)	r  r  rX  r
   rn  r  r  rL  r  )r  r  r  rT   r   r^   r]   r   rN   r   r  r  r  r$   r  
time_ranger  from_idxr  r:  r  r  r  s                          r  query_ingest_traceszESClient.query_ingest_traces]  s     ( &( 	6KK(F!34555 	:KK*h!78999 	8KK)W!56777 	6KK(F!34555 	DKK/=!ABCCC 	<KK+y!9:;;; 	@KK-!=>??? 	MKK8h8H-I"JKLLL 	? 	?)+J /$.
5! -$,
5!KK<"<=>>> 	KK$RRR       -1G&$(({B6G1H	)X__)&&(9:; !	  % 
 
 
 
 
 
 
 
 !t,,;dd$)wwvr"""%%))'155>>&")=)=>>>9QXYYYr  c                   K   	 | j                             t          j        |           d{V }t	          |t
                    r|n|j        }|                    d          S # t          $ r  t          	                    dd           Y dS w xY w)zGet a single trace by ID.rY   idNr  zFailed to get ingest traceTexc_info)
r  r  r
   rn  r  r  rL  r[  rY  r]  )r  r   r  r:  s       r  get_ingest_tracezESClient.get_ingest_trace  s      	H,CQQQQQQQQD$T400?$$diC779%%% 	 	 	NN7$NGGG44	s   AA# #&BBd   )r  r  r   r]   r   r   r   c               B  K   dd|iig}|r|                     dd|ii           |r|                     dd|ii           |r|                     dd|ii           |dz
  |z  }| j                            t          j        dd|iid	d
diig||d           d{V }	t          |	t                    r|	n|	j        }
|
                    di           }|                    di                               dd          }d |                    dg           D             }|||dS )z)Query events for a trace, ordered by seq.r  r   r   r]   r   rS   r>  r  r   r  ascr  re  Nr  r  r  r   c                    g | ]
}|d          S r  r+  r  s     r  r  z0ESClient.query_ingest_events.<locals>.<listcomp>  r  r  r  r   r  )	r  r  rX  r
   ro  r  r  rL  r  )r  r   r  r  r   r]   r   r  r  r  r:  r  r  r  s                 r  query_ingest_eventszESClient.query_ingest_events  s      (.
H/E&F%G 	4KK'5!12333 	6KK(F!34555 	>KK,
!;<===1H	)X__) 64.1'5!123 !	  % 
 
 
 
 
 
 
 
 !t,,;dd$)wwvr"""%%))'155>>&")=)=>>>HIIIr  r   c                   K   	 | j                             t          j        |           d{V }t	          |t
                    r|n|j        }|                    d          S # t          $ r Y dS w xY w)zGet a single event by ID.r  Nr  )r  r  r
   ro  r  r  rL  r[  )r  r   r  r:  s       r  get_ingest_eventzESClient.get_ingest_event  s      	H,CQQQQQQQQD$T400?$$diC779%%% 	 	 	44	   AA# #
A10A12   )r  r  r   r   r   c                 K   dd|iig}|r|                     dd|ii           |r|                     dd|ii           |dz
  |z  }| j                            t          j        dd|iidd	d
iig||d           d{V }t          |t                    r|n|j        }	|	                    di           }
|
                    di                               dd          }d |
                    dg           D             }|||dS )zQuery artifacts for a trace.r  r   r   r   rS   r>  r  rO   r  r$  r  re  Nr  r  r  r   c                    g | ]
}|d          S r  r+  r  s     r  r  z3ESClient.query_ingest_artifacts.<locals>.<listcomp>  r  r  r&  )	r  r  rX  r
   rp  r  r  rL  r  )r  r   r  r  r   r   r  r  r  r:  r  r  r  s                r  query_ingest_artifactszESClient.query_ingest_artifacts  s^      (.
H/E&F%G 	DKK/=!ABCCC 	4KK'5!123331H	)X__, 64.1&%(89: !	  % 
 
 
 
 
 
 
 
 !t,,;dd$)wwvr"""%%))'155>>&")=)=>>>HIIIr  r   c                   K   	 | j                             t          j        |           d{V }t	          |t
                    r|n|j        }|                    d          S # t          $ r Y dS w xY w)zGet a single artifact by ID.r  Nr  )r  r  r
   rp  r  r  rL  r[  )r  r   r  r:  s       r  get_ingest_artifactzESClient.get_ingest_artifact  s      	0[ &        D %T400?$$diC779%%% 	 	 	44	r*  c                4  K   | j                             t          j        dddddddiiidd	d
didd	ddgiiddddiiiddd           d{V }t	          |t
                    r|n|j        }|                    di           }|                    di                               dd          }i }|                    di                               dg           D ]}|d         ||d         <   |                    di                               di                               d          }||                    dd          |                    dd          |                    d d          |                    dd          z   |rt          |          ndd!S )"u   获取入库追踪的聚合统计：今日总数、运行中、已完成、失败数和平均耗时。

        Get aggregate statistics for traces (today counts, running, failed).r   r   r  r   znow/dznow+1d/d)r  ltr  r]   
   )fieldr  r  r  avg_msavgr4  r   )r   aggs)today_total	by_statusavg_duration)r  r7  re  Naggregationsr8  r  r9  bucketsr  r:  r  runningr
  )r8  r=  r  r
  avg_duration_ms)	r  rX  r
   rn  r  r  rL  r  round)r  r  r:  r7  r8  status_countsbucketr:  s           r  get_trace_statszESClient.get_trace_stats  s      X__) !7\7R\;];],^"_$  8R!@!@"
 $h>N0O%P# %uw.F&G!	% %   % 
 
 
 
 
 
 
 
, !t,,;dd$)ww~r**hh}b1155k1EE(*hh{B//33IrBB 	? 	?F+1++>M&-((xx3377"EEII'RR '$((A66&**;::#''!44##$4a8896BIu\222
 
 	
r  c                  K   d}d}	 | j                             t          j        |           d{V }t	          |t
                    r|n|j        }|                    di           }|                    dd          }d}n# t          $ r Y nw xY wd}|r]	 | j                             t          j        |d           d{V  d}n,# t          $ r t          
                    d	d
           Y nw xY wd}d}		 |                     |           d{V }	n,# t          $ r t          
                    dd
           Y nw xY w|r|r|                     |           d{V }
|
sh| j                             t          j        ddd|iiid           d{V }t	          |t
                    r|n|j        }|                    dd          }n|                     |           d{V  ni|rg| j                             t          j        ddd|iiid           d{V }t	          |t
                    r|n|j        }|                    dd          }t                              d||r
|dd         nd||	|           |t#          |          |	dS )u  文档删除的内部实现（无锁），由 delete_document 在锁保护下调用。

        Inner implementation of document deletion (no lock).
        Execution order: delete meta first -> query remaining metas -> decide
        whether to delete chunks or recompute ACL.
        Fr  r  Nr  r%   T)rY   r  r  zFailed to delete meta documentr  r   z'Failed to delete service guide documentr  r  r  r  r&   es_document_deletedr  )rT   r%   ry  guidesr  )deleted_chunksdeleted_metadeleted_guides)r  r  r
   rl  r  r  rL  r[  deleterY  r]  r  r  r  rk  r  rZ  r  )r  rT   meta_loadedr%   	meta_respr:  re   rG  rF  rH  remaining_metasr  raw_resps                r  _delete_document_innerzESClient._delete_document_inner4  s      		"hll1GFlSSSSSSSSI))T::N))	CWWY++F!::nb99LKK 	 	 	 D	  
	P	Phoo"0  &         
  $ P P P?$OOOOOP 	U#'#G#G#O#OOOOOOONN 	U 	U 	UNNDtNTTTTT	U  	8< 	8 %)$F$F|$T$TTTTTTTO" D "X55"1!F^\,J#KL  6        
 $.dD#9#9H44ty!)i!;!; 55lCCCCCCCCCC 		8 11-F(;<= 2        D
  *$55Dtt49H%\\)Q77N!.:Bcrc**!! 	 	
 	
 	
 ---,
 
 	
s6   A8B 
BB/C &C/.C/7D &D<;D<c                j  K   | j         	 | j                            t          j        |dg           d{V }t          |t                    r|n|j        }|                    di                               dd          }n# t          $ r d}Y nw xY w|rhd| }| j         	                    |d          4 d{V  | 
                    |           d{V cddd          d{V  S # 1 d{V swxY w Y   dS | 
                    |           d{V S | 
                    |           d{V S )	u  内容感知的文档删除，使用分布式锁防止并发删除/ACL 重算竞态。

        Delete a document, handling shared content correctly.
        Uses a Redis distributed lock (content_hash granularity) to prevent
        race conditions between concurrent delete and ACL recomputation.

        执行顺序：先删 meta → 查询剩余 meta → 决定删 chunks 还是 recompute ACL，
        整个流程在 content_hash 锁内完成。若无 RedisClient 则直接执行（兼容无锁场景）。
        Nr%   )rY   r  r  r  r  r  r  r  )r  r  r  r
   rl  r  r  rL  r[  r  rN  )r  rT   rK  r:  r%   r   s         r  delete_documentzESClient.delete_document  sK      ;"	""&(,,"0+, #/ # #      	
 $.i#>#>RiiIN"wwy"5599."MM " " "!"  A ?>>	;++Ir+BB E E E E E E E E!%!<!<V!D!DDDDDDDE E E E E E E E E E E E E E E E E E E E E E E E E E E E E E "88@@@@@@@@@44V<<<<<<<<<s$   A6B BB>C,,
C69C6)r  r   r  r  r  r  )r  r  r  r  )r  r   )r  r  )r  r>  )rB  rC  r  r>  )rL  r   rY   rC  r  rC  r  rM  )ry  rz  rY   r{  r  r|  )r%   rC  r  r  )r%   rC  r>   r   r  r  )rT   rC  r;   r  r  r  )rT   rC  r  r  r  r  )rT   rC  r  r  )rT   rC  r  r  r  r  )r  r   r  rC  r&   r  r  r  rl   rC  r  r  r  rz  )r%   rC  r  r{  r  rz  )r%   rC  r  r{  r  r  )r  r  r  r  rT   r{  r   r{  r^   r{  r]   r{  r   r{  rN   r{  r   r{  r  r  r  r{  r  r{  r$   r{  r  r   )r   rC  r  r  )r   rC  r  r  r  r  r   r{  r]   r{  r   r{  r  r   )r   rC  r  r  )r   rC  r  r  r  r  r   r{  r   r{  r  r   )r   rC  r  r  )r  r   )rT   rC  r  r   )#__name__
__module____qualname____doc__rJ  r  classmethodr7  propertyr:  r=  rA  rK  rb  rx  r  r  r  r  r  r  r  r  r  r  r  r  r!  r'  r)  r.  r0  rB  rN  rP  r+  r  r  r  r    s         &Z" .2	* * * * * *  .2%I %I %I %I %I [%IN    X   
 1 1 1 X1V V V V5 5 5 5r2 2 2 2t !	     <   8* * * *>* * * *0 *  *  *  *D* * * *" +/	* * * * * *@ $('+i3 i3 i3 i3 i3 i3^ &*	,0 ,0 ,0 ,0 ,0 ,0d &*	6
 6
 6
 6
 6
 6
x &*	     H !#"!$( $"&!%!%#"DZ DZ DZ DZ DZ DZL	 	 	 	  !!%!J !J !J !J !J !JF    $( J J J J J J@	 	 	 	+
 +
 +
 +
^W
 W
 W
 W
r!= != != != != !=r  r  N) rT  
__future__r   r   r   typingr   r   opensearchpyr   opensearchpy.helpersr	   
app.configr
   app.utils.loggerr   app.infrastructure.redis_clientr   rQ  rY  r   __annotations__es_number_of_replicasrR   r`   r   r   r   r   rv  r	  r  r+  r  r  <module>r`     s     # " " " " " ' ' ' ' ' ' ' ' % % % % % % % % ( ( ( ( ( ( + + + + + +       ' ' ' ' ' ' <;;;;;;	H		
  !*&-  !'&6! !
 
 !  
 

 )     D &< 	 
   	 B
+B
 VY/B
 	*	B

 FI.B
 *#5 B
 $!""/&+.# #	  B
4 	*5B
8 *#5 )(+   	
 
9B
N 69-OB
P FI.QB
R +SB
T fi0UB
V vy)WB
X @ YB
b !69"5cB
d &	':eB
f "FI#6gB
j #I.#Y/"()!4#Y/ ),C 
 C }B
 B
 B
DM* M*  M M M Mb &<    	 2
vy)2
VY/2
 *#5$y#&N&NO	 2
 69-2
 FI.2
 +2
 fi02
 vy)2
 @ 2
$ 	*%2
& !69"5'2
( &	':)2
* "FI#6+2
, #VY$7-2
.  3/2
0  *#5$y#&N&NO	" "12
< *#5 =2
F #I.!9-"+e<< ),y)	*$u55C 
 C ]2
 2
 2
4;( ;(  ; ; ; ;~ &<    	 ~
vy1~
69-~
 vy)~
 VY/	~

 	*~
 69-~
 vy)~
 9u==~
 69-~
 fi0~
  &)!4~
 V7`aa~
 65^__~
 *#5$y#&N&NO	 ~
( *#5$y#&N&NO	! !)~
4 FI.5~
6 "FI#67~
 ~
8 69-9~
: !69"5;~
< vy1=~
> #*#5$y#&N&NO	% %?~
J *FI+>K~
L #*#5$y#&N&NO	% %M~
X 	2Y~
Z fi0[~
\ VY/]~
^ fi0_~
`  &)!4a~
b $fi%8c~
d FI.e~
f !6~bt"u"ug~
h "FI#6i~
j '(;k~
l $fi%8m~
 ~
 ~
n  &)!4o~
p VY/q~
r  &)!4s~
t vy1u~
v &),w~
x VY/y~
z  &)!4{~
| !69"5}~
~ #VY$7~
@  &nas!t!tA~
B VY/C~
D !67"3E~
F  1G~
H h5AAI~
J UCCK~
L 8>>M~
N # +19*=(.	':/85&I&I*0))<&,i%8&,u==)/% @ @ % %O~
 ~
f %-??$,V_qtMuMu  IO  QZ  H[  w@  RU  nV  nV  py  KN  gO  gO  sy  {D  rE  Y_  jo  Pp  Pp  =q  =q  r  r(0UCC%-%@@ '&))<# &$2+=#,yRU.V.V"W	& & )69*= (&))< %vy&9 !69"5 !69"5 !69"5 0&)1D (&))<  3VY4G!" $fi%8#$ %vy&9%& (&ni{)|)|'( fu==)* ,fi-@+, +VY,?-. *0(;.4u%E%E-3e$D$D3   < &6_m  CU  bk  v  QT  mU  mU  aV  CW  CW  qz  LO  hP  hP  ci  kr  bs  BH  JS  AT  hn  py  gz  PV  Xa  Ob  rx  zC  qD  X^  in  Oo  Oo  6p  6p  q  q$,R[mpIqIq  LU  gj  Ck  Ck  DM  _b  {c  {c  ~G  Y\  u]  u]  z@  LM  qN  qN  lr  }B  cC  cC  =D  =D  E  E/7E&J&J!)e<<5=%,P,P(0YbtwPxPx  PY  kn  Go  Go  JS  eh  Ai  Ai  DM  _b  {c  {c  |B  MR  sS  sS  gp  BE  ^F  ^F  AG  AG   H   H!)e<< (U;;%-%@@{~
 ~
 ~
A AI. I.  I I I I^ &<   	 %
+%
vy)%
 	*%
 VY/	%

 FI.%
 vy)%
 fi0%
 &),%
  &)!4%
 $y#&N&NO %
 +%
 fi0%
  69-!%
" C #%
* C +%
2 FF+3%
4 69-5%
6 '-u==%y1C 
 C C%
 %
 %
'.1 .1  . . . .d &<   	 
+
+
 vy)
 	*	

 fi(
 69-
 vy)
 FI&
 	*
 	*
 +
 &),
 $y#&N&NO 
" FF+#
$ +%
& 69-'
( fu==)
* %i0 (U;;C 
 C 7
 
 
!(1 (1  ( ( ( (X &<   	 
FI.
+
 +
 vy)	

 fi(
 fi0
 	2
 FI.
  3
 Ve<<
 X%@@
 Ve<<
 	2
 Y??
 ff-
  C !
( C )
!4 !4   ! ! ! !J ,  F &"-y!9!&#2B"7   ( (	

!& &     &D= D= D= D= D= D= D= D= D= D=r  