o
    BiE                      @  s   d Z ddlmZ ddlZddlmZmZmZ ddlm	Z	m
Z
mZmZmZ eG dd dZeG dd	 d	ZG d
d dZG dd dZdS )uH  父子索引存储模块

实现父子检索策略（Parent-Child Retrieval）：
- 子片（ChildChunk）用于向量检索（粒度细、语义精准）
- 父片（ParentChunk）用于上下文扩展（粒度粗、上下文完整）

当前实现为内存存储，接口设计支持后续替换为 Milvus/PostgreSQL 后端。
    )annotationsN)	dataclassfieldasdict)DictListOptionalTupleAnyc                   @  sz   e Zd ZU dZded< ded< ded< ded< eedZded	< d
Zded< ee	dZ
ded< dddZedddZd
S )ParentChunkuM   父片：包含完整上下文的较大文本块，通常 1500-3000 字符。str	parent_iddocument_idtextarticle_rangedefault_factory	List[str]	child_idsNzOptional[str]chapterDict[str, Any]metadatareturnc                 C     t | S Nr   self r   Q/lsinfo/ai/hellotax_ai/base_platform/app/services/knowledge/parent_child_store.pyto_dict      zParentChunk.to_dictdata'ParentChunk'c                 C     | di |S Nr   r   clsr"   r   r   r   	from_dict       zParentChunk.from_dictr   r   )r"   r   r   r#   )__name__
__module____qualname____doc____annotations__r   listr   r   dictr   r    classmethodr(   r   r   r   r   r      s   
 
r   c                   @  sp   e Zd ZU dZded< ded< ded< ded< ded< dZd	ed
< eedZded< dddZ	e
dddZdS )
ChildChunkuK   子片：用于向量检索的细粒度文本块，通常 200-800 字符。r   child_idr   r   r   levelNzOptional[List[float]]	embeddingr   r   r   r   c                 C  r   r   r   r   r   r   r   r    1   r!   zChildChunk.to_dictr"   'ChildChunk'c                 C  r$   r%   r   r&   r   r   r   r(   4   r)   zChildChunk.from_dictr*   )r"   r   r   r7   )r+   r,   r-   r.   r/   r6   r   r1   r   r    r2   r(   r   r   r   r   r3   %   s   
 
r3   c                   @  sd   e Zd ZdZd$ddZd%ddZd&ddZd'ddZd(ddZd)ddZ	d*ddZ
ed+d!d"Zd#S ),ParentChildStoreu   父子块存储。

    当前为内存实现，用于开发和测试。接口设计与存储后端无关，
    后续可通过继承或组合替换为 Milvus/PostgreSQL 实现。
    r   Nonec                 C  s   i | _ i | _i | _d S r   )_parents_child_to_parent_parent_to_childrenr   r   r   r   __init__@   s   
zParentChildStore.__init__parentr   childrenList[ChildChunk]boolc                 C  sd   z'dd |D }||_ || j|j< t|| j|j< |D ]	}|j| j|j< qW dS  ty1   Y dS w )u   存储父片及其所有子片，同时维护双向索引。

        Args:
            parent: 父片对象。
            children: 属于该父片的子片列表。

        Returns:
            成功返回 True，否则返回 False。
        c                 S     g | ]}|j qS r   r4   .0cr   r   r   
<listcomp>S       z1ParentChildStore.store_chunks.<locals>.<listcomp>TF)r   r:   r   r0   r<   r;   r4   	Exception)r   r>   r?   r   childr   r   r   store_chunksG   s   
zParentChildStore.store_chunksr   r   Optional[ParentChunk]c                 C  s   | j |S )u   根据父片 ID 获取父片。)r:   getr   r   r   r   r   
get_parent`   s   zParentChildStore.get_parentc                 C  s   | j |g S )u!   获取某父片下所有子片。)r<   rM   rN   r   r   r   get_childrend   r)   zParentChildStore.get_childrenr4   c                 C  s$   | j |}|du rdS | j|S )u   根据子片 ID 反查父片。N)r;   rM   r:   )r   r4   r   r   r   r   get_parent_by_childh   s   z$ParentChildStore.get_parent_by_childr   r   List[ParentChunk]c                 C  sJ   t  }g }|D ]}| |}|dur"|j|vr"||j || q|S )u   给定子片 ID 列表，返回去重的父片列表（检索后上下文扩展）。

        Args:
            child_ids: 向量检索命中的子片 ID 列表。

        Returns:
            去重、保序的父片列表。
        N)setrQ   r   addappend)r   r   seenparentsr4   r>   r   r   r   expand_to_parent_contexto   s   	

z)ParentChildStore.expand_to_parent_contextr   c                 C  s*   dd | j  D dd | j D dS )u!   序列化整个存储为字典。c                 S  s   i | ]	\}}||  qS r   r    )rE   pidpr   r   r   
<dictcomp>   s    z,ParentChildStore.to_dict.<locals>.<dictcomp>c                 S  s    i | ]\}}|d d |D qS )c                 S  s   g | ]}|  qS r   rY   rD   r   r   r   rG      s    z7ParentChildStore.to_dict.<locals>.<dictcomp>.<listcomp>r   )rE   rZ   r?   r   r   r   r\      s    )rW   parent_to_children)r:   itemsr<   r   r   r   r   r       s
   zParentChildStore.to_dictr"   'ParentChildStore'c                 C  sX   |  }| di  D ]\}}t|}dd | di  |g D }||| q|S )u   从字典反序列化存储。rW   c                 S  s   g | ]}t |qS r   )r3   r(   rD   r   r   r   rG      s    z.ParentChildStore.from_dict.<locals>.<listcomp>r]   )rM   r^   r   r(   rK   )r'   r"   storerZ   parent_datar>   r?   r   r   r   r(      s   
zParentChildStore.from_dictN)r   r9   )r>   r   r?   r@   r   rA   )r   r   r   rL   )r   r   r   r@   )r4   r   r   rL   )r   r   r   rR   r*   )r"   r   r   r_   )r+   r,   r-   r.   r=   rK   rO   rP   rQ   rX   r    r2   r(   r   r   r   r   r8   9   s    







r8   c                   @  s   e Zd ZdZ	ddd	d
ZdS )ParentChildSplitStrategyu   将扁平文本块列表转换为父子结构。

    策略：滑动窗口合并——每 window_size 个连续子块合并为一个父块，
    子块保留对父块的引用（parent_id）。
       chunks	List[Any]window_sizeintr   *Tuple[List[ParentChunk], List[ChildChunk]]c                 C  s  |sg g fS g }g }t dt||D ]}||||  }tt }|d }t|ddp-d}	t|dd}
g }g }|D ]9}tt }t|dt|}|| t||t|d|	|t|ddt|d	dtt|d
i pji d}|| q:d	|}dd |D }|rd|d  d|d  d}n|d }|t| }d| d| }t
||	||dd |D |
i d}|| || q||fS )u\  将块列表转换为父子对。

        Args:
            chunks: 任意文本块列表。每个块须有 .text 属性；
                    若有 .document_id / .chapter / .metadata 则一并使用。
            window_size: 每个父块包含的子块数量，默认 3。

        Returns:
            (parents, children) 元组。
        r   r    r   Nr   r5   chunkr6   r   )r4   r   r   r   r5   r6   r   
c                 S  s(   g | ]}t |d drtt |d dqS )
article_noNri   )getattrr   rD   r   r   r   rG      s
    zFParentChildSplitStrategy.create_parent_child_pairs.<locals>.<listcomp>u   第-u   条   u   块c                 S  rB   r   rC   rD   r   r   r   rG      rH   )r   r   r   r   r   r   r   )rangelenr   uuiduuid4rm   rU   r3   r1   joinr   extend)r   rd   rf   rW   r?   window_startwindowr   firstr   r   window_childrenchild_textsrj   r4   r   rJ   parent_textarticle_nosr   	start_idxend_idxr>   r   r   r   create_parent_child_pairs   s^   



	


z2ParentChildSplitStrategy.create_parent_child_pairsN)rc   )rd   re   rf   rg   r   rh   )r+   r,   r-   r.   r   r   r   r   r   rb      s    	rb   )r.   
__future__r   rs   dataclassesr   r   r   typingr   r   r   r	   r
   r   r3   r8   rb   r   r   r   r   <module>   s    	`