o
    ՂiQh                  $   @   s  d Z ddlmZ ddlmZ ddlmZmZ ddlm	Z	m
Z
mZmZmZ ddlmZ ddlmZ ddlmZmZ dd	lmZ dd
lmZ ddlmZ ddlmZmZ ddlmZm Z  ddl!m"Z" ddl#m$Z$ ddl%m&Z& ddl'm(Z(m)Z)m*Z* e	 Z+dzdede,de,de-fddZ.G dd deZ/dede-de-fddZ0dede1fdd Z2dede3fd!d"Z4e+5d#e
efdefd$d%Z6e+5d&d'd'ed'd(d)ed'd*d)ed'd+d)ed'd,d)ed'd-d)ed'd.d)ed'd/d)ed'd0d)d'd'd'd'ded1d2d3e
efd4ee, d5ee1 d6ee7 d7ee d8ee d9ee7 d:ee1 d;ee7 d<ee7 d=ee1 d>ee, d?ee7 d@ee7 dAee7 dBe,dCe,def"dDdEZ8e+5dFedGdHdIdJd'ed'ed'ed'ed'ded1d2d3e
ef	dKe7d4ee, d7ee d8ee d<ee7 d=ee1 dBe,dCe,defdLdMZ9e+:dNe
efdefdOdPZ;e+5dQe
efde,defdRdSZ<e+5dTe
efde,dUe,defdVdWZ=e+5dXe
efde,defdYdZZ>e+5d[e
efde,d\e,defd]d^Z?e+:d_e
efde,defd`daZ@e+:dbe
efde,defdcddZAe+:deedGe
efdfe/defdgdhZBG didj djeZCe+Ddke
efde,dleCdefdmdnZEe+5doe
efde,defdpdqZFe+5dre
efde,dse,defdtduZGe+5dve
efde,dwe,defdxdyZHd'S ){u   文档管理 API    date)Path)ListOptional)	APIRouterDependsHTTPExceptionBodyQuery)FileResponse)	BaseModel)funcor_)Session)settings)get_db)TaxDocumentTaxDocumentVersion)can_apply_legal_relationis_regulation_like)CategoryProcessor)KnowledgeBaseClient)is_same_legal_reference)import_to_knowledge_base_taskreprocess_from_local_taskstrip_ocr_content_task2   dbdoc_id	max_nodesreturnc                    s  ddl m} | ttjk }|rt|sg S i }g}|rt||k r|	d}||v r2q | ttj|k }|rEt|sFq |||< |j
r|j
D ].}	t|	tr\|	dnd}
|
rd|
|v reqP| ttj|
k }|r~t||r~||
 qP| |d| d }|D ]$}|d }||v rq| ttj|k }|rt||r|| q|rt||k s(t|dkrg S ddlm  t|  fd	d
dd}fdd|D S )u  双向 BFS 找出当前文档所属的修订/废止家族，按成文日期排序后返回。

    - 正向：顺着 supersedes[].doc_id 追溯被废止/修订的旧文档
    - 反向：通过 JSONB @> 查询找到废止/修订了本文档的新文档
    r   )textr   Nz>SELECT id FROM tax_documents WHERE supersedes @> '[{"doc_id": z}]'   r   c                    s   | j p jS )N)
issue_datemin)d)date_min B/lsinfo/ai/hellotax_ai/data_center/backend/app/api/v1/documents.py<lambda>T   s    z!_build_timeline.<locals>.<lambda>T)keyreversec              	      s<   g | ]}|j |j|j|j|jr|j nd |j  kdqS )N)idtitle
doc_number
doc_statusr$   
is_current)r-   r.   r/   r0   r$   	isoformat.0r&   )r   r(   r)   
<listcomp>W   s    	z#_build_timeline.<locals>.<listcomp>)
sqlalchemyr"   queryr   filterr-   firstr   lenpop
supersedes
isinstancedictgetr   appendexecutefetchalldatetimer   sortedvalues)r   r   r    sa_textrootfamilyqueue
current_idcurrentitemanc_idancestorrowsrow	source_idsourcesorted_docsr(   )r'   r   r)   _build_timeline   sh   



#

	rT   c                   @   s   e Zd ZU ee ed< dS )BatchImportRequestdoc_idsN)__name__
__module____qualname__r   int__annotations__r(   r(   r(   r)   rU   d   s   
 rU   refsc                 C   s   g }|D ]O}| ds4| dr4| tjtjtj|d k }|r3i |d|ji}|jdkr3qn| drN| tjtj|d k }|dkrNq|	| q|S )Nr   
source_urlobsolete)
r?   r7   r   r-   r0   r8   r]   r9   scalarr@   )r   r\   resultrrP   statusr(   r(   r)   _resolve_referencesh   s&   

 rc   docc                 C   s    | j dkrdS | jpd}d|v S )uQ   判断文档正文是否含图片（检查 content_markdown 中的图片语法）qaF z![)doc_typecontent_markdown)rd   mdr(   r(   r)   _has_images{   s   

rj   c                 C   s   i d| j d| jd| jd| jd| jd| jd| jr"| j ndd	| jr-| j ndd
| j	d| j
d| jdt| jdt| d| jd| jd| jrW| j ndd| jrc| j S dS )u*   文档列表项序列化（不含正文）r-   r]   category_idr/   r.   issuing_authorityr$   Neffective_dateprocessing_statusr0   is_importedhas_attachments
has_imagesinterpretation_formrg   
created_at
updated_at)r-   r]   rk   r/   r.   rl   r$   r2   rm   rn   r0   ro   boolattachmentsrj   rr   rg   rs   rt   )rd   r(   r(   r)   _doc_summary   sJ   	

rw   z/categoriesc                    s`   t  }g }| D ]!}| ttj|d k }||d |d ||d d q
d|iS )u3   获取文档分类列表（含各类实际条数）r-   nametype)r-   rx   country   
categories)r   get_all_categoriesr7   r   r8   rk   rz   r@   )r   category_processorr{   c
real_countr(   r(   r)   get_categories   s   	r   rf   Nu   文号精确或模糊查询)descriptionu   成文日期起始（含）u   成文日期截止（含）u   发文机关关键词u   是否有附件u:   文件状态 effective/obsolete/partially_obsolete/amendedu/   附件类型 pdf/doc/docx/xls/xlsx/wps/ppt/pptxu   正文是否含图片      )lerk   ro   r/   issue_date_fromissue_date_torl   rp   r0   attachment_typerq   rQ   rg   region_coderr   skiplimitc                    s  | t}| dur|tj| k}|dur|tj|k}|r-|tjd| d}|r7|tj|k}|rA|tj|k}|rP|tjd| d}|du r^|tj	
d}n|du rq|ttj	dtj	g k}|r{|tj|k}|
dur|tj|
k}|dur|tj|k}|dur|tj|k}|dur|tj|k}| } sdur|tj  tj  } rЇ fdd|D }dur݇fdd|D }t|}||||  }n|tj  tj || }|}|||dd |D d	S )
u  
    获取文档列表（支持多维度过滤）

    - **category_id**: 分类ID（1-8）
    - **doc_number**: 文号模糊搜索（如 "财税[2023]"）
    - **issue_date_from / issue_date_to**: 成文日期范围
    - **issuing_authority**: 发文机关关键词
    - **has_attachments**: true=仅含附件，false=无附件
    - **is_imported**: 是否已导入知识库
    N%TFc                    .   g | ]}|j rt fd d|j D r|qS )c                 3       | ]
}| d  kV  qdS ry   Nr?   r4   ar   r(   r)   	<genexpr>       
z,list_documents.<locals>.<listcomp>.<genexpr>rv   anyr3   r   r(   r)   r5          z"list_documents.<locals>.<listcomp>c                       g | ]
}t | kr|qS r(   rj   r3   rq   r(   r)   r5          c                 S      g | ]}t |qS r(   rw   r4   rd   r(   r(   r)   r5         )totalr   r   items)r7   r   r8   rk   ro   r/   iliker$   rl   rv   isnotr   is_r0   rQ   rg   r   rr   rz   order_bydesc	nullslastr-   allr:   offsetr   )rk   ro   r/   r   r   rl   rp   r0   r   rq   rQ   rg   r   rr   r   r   r   r7   r   docsfiltered_totalr(   r   rq   r)   list_documents   sf   
  r   z/search.r#   u   全文关键词)
min_lengthr   qc	              
      s  ddl }	t|	d| }
|
r|ttj|  d}n$|tttj	d|  dtjd|  dtj
d|  d}|durO|tj|k}|rY|tj|k}|rc|tj|k}| } smdur|tj  tj  } r fdd|D }durfdd|D }t|}||||  }n|tj  tj || }|}||||
dd |D d	S )
u   
    关键词搜索（标题、文号、发文机关）

    - **q**: 搜索关键词（自动识别文号格式进行精确匹配）
    r   Nu\   [\u4e00-\u9fa5]{2,10}[〔\[\（(]\d{4}[〕\]\）)]\d+号|(?:国务院令|主席令)第\d+号r   c                    r   )c                 3   r   r   r   r   r   r(   r)   r   >  r   z.search_documents.<locals>.<listcomp>.<genexpr>r   r3   r   r(   r)   r5   >  r   z$search_documents.<locals>.<listcomp>c                    r   r(   r   r3   r   r(   r)   r5   B  r   c                 S   r   r(   r   r   r(   r(   r)   r5   T  r   )r   r   r   is_doc_number_searchr   )reru   searchr7   r   r8   r/   r   r   r.   rl   rk   r$   rz   r   r   r   r-   r   r:   r   r   )r   rk   r   r   r   rq   r   r   r   r   is_doc_numberr7   r   r   r   r(   r   r)   search_documents  sZ   

 r   z/strip-ocr-contentc                    s   t  }d|jdS )uG   清除所有文档 content_markdown 中残留的 OCR 图片内容段落u   OCR 内容清理任务已启动)messagecelery_task_id)r   delayr-   )r   taskr(   r(   r)   strip_ocr_contentX  s   r   z	/{doc_id}c           
         s  | ttj| k }|stdddd}d}|jr>| tjtjtj|jk }|r>|j}t	|j
|j|j|j}i d|jd|jd|jd	|jd
|jd|jd|jr`|j ndd|jrk|j ndd|jd|jd|jd|jp~g dt|jdt|d|jd|jd|ji d|jd|jd|jd|jd|jd|j d|j!d|j"r|j" ndd|j#r|j# ndd |j$d!|jd"|d#|rdn|j
d$|rdn|jd%|rdn|j%d&dd'|j&|j'|j(pg t)||j*pg t+|| d(}|jr^|jr^z@t, }|-|jI dH }|rJ|.d)rM|.d*rP|d* }	|	.d |d < |	.d!|d!< |	.d&|d&< W |S W |S W |S W |S  t/y]   Y |S w |S )+u  获取文档详情（含正文、附件、元数据）

    若文档已导入知识库，将透传 doc_status / superseded_by_doc_id / version_number 字段
    （从 base_platform 异步回查，base_platform 不可达时忽略，详情页仍正常返回）。
         文档不存在status_codedetailNFr-   r]   rk   r/   r.   rl   r$   rm   rh   content_html	file_pathrv   rp   rq   rn   content_hashro   knowledge_doc_idrg   qa_question	qa_answerrQ   r   rr   rs   rt   r0   superseded_by_doc_idsuperseded_by_titlesuperseded_by_doc_numbersuperseded_by_ext_titlesuperseded_by_source_urlversion_numberinline_images)inline_videosr<   
referencestimelinesuccessdata)0r7   r   r8   r-   r9   r	   r   r.   r/   r   r   r   r]   rk   rl   r$   r2   rm   rh   r   r   rv   ru   rj   rn   r   ro   r   rg   r   r   rQ   r   rr   rs   rt   r0   r   r   r   r<   rc   r   rT   r   get_document_statusr?   	Exception)
r   r   rd   r   suppress_external_superseded_by
supersederr`   clientkb_respkb_datar(   r(   r)   get_document_detail_  s   

	


 !"#$-			r   z*/{doc_id}/attachments/{att_index}/download	att_indexc           	         s   | ttj| k }|stddd|jpg }|dk s%|t|kr+tddd|| }|dd}|s=tdddt	t
j| }z| }W n tyU   d	}Y nw |s^tdddtt||jd
dS )u   下载本地附件文件r   r   r   r   u   附件不存在pathrf   u'   本地文件不存在，请重新下载Fapplication/octet-stream)r   filename
media_type)r7   r   r8   r-   r9   r	   rv   r:   r?   r   r   project_rootis_fileOSErrorr   strrx   )	r   r   r   rd   rv   att
local_pathabs_pathr   r(   r(   r)   download_attachment  s0   
r   z/{doc_id}/attachmentsc                    s<   | ttj| k }|stddd| |jpg dS )u   获取文档附件列表r   r   r   )r   rv   )r7   r   r8   r-   r9   r	   rv   )r   r   rd   r(   r(   r)   get_document_attachments  s
   r   z#/{doc_id}/videos/{vid_index}/stream	vid_indexc           	         s   | ttj| k }|stddd|jpg }|t|kr'tddd|| dd}|s7tdddt	|
 rAt	|nt	tj| }| sRtddd|j }|d	kr]d
n|dkrcdnd}tt||dS )u   流式返回内联视频文件r   r   r   u   视频不存在r   rf   u   视频文件路径为空u   视频文件不存在z.mp4z	video/mp4z.webmz
video/webmr   r   r   )r7   r   r8   r-   r9   r	   r   r:   r?   r   is_absoluter   r   r   suffixlowerr   r   )	r   r   r   rd   videosr   r   r   r   r(   r(   r)   stream_inline_video  s    
"
r   z/{doc_id}/reprocessc                    sT   | ttj| k }|stdddt| }d| |j|j	t
|j|jdS )uy   重新处理单个文档。优先从本地存储的原始 HTML 重处理，content_html 为空时回退到网络抓取。r   r   r   u   文档重处理任务已启动)r   r   r]   rk   
from_localr   )r7   r   r8   r-   r9   r	   r   r   r]   rk   ru   r   r   r   rd   r   r(   r(   r)   reprocess_document  s   
r   z/{doc_id}/importc                    sX   | ttj| k }|stddd|jr d| |jdS t	| }d| |jdS )u   导入单个文档到知识库r   r   r   u   文档已导入)r   r   r   u   文档导入任务已启动)r   r   r   )
r7   r   r8   r-   r9   r	   ro   r   r   r   r   r(   r(   r)   import_document  s   
r   z/batch-importrequestc                    s   | tjtj| jtjd }dd |D }g }|D ]}t	
|}||j q!dt| dt| jt|t| jt| |dS )u6   批量导入文档到知识库（跳过已导入的）Fc                 S   s   g | ]}|d  qS )r   r(   )r4   rP   r(   r(   r)   r5   B  r   z*batch_import_documents.<locals>.<listcomp>u"   批量导入任务已启动，共 u
    个文档)r   	requestedqueuedskipped_already_importedcelery_task_ids)r7   r   r-   r8   in_rV   ro   r   r   r   r   r@   r:   )r   r   
valid_docsvalid_doc_idsr   r   r   r(   r(   r)   batch_import_documents4  s&   


r   c                   @   s   e Zd ZU eed< dS )UpdateContentRequestrh   N)rW   rX   rY   r   r[   r(   r(   r(   r)   r   T  s   
 r   z/{doc_id}/contentbodyc                    s   | ttj| k }|stddd| ttj	tj
| k p(d}|d }t| ||jd}|| |j|_|jrGd|_d|_|  d	| |d
S )uW   保存文档编辑内容，自动记录版本历史；若已导入则重置为未导入r   r   r   r   r#   )r   r   rh   FNu   保存成功)r   r   r   )r7   r   r8   r-   r9   r	   r   maxr   r   r   r_   rh   addro   r   commit)r   r   r   rd   last_vernew_versionversionr(   r(   r)   update_document_contentX  s.   
r  z/{doc_id}/versionsc                    sf   | ttj| k }|stddd| ttj| ktj	
  }| dd |D dS )u   获取文档版本历史列表r   r   r   c                 S   s,   g | ]}|j |j|jr|j nd dqS )N)r-   r   rs   )r-   r   rs   r2   )r4   vr(   r(   r)   r5     s    z*list_document_versions.<locals>.<listcomp>)r   versions)r7   r   r8   r-   r9   r	   r   r   r   r   r   r   )r   r   rd   r  r(   r(   r)   list_document_versions~  s   r  z#/{doc_id}/versions/{version_number}r   c                    sZ   | ttj| ktj|k }|stddd| |j|j|jr)|j	 dS ddS )u=   获取指定版本的 Markdown 内容（用于 diff 对比）r   u   版本不存在r   N)r   r   rh   rs   )
r7   r   r8   r   r   r9   r	   rh   rs   r2   )r   r   r   verr(   r(   r)   get_document_version  s"   r  z/{doc_id}/images/{img_index}	img_indexc           	         s   | ttj| k }|stddd|jpg }|dk s%|t|kr+tddd|| dd}|s;tdddt	t
j| }| sLtdd	d|j }d
d
dddd|dd
}tt||dS )u   返回文档内联图片文件r   r   r   r   u   图片不存在r   rf   u   图片路径为空u   图片文件不存在z
image/jpegz	image/pngz	image/gifz
image/webp)jpgjpegpnggifwebp.r   )r7   r   r8   r-   r9   r	   r   r:   r?   r   r   r   r   r   r   lstripr   r   )	r   r	  r   rd   imagesr   r   r   r   r(   r(   r)   get_document_image  s    

r  )r   )I__doc__rC   r   pathlibr   typingr   r   fastapir   r   r	   r
   r   fastapi.responsesr   pydanticr   r6   r   r   sqlalchemy.ormr   
app.configr   app.databaser   app.models.tax_datar   r   0app.services.tax_data_processor.relation_builderr   r   2app.services.tax_data_processor.category_processorr   5app.services.tax_data_processor.knowledge_base_clientr   1app.services.tax_data_processor.relation_identityr   app.tasks.processor_tasksr   r   r   routerrZ   listrT   rU   rc   ru   rj   r>   rw   r?   r   r   r   r   postr   r   r   r   r   r   r   r   r   putr  r  r  r  r(   r(   r(   r)   <module>   s   H








	
]
	LZ"%