From 22b235d2e37f51a1531dcef8c1d0138a23ec8fdb Mon Sep 17 00:00:00 2001 From: OpenClaw Bot Date: Fri, 20 Feb 2026 00:12:58 +0800 Subject: [PATCH] Fix db_manager.py duplicate code --- .../__pycache__/db_manager.cpython-312.pyc | Bin 36052 -> 46458 bytes backend/__pycache__/main.cpython-312.pyc | Bin 71711 -> 71953 bytes backend/db_manager.py | 1200 ++--------------- 3 files changed, 148 insertions(+), 1052 deletions(-) diff --git a/backend/__pycache__/db_manager.cpython-312.pyc b/backend/__pycache__/db_manager.cpython-312.pyc index b06fe2e941c0cf31bef607fd56aa1be177ce0992..1f0203c08f24f1fa519485400f95765b53c58f64 100644 GIT binary patch delta 16341 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 diff --git a/backend/__pycache__/main.cpython-312.pyc b/backend/__pycache__/main.cpython-312.pyc index 96bd3608426e4523e18595c719bafc12fc15577a..31b5d48105f758aea3f2d30df94f36b7ec2fff68 100644 GIT binary patch delta 4407 zcmai14OG-s7N7T@`TrUI!+bFV4x^wV0i&XPXqMj;uDepCo4Rf3q0!+WC*o4(q)gOSZPJ2P&7-D>M_W!V?!9kDu}+@m9DeWKcklh( z`#%2fJ=SkEtk`a_d}cPQI{cj-+)xs{u+1{F!em4B8T`9?Lx!wA8z8lER&0r`&KT6^ z>88G<)73=>S*ac7r#e_PWConjJS5tz2I7c@enAz!sZ4aJdAen$=~A(7x-`{-QKp~? zD!+9muaI7{8L+d?w26fSZnZvTtrUz33Q}b_F)?71ol>}m(nQ4sV}i!8-h(_|T58Zy zUCYeVr4q`NfSJreGu0(w^W=R_SjVIEmO3-EolSt_++hexrM+O+L+*#D%||b zxHx8pt^1vZpt&@K#;P}$riwPI)FZ}DIeIdWU@`|RDAA1E>bs*KBE83 z!ye&^rPk+I8-=+a6uGTI1#{aXxp6J)Y(ZNjx6S7iaW-FEq>UHx-PRf~zMF`*1!au4 zN8*!py7)SK&>kVM`&=T>j>Xf&FR6-|WcPI!Bs~O4iXgH364xq^;xaO^KdNs@=3kMl zxBG0jB|BWII>n6OO-q0?bL=r8)f))#l4(_eB0pT8GnWm3s_H@(2llyfaK8FkDHI1Q zI^v+>sh%trmOtg5mW^AIXCZ`Qipm2efto^pz#H&~>=CVtFQmt=kg<#mv&;PgNY>ZbND$pv+qlVm!o(LoDErMGLgdbb+zPX&QrBzQz6Fcj;mN++3JZGv4fSHyh$k zSi80BjB;Q2XU&*_hmAejRGr;%+?IaWmj0IGz4*7|kJ>Vymrta4nvDss^jV*}I`xRL zcbgp}+Ty`d>$N58;*+j-i-}X);&t}eHV4$#7WX=lH1K#*&f%n-t+_k$w&gV@A59u@ z)gEoOwCl-p0JPV()K6tYVw5)B5LxMHcj(gdSx37?7n|MD-UEF)+TEsjM3<4|Sffkr z@c$7#gmb z>twO_qQRr*qp%MCG(i?zsP8F{!|unxI?pHlp23#>E?6>e4~eg3IMs)UkYJitIe)O8 zJq&+a_z25^9~a(_a@Hb;GM+ppz_>*ZnkJ%HMv0vaSE;+EEy|Z*!7&F^q?w?{^F!sy z=&(k6;`wZ;UtbD)h>Ccapn#ws_IaZC9Fpw>gUBfd8ebfscYi^Aol*pmA*57zxxdKA1?LOIxs+^+Ny>n+8wYqs zh)x2$iXwHcBozo&5d_IF50a*)sSTtPc(g+s$17NIugzl>KlKK*vdsg-H91W4s7H*n z8b01KNQsR1+Lkf$Iy5cRM!h#!ukHfDDPpc2>?nzfKAa!v(mDvJ2~6#D^`?{>2f;f38Ewh6S|Y4T3&&eFc!ox@E3B9BQ_ z`DExl*r9$(I>8@c=w6RPQf&r)yXo4By-^0YSW~r_dn;xX`n&-zr}zSo!s}uCfuXDv zo;>CX&wUON4FX$JUz^Aw_TPT$Tm|Er1_>sfONmTN6da+bqXhE_KEmW4bNI};O&DH?&&V!TBbO>{A?T|0-()=k4b6k)6DTgw z4mY1;(n$tc?jCB0j0D@E^@y8=v>Qiy>GSqbZw1l#33Q}36YNF6gO6r7JP{{RD)30* zvPkh*?ikL`X8D<8sl$YaDeQfUNW@0!s$UT2QqC5Fu0i~r!PG@AxN*#`wqV?cw6!eo zdDB8|&GBZI@*m=gp%4?nX;Obj(ixH_lXMoAedSWWBc0$JZ2M{|`wpz3({UpD7cpnS z`1OFE!txuG=ZKP^9jd>sX6N8C;wmUUIY_#Q$9F z9x{2TzKbXoc%pfKQZfhz5QyZ0AX9k3;!}yHOB6*QDWIM5uuIyhQ~RX5jH9t7hh2e- zEopbLr?>V~E)(Gu_;c%c`8tY5ZA$kUD*ax4gK>S2(Rk ztmH(G%x6*x@(h`8&4Rd}!uQ2Ow0f*}RCpCVc{Jt-U)j|Z-AR2S*+5d?C-^|8)6)U9 ztIeHCn}(+mQ&c#eLh*t`j7?lhmw{awsoKyomj-wmCXDZ{`L4XIk2jLH_{0OqQNZ6N zeidxj+_5G!;g>FQo5^hz48PX>QL$e|>9N02y70V9{gErvLcAB@oojBzPGU4PT}uw{ zMJu#l8)1sa-VIcafUT(6?)&cmp`}%Fe8QT?O_=2cAqouDlNct?;dSd&0ra zd^rufuV{%kS4o*7^v)8*mJ`eMt6UAOjK$@p5U-e<-*6A7zMUlex!fb78-_|OTkeZ; zbVH@Y{>svKSoQ36Nug!;nDKudXT#?*d&DgCsPKun~0*gF2n&Zj$m=_(gDfo z7~^#6a5^|R9SxkmFiu;R(=Ot)OZeMVFRd7-x#F1(8%%5n`_qn)iETHyr!uCzSXM?alRKs*?WJsrx@a-xyv`{h9u2EqG8?}YPVq{>vHCne8(pQ;@7Rb{P zG6?IVHL4n2jdqiBBy93VK%~LX$DXF25-FBXtj>1FyTZS$17h5ZlIPz74Hg!@|}FLdazy=-qm| z9`nMU2L9Cf9mWR6cH@v6y*>0=joQHY7aCy!u5f(XP2Be^?pyDcm~TVCH#?{rgWC}B zZLmjhJA>UEc#h!qkxd#FdnC6vxJ71f4A{r3R3mDPZexJJXpiECMhw2t1kn|`RHHqb zlf-b61WsbK$2LiKvNI$6R4cDY+Dgc+h|wB`CXC>&I7NcMmNXKam0?g{`7oIX#uWu5 z0!mkGgE3WcBouP1EY=KGXEdFGN9QPUEp}BEI9)cE)1zi1Pw+CFD=l$)$TE1UDq0iD z(gFvnCTMe+#b@jQohJpTPa6jhx5q&Ghv6`LWloSW(x(zd88)rFS^r^hj8|j>Z>Y+e z0VV5WU|jXg(9x<9qb{1YX4xB|3Jdp!L1lH3?t(E`Z}6#cHU<7yeY*BOGFc~iRg*<& z(5F|8pF;+H!K(1gL0=5h8T3VIM<52H93C7c4?5?w=pLClJwKbY=a}*%L*EV?&E&T; zM&-w74jFSzZ17I}%*gyP!H1_vY;gFdTq!?MJ}TnisMeC7tUEf6%gIUk=^=(=;bYmt z%0yUxaFp^`%^)$(!fYW-Q?tE44rA&Pz}-Gp>8cGU;n{57s-`!wC}=f8I{fgLIPn%X zeJ8}!*-0MM)twUZ2<)5~A^!oX5Ed#AKa#B`x5B>1{zx)GvwDir$l~QOtqCbt&=!ChkPKE>4%fevUv=VatX*k_De`ut>acX24Gy zlEr(NYMYY0alW9L%tV;q5C_{Ebn(-WZ8`&|hWQ!IesmH#L?sT#l=mBE2x1W%cPrtW z4~t|ec%Ix#LX^L4O((P`aDvNi58XyJt(KI9sNy+?__^94Jdjzz+#O1JsRke6vnC@^Y3G*s{=a zv$&Nl)hf5OWUHlQ)JlOh%A4|2Ol)S!ckXAUI$o*RrxoO#Z0;b=&4zCeB`SMfZ4#LK zxouHo8Wgq7CmW!xEx~*}_dCGXmKMsLx-p#R(b06scx{5Xi-{j754`riKwf~+>f0^t>C4|cwW#GUAg51jFD3Pg_ z;ftrZ+s~0$Osq#6XkD(VG6!uzvpo!~A&{F8A<(_B<&c#$!@)yI;%jU^Md?2@iIDv; z`rTOh4b(=+gW2y|$s5Y@ce6AWe$sLgwUsR@u-jZVx}RyxwMw)a4jh|G9s*-$l#ZW9 zCfZNJw{Qexb(lg?(SPJ`qf`%fcVwB7j^<*@P9waJ&<;B~(!?WdC12_4*ry=}VeN?o z@)qnqF-p<+(i!b)8Y#b{uyi z9EWE+v&AkZ)+jxleMI<#K!zno{s$o8%r85&jGNJqv{%0_o9F zko{?gne+V0`Yf2cQxo{g?;-yj$q~MWRo#`s=YDeYsryMixK3pWeFW-8M#{aYhVVUf zoU#hNelp&CO_utRc>oH|&XX@NQO&xL^Bl~d7p-)itsRbd(HA+x zP`>^}G?@V(e~~Tzz}ONYtoI~2r=0KIPRMt#u5Wtcb+gV?S#6!eP!Vmy*%{ z5fRSeXW0ZX1LGutU2KXaI5;syaSnVa)N<2$rb@e*{Vu^H7cR1u6glh#4_47W^lTOB zQlv&rkKW~2R#t3t`M)oe8?IujR^v61d67V63F<;Z%rQiaL;oFC>A-xf6yfn1Ep4?-K< zTsgi(y=&XPT(hxv+rHi{Ei{-dT{#FD)i9O?RtU;>SXy4+SA5?rxYZ`dO(K`TL;dqD3;GD8Y$~@&0nu!4DU2luT^)9}?_X9jDy;{ca(SCkvyx zCh7bmfI(Vi*8k13%|+Kqo0BGDc8Bb#8U1mqz)=n*&m&^8olyGYBzYv$I)oW~l6^a8 zDc}6)BqSILFU^%WlLcBYO%Y?6(kbP;OX)(+C?;Bif91;)Qd(>;sIV26J3JvJj!M5s zLzwQ>%CfmwA_^~OBwr12G*h5s5KxuIBVhGiDZWu~;PMqMp(aa7QY+Dr zX9RM-@i~bs(qnziV-e>7O2$KqM-w8qUkas#=m^*tTgtFhcei1bgBPleA5YLzg>E1>AD5L*8pMrKQ7 LPV4DNl0p6teTZv+ diff --git a/backend/db_manager.py b/backend/db_manager.py index aec803e..1b0441c 100644 --- a/backend/db_manager.py +++ b/backend/db_manager.py @@ -1,13 +1,14 @@ #!/usr/bin/env python3 """ -InsightFlow Database Manager - Phase 3 +InsightFlow Database Manager - Phase 5 处理项目、实体、关系的持久化 -支持文档类型和多文件融合 +支持实体属性扩展 """ import os import json import sqlite3 +import uuid from datetime import datetime from typing import List, Dict, Optional, Tuple from dataclasses import dataclass @@ -114,7 +115,8 @@ class DatabaseManager: conn.commit() conn.close() - # Project operations + # ==================== Project Operations ==================== + def create_project(self, project_id: str, name: str, description: str = "") -> Project: conn = self.get_conn() now = datetime.now().isoformat() @@ -140,7 +142,8 @@ class DatabaseManager: conn.close() return [Project(**dict(r)) for r in rows] - # Entity operations + # ==================== Entity Operations ==================== + def create_entity(self, entity: Entity) -> Entity: conn = self.get_conn() conn.execute( @@ -168,9 +171,7 @@ class DatabaseManager: return None def find_similar_entities(self, project_id: str, name: str, threshold: float = 0.8) -> List[Entity]: - """查找相似实体(简单实现,生产可用 embedding)""" - # TODO: 使用 embedding 或模糊匹配 - # 现在简单返回包含相同关键词的实体 + """查找相似实体""" conn = self.get_conn() rows = conn.execute( "SELECT * FROM entities WHERE project_id = ? AND name LIKE ?", @@ -186,10 +187,9 @@ class DatabaseManager: return entities def merge_entities(self, target_id: str, source_id: str) -> Entity: - """合并两个实体(实体对齐)""" + """合并两个实体""" conn = self.get_conn() - # 获取两个实体 target = conn.execute("SELECT * FROM entities WHERE id = ?", (target_id,)).fetchone() source = conn.execute("SELECT * FROM entities WHERE id = ?", (source_id,)).fetchone() @@ -197,41 +197,21 @@ class DatabaseManager: conn.close() raise ValueError("Entity not found") - # 合并别名 target_aliases = set(json.loads(target['aliases']) if target['aliases'] else []) target_aliases.add(source['name']) target_aliases.update(json.loads(source['aliases']) if source['aliases'] else []) - # 更新目标实体 conn.execute( "UPDATE entities SET aliases = ?, updated_at = ? WHERE id = ?", (json.dumps(list(target_aliases)), datetime.now().isoformat(), target_id) ) - - # 更新提及记录 - conn.execute( - "UPDATE entity_mentions SET entity_id = ? WHERE entity_id = ?", - (target_id, source_id) - ) - - # 更新关系 - source 作为 source_entity_id - conn.execute( - "UPDATE entity_relations SET source_entity_id = ? WHERE source_entity_id = ?", - (target_id, source_id) - ) - - # 更新关系 - source 作为 target_entity_id - conn.execute( - "UPDATE entity_relations SET target_entity_id = ? WHERE target_entity_id = ?", - (target_id, source_id) - ) - - # 删除源实体 + conn.execute("UPDATE entity_mentions SET entity_id = ? WHERE entity_id = ?", (target_id, source_id)) + conn.execute("UPDATE entity_relations SET source_entity_id = ? WHERE source_entity_id = ?", (target_id, source_id)) + conn.execute("UPDATE entity_relations SET target_entity_id = ? WHERE target_entity_id = ?", (target_id, source_id)) conn.execute("DELETE FROM entities WHERE id = ?", (source_id,)) conn.commit() conn.close() - return self.get_entity(target_id) def get_entity(self, entity_id: str) -> Optional[Entity]: @@ -259,7 +239,49 @@ class DatabaseManager: entities.append(Entity(**data)) return entities - # Mention operations + def update_entity(self, entity_id: str, **kwargs) -> Entity: + """更新实体信息""" + conn = self.get_conn() + + allowed_fields = ['name', 'type', 'definition', 'canonical_name'] + updates = [] + values = [] + + for field in allowed_fields: + if field in kwargs: + updates.append(f"{field} = ?") + values.append(kwargs[field]) + + if 'aliases' in kwargs: + updates.append("aliases = ?") + values.append(json.dumps(kwargs['aliases'])) + + if not updates: + conn.close() + return self.get_entity(entity_id) + + updates.append("updated_at = ?") + values.append(datetime.now().isoformat()) + values.append(entity_id) + + query = f"UPDATE entities SET {', '.join(updates)} WHERE id = ?" + conn.execute(query, values) + conn.commit() + conn.close() + return self.get_entity(entity_id) + + def delete_entity(self, entity_id: str): + """删除实体及其关联数据""" + conn = self.get_conn() + conn.execute("DELETE FROM entity_mentions WHERE entity_id = ?", (entity_id,)) + conn.execute("DELETE FROM entity_relations WHERE source_entity_id = ? OR target_entity_id = ?", (entity_id, entity_id)) + conn.execute("DELETE FROM entity_attributes WHERE entity_id = ?", (entity_id,)) + conn.execute("DELETE FROM entities WHERE id = ?", (entity_id,)) + conn.commit() + conn.close() + + # ==================== Mention Operations ==================== + def add_mention(self, mention: EntityMention) -> EntityMention: conn = self.get_conn() conn.execute( @@ -280,10 +302,10 @@ class DatabaseManager: ).fetchall() conn.close() return [EntityMention(**dict(r)) for r in rows] - - # Transcript operations + + # ==================== Transcript Operations ==================== + def save_transcript(self, transcript_id: str, project_id: str, filename: str, full_text: str, transcript_type: str = "audio"): - """保存转录记录""" conn = self.get_conn() now = datetime.now().isoformat() conn.execute( @@ -294,16 +316,12 @@ class DatabaseManager: conn.close() def get_transcript(self, transcript_id: str) -> Optional[dict]: - """获取转录记录""" conn = self.get_conn() row = conn.execute("SELECT * FROM transcripts WHERE id = ?", (transcript_id,)).fetchone() conn.close() - if row: - return dict(row) - return None + return dict(row) if row else None def list_project_transcripts(self, project_id: str) -> List[dict]: - """列出项目的所有转录""" conn = self.get_conn() rows = conn.execute( "SELECT * FROM transcripts WHERE project_id = ? ORDER BY created_at DESC", @@ -312,10 +330,22 @@ class DatabaseManager: conn.close() return [dict(r) for r in rows] - # Relation operations + def update_transcript(self, transcript_id: str, full_text: str) -> dict: + conn = self.get_conn() + now = datetime.now().isoformat() + conn.execute( + "UPDATE transcripts SET full_text = ?, updated_at = ? WHERE id = ?", + (full_text, now, transcript_id) + ) + conn.commit() + row = conn.execute("SELECT * FROM transcripts WHERE id = ?", (transcript_id,)).fetchone() + conn.close() + return dict(row) if row else None + + # ==================== Relation Operations ==================== + def create_relation(self, project_id: str, source_entity_id: str, target_entity_id: str, relation_type: str = "related", evidence: str = "", transcript_id: str = ""): - """创建实体关系""" conn = self.get_conn() relation_id = str(uuid.uuid4())[:8] now = datetime.now().isoformat() @@ -330,7 +360,6 @@ class DatabaseManager: return relation_id def get_entity_relations(self, entity_id: str) -> List[dict]: - """获取实体的所有关系""" conn = self.get_conn() rows = conn.execute( """SELECT * FROM entity_relations @@ -342,7 +371,6 @@ class DatabaseManager: return [dict(r) for r in rows] def list_project_relations(self, project_id: str) -> List[dict]: - """列出项目的所有关系""" conn = self.get_conn() rows = conn.execute( "SELECT * FROM entity_relations WHERE project_id = ? ORDER BY created_at DESC", @@ -351,68 +379,8 @@ class DatabaseManager: conn.close() return [dict(r) for r in rows] - def update_entity(self, entity_id: str, **kwargs) -> Entity: - """更新实体信息""" - conn = self.get_conn() - - # 构建更新字段 - allowed_fields = ['name', 'type', 'definition', 'canonical_name'] - updates = [] - values = [] - - for field in allowed_fields: - if field in kwargs: - updates.append(f"{field} = ?") - values.append(kwargs[field]) - - # 处理别名 - if 'aliases' in kwargs: - updates.append("aliases = ?") - values.append(json.dumps(kwargs['aliases'])) - - if not updates: - conn.close() - return self.get_entity(entity_id) - - updates.append("updated_at = ?") - values.append(datetime.now().isoformat()) - values.append(entity_id) - - query = f"UPDATE entities SET {', '.join(updates)} WHERE id = ?" - conn.execute(query, values) - conn.commit() - conn.close() - - return self.get_entity(entity_id) - - def delete_entity(self, entity_id: str): - """删除实体及其关联数据""" - conn = self.get_conn() - - # 删除提及记录 - conn.execute("DELETE FROM entity_mentions WHERE entity_id = ?", (entity_id,)) - - # 删除关系 - conn.execute("DELETE FROM entity_relations WHERE source_entity_id = ? OR target_entity_id = ?", - (entity_id, entity_id)) - - # 删除实体 - conn.execute("DELETE FROM entities WHERE id = ?", (entity_id,)) - - conn.commit() - conn.close() - - def delete_relation(self, relation_id: str): - """删除关系""" - conn = self.get_conn() - conn.execute("DELETE FROM entity_relations WHERE id = ?", (relation_id,)) - conn.commit() - conn.close() - def update_relation(self, relation_id: str, **kwargs) -> dict: - """更新关系""" conn = self.get_conn() - allowed_fields = ['relation_type', 'evidence'] updates = [] values = [] @@ -430,42 +398,25 @@ class DatabaseManager: row = conn.execute("SELECT * FROM entity_relations WHERE id = ?", (relation_id,)).fetchone() conn.close() - return dict(row) if row else None - def update_transcript(self, transcript_id: str, full_text: str) -> dict: - """更新转录文本""" + def delete_relation(self, relation_id: str): conn = self.get_conn() - now = datetime.now().isoformat() - - conn.execute( - "UPDATE transcripts SET full_text = ?, updated_at = ? WHERE id = ?", - (full_text, now, transcript_id) - ) + conn.execute("DELETE FROM entity_relations WHERE id = ?", (relation_id,)) conn.commit() - - row = conn.execute("SELECT * FROM transcripts WHERE id = ?", (transcript_id,)).fetchone() conn.close() - - return dict(row) if row else None - # Phase 3: Glossary operations + # ==================== Glossary Operations ==================== + def add_glossary_term(self, project_id: str, term: str, pronunciation: str = "") -> str: - """添加术语到术语表""" conn = self.get_conn() - - # 检查是否已存在 existing = conn.execute( "SELECT * FROM glossary WHERE project_id = ? AND term = ?", (project_id, term) ).fetchone() if existing: - # 更新频率 - conn.execute( - "UPDATE glossary SET frequency = frequency + 1 WHERE id = ?", - (existing['id'],) - ) + conn.execute("UPDATE glossary SET frequency = frequency + 1 WHERE id = ?", (existing['id'],)) conn.commit() conn.close() return existing['id'] @@ -480,7 +431,6 @@ class DatabaseManager: return term_id def list_glossary(self, project_id: str) -> List[dict]: - """列出项目术语表""" conn = self.get_conn() rows = conn.execute( "SELECT * FROM glossary WHERE project_id = ? ORDER BY frequency DESC", @@ -490,20 +440,14 @@ class DatabaseManager: return [dict(r) for r in rows] def delete_glossary_term(self, term_id: str): - """删除术语""" conn = self.get_conn() conn.execute("DELETE FROM glossary WHERE id = ?", (term_id,)) conn.commit() conn.close() - # Phase 3: Get all entities for embedding - def get_all_entities_for_embedding(self, project_id: str) -> List[Entity]: - """获取所有实体用于 embedding 计算""" - return self.list_project_entities(project_id) + # ==================== Phase 4: Agent & Provenance ==================== - # Phase 4: Agent & Provenance methods def get_relation_with_details(self, relation_id: str) -> Optional[dict]: - """获取关系详情,包含源文档信息""" conn = self.get_conn() row = conn.execute( """SELECT r.*, @@ -517,19 +461,11 @@ class DatabaseManager: (relation_id,) ).fetchone() conn.close() - if row: - return dict(row) - return None + return dict(row) if row else None def get_entity_with_mentions(self, entity_id: str) -> Optional[dict]: - """获取实体详情及所有提及位置""" conn = self.get_conn() - - # 获取实体信息 - entity_row = conn.execute( - "SELECT * FROM entities WHERE id = ?", (entity_id,) - ).fetchone() - + entity_row = conn.execute("SELECT * FROM entities WHERE id = ?", (entity_id,)).fetchone() if not entity_row: conn.close() return None @@ -537,23 +473,18 @@ class DatabaseManager: entity = dict(entity_row) entity['aliases'] = json.loads(entity['aliases']) if entity['aliases'] else [] - # 获取提及位置 mentions = conn.execute( """SELECT m.*, t.filename, t.created_at as transcript_date FROM entity_mentions m JOIN transcripts t ON m.transcript_id = t.id - WHERE m.entity_id = ? - ORDER BY t.created_at, m.start_pos""", + WHERE m.entity_id = ? ORDER BY t.created_at, m.start_pos""", (entity_id,) ).fetchall() - entity['mentions'] = [dict(m) for m in mentions] entity['mention_count'] = len(mentions) - # 获取相关关系 relations = conn.execute( - """SELECT r.*, - s.name as source_name, t.name as target_name + """SELECT r.*, s.name as source_name, t.name as target_name FROM entity_relations r JOIN entities s ON r.source_entity_id = s.id JOIN entities t ON r.target_entity_id = t.id @@ -561,14 +492,12 @@ class DatabaseManager: ORDER BY r.created_at DESC""", (entity_id, entity_id) ).fetchall() - entity['relations'] = [dict(r) for r in relations] conn.close() return entity def search_entities(self, project_id: str, query: str) -> List[Entity]: - """搜索实体""" conn = self.get_conn() rows = conn.execute( """SELECT * FROM entities @@ -587,49 +516,33 @@ class DatabaseManager: return entities def get_project_summary(self, project_id: str) -> dict: - """获取项目摘要信息,用于 RAG 上下文""" conn = self.get_conn() + project = conn.execute("SELECT * FROM projects WHERE id = ?", (project_id,)).fetchone() - # 项目基本信息 - project = conn.execute( - "SELECT * FROM projects WHERE id = ?", (project_id,) - ).fetchone() - - # 统计信息 entity_count = conn.execute( - "SELECT COUNT(*) as count FROM entities WHERE project_id = ?", - (project_id,) + "SELECT COUNT(*) as count FROM entities WHERE project_id = ?", (project_id,) ).fetchone()['count'] transcript_count = conn.execute( - "SELECT COUNT(*) as count FROM transcripts WHERE project_id = ?", - (project_id,) + "SELECT COUNT(*) as count FROM transcripts WHERE project_id = ?", (project_id,) ).fetchone()['count'] relation_count = conn.execute( - "SELECT COUNT(*) as count FROM entity_relations WHERE project_id = ?", - (project_id,) + "SELECT COUNT(*) as count FROM entity_relations WHERE project_id = ?", (project_id,) ).fetchone()['count'] - # 获取最近的转录文本片段 recent_transcripts = conn.execute( - """SELECT filename, full_text, created_at - FROM transcripts - WHERE project_id = ? - ORDER BY created_at DESC - LIMIT 5""", + """SELECT filename, full_text, created_at FROM transcripts + WHERE project_id = ? ORDER BY created_at DESC LIMIT 5""", (project_id,) ).fetchall() - # 获取高频实体 top_entities = conn.execute( """SELECT e.name, e.type, e.definition, COUNT(m.id) as mention_count FROM entities e LEFT JOIN entity_mentions m ON e.id = m.entity_id WHERE e.project_id = ? - GROUP BY e.id - ORDER BY mention_count DESC - LIMIT 10""", + GROUP BY e.id ORDER BY mention_count DESC LIMIT 10""", (project_id,) ).fetchall() @@ -645,74 +558,49 @@ class DatabaseManager: 'recent_transcripts': [dict(t) for t in recent_transcripts], 'top_entities': [dict(e) for e in top_entities] } - - # Phase 5: Timeline operations + + def get_transcript_context(self, transcript_id: str, position: int, context_chars: int = 200) -> str: + conn = self.get_conn() + row = conn.execute("SELECT full_text FROM transcripts WHERE id = ?", (transcript_id,)).fetchone() + conn.close() + if not row: + return "" + text = row['full_text'] + start = max(0, position - context_chars) + end = min(len(text), position + context_chars) + return text[start:end] + + # ==================== Phase 5: Timeline Operations ==================== + def get_project_timeline(self, project_id: str, entity_id: str = None, start_date: str = None, end_date: str = None) -> List[dict]: - """获取项目时间线数据 - 按时间顺序的实体提及和事件""" conn = self.get_conn() - # 构建查询条件 conditions = ["t.project_id = ?"] params = [project_id] if entity_id: conditions.append("m.entity_id = ?") params.append(entity_id) - if start_date: conditions.append("t.created_at >= ?") params.append(start_date) - if end_date: conditions.append("t.created_at <= ?") params.append(end_date) where_clause = " AND ".join(conditions) - # 获取实体提及时间线 mentions = conn.execute( f"""SELECT m.*, e.name as entity_name, e.type as entity_type, e.definition, t.filename, t.created_at as event_date, t.type as source_type FROM entity_mentions m JOIN entities e ON m.entity_id = e.id JOIN transcripts t ON m.transcript_id = t.id - WHERE {where_clause} - ORDER BY t.created_at, m.start_pos""", + WHERE {where_clause} ORDER BY t.created_at, m.start_pos""", params ).fetchall() - # 获取关系创建时间线 - relation_conditions = ["r.project_id = ?"] - relation_params = [project_id] - - if start_date: - relation_conditions.append("r.created_at >= ?") - relation_params.append(start_date) - - if end_date: - relation_conditions.append("r.created_at <= ?") - relation_params.append(end_date) - - relation_where = " AND ".join(relation_conditions) - - relations = conn.execute( - f"""SELECT r.*, - s.name as source_name, t.name as target_name, - tr.filename, r.created_at as event_date - FROM entity_relations r - JOIN entities s ON r.source_entity_id = s.id - JOIN entities t ON r.target_entity_id = t.id - LEFT JOIN transcripts tr ON r.transcript_id = tr.id - WHERE {relation_where} - ORDER BY r.created_at""", - relation_params - ).fetchall() - - conn.close() - - # 合并并格式化时间线事件 timeline_events = [] - for m in mentions: timeline_events.append({ 'id': m['id'], @@ -721,52 +609,26 @@ class DatabaseManager: 'entity_id': m['entity_id'], 'entity_name': m['entity_name'], 'entity_type': m['entity_type'], - 'entity_definition': m['definition'], 'text_snippet': m['text_snippet'], 'confidence': m['confidence'], - 'source': { - 'transcript_id': m['transcript_id'], - 'filename': m['filename'], - 'type': m['source_type'] - } + 'source': {'transcript_id': m['transcript_id'], 'filename': m['filename'], 'type': m['source_type']} }) - for r in relations: - timeline_events.append({ - 'id': r['id'], - 'type': 'relation', - 'event_date': r['event_date'], - 'relation_type': r['relation_type'], - 'source_entity': r['source_name'], - 'target_entity': r['target_name'], - 'evidence': r['evidence'], - 'source': { - 'transcript_id': r.get('transcript_id'), - 'filename': r['filename'] - } - }) - - # 按时间排序 + conn.close() timeline_events.sort(key=lambda x: x['event_date']) - return timeline_events def get_entity_timeline_summary(self, project_id: str) -> dict: - """获取项目实体时间线摘要统计""" conn = self.get_conn() - # 按日期统计提及数量 daily_stats = conn.execute( """SELECT DATE(t.created_at) as date, COUNT(*) as count FROM entity_mentions m JOIN transcripts t ON m.transcript_id = t.id - WHERE t.project_id = ? - GROUP BY DATE(t.created_at) - ORDER BY date""", + WHERE t.project_id = ? GROUP BY DATE(t.created_at) ORDER BY date""", (project_id,) ).fetchall() - # 按实体统计提及数量 entity_stats = conn.execute( """SELECT e.name, e.type, COUNT(m.id) as mention_count, MIN(t.created_at) as first_mentioned, @@ -775,23 +637,7 @@ class DatabaseManager: LEFT JOIN entity_mentions m ON e.id = m.entity_id LEFT JOIN transcripts t ON m.transcript_id = t.id WHERE e.project_id = ? - GROUP BY e.id - ORDER BY mention_count DESC - LIMIT 20""", - (project_id,) - ).fetchall() - - # 获取活跃时间段 - active_periods = conn.execute( - """SELECT - DATE(t.created_at) as date, - COUNT(DISTINCT m.entity_id) as active_entities, - COUNT(m.id) as total_mentions - FROM transcripts t - LEFT JOIN entity_mentions m ON t.id = m.transcript_id - WHERE t.project_id = ? - GROUP BY DATE(t.created_at) - ORDER BY date""", + GROUP BY e.id ORDER BY mention_count DESC LIMIT 20""", (project_id,) ).fetchall() @@ -799,56 +645,42 @@ class DatabaseManager: return { 'daily_activity': [dict(d) for d in daily_stats], - 'top_entities': [dict(e) for e in entity_stats], - 'active_periods': [dict(a) for a in active_periods] + 'top_entities': [dict(e) for e in entity_stats] } - - # Phase 5: Attribute Template operations - def create_attribute_template(self, project_id: str, template_data: dict) -> dict: - """创建属性模板""" + + # ==================== Phase 5: Entity Attributes ==================== + + def create_attribute_template(self, template: AttributeTemplate) -> AttributeTemplate: conn = self.get_conn() - template_id = str(uuid.uuid4())[:8] now = datetime.now().isoformat() - conn.execute( """INSERT INTO attribute_templates - (id, project_id, name, type, description, options, is_required, default_value, sort_order, created_at, updated_at) + (id, project_id, name, type, options, default_value, description, is_required, display_order, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", - (template_id, project_id, template_data['name'], template_data['type'], - template_data.get('description', ''), - json.dumps(template_data.get('options', [])) if template_data.get('options') else None, - 1 if template_data.get('is_required') else 0, - template_data.get('default_value'), - template_data.get('sort_order', 0), - now, now) + (template.id, template.project_id, template.name, template.type, + json.dumps(template.options) if template.options else None, + template.default_value, template.description, template.is_required, + template.display_order, now, now) ) conn.commit() conn.close() - return self.get_attribute_template(template_id) + return template - def get_attribute_template(self, template_id: str) -> Optional[dict]: - """获取属性模板详情""" + def get_attribute_template(self, template_id: str) -> Optional[AttributeTemplate]: conn = self.get_conn() - row = conn.execute( - "SELECT * FROM attribute_templates WHERE id = ?", - (template_id,) - ).fetchone() + row = conn.execute("SELECT * FROM attribute_templates WHERE id = ?", (template_id,)).fetchone() conn.close() - if row: data = dict(row) - if data.get('options'): - data['options'] = json.loads(data['options']) - return data + data['options'] = json.loads(data['options']) if data['options'] else [] + return AttributeTemplate(**data) return None - def list_attribute_templates(self, project_id: str) -> List[dict]: - """列出项目的所有属性模板""" + def list_attribute_templates(self, project_id: str) -> List[AttributeTemplate]: conn = self.get_conn() rows = conn.execute( - """SELECT * FROM attribute_templates - WHERE project_id = ? - ORDER BY sort_order, created_at""", + """SELECT * FROM attribute_templates WHERE project_id = ? + ORDER BY display_order, created_at""", (project_id,) ).fetchall() conn.close() @@ -856,444 +688,51 @@ class DatabaseManager: templates = [] for row in rows: data = dict(row) - if data.get('options'): - data['options'] = json.loads(data['options']) - templates.append(data) + data['options'] = json.loads(data['options']) if data['options'] else [] + templates.append(AttributeTemplate(**data)) return templates - def update_attribute_template(self, template_id: str, template_data: dict) -> Optional[dict]: - """更新属性模板""" + def update_attribute_template(self, template_id: str, **kwargs) -> Optional[AttributeTemplate]: conn = self.get_conn() - now = datetime.now().isoformat() - - allowed_fields = ['name', 'type', 'description', 'is_required', 'default_value', 'sort_order'] + allowed_fields = ['name', 'type', 'options', 'default_value', 'description', 'is_required', 'display_order'] updates = [] values = [] for field in allowed_fields: - if field in template_data: + if field in kwargs: updates.append(f"{field} = ?") - if field == 'is_required': - values.append(1 if template_data[field] else 0) + if field == 'options': + values.append(json.dumps(kwargs[field]) if kwargs[field] else None) else: - values.append(template_data[field]) - - if 'options' in template_data: - updates.append("options = ?") - values.append(json.dumps(template_data['options']) if template_data['options'] else None) - - if not updates: - conn.close() - return self.get_attribute_template(template_id) - - updates.append("updated_at = ?") - values.append(now) - values.append(template_id) - - query = f"UPDATE attribute_templates SET {', '.join(updates)} WHERE id = ?" - conn.execute(query, values) - conn.commit() - conn.close() - - return self.get_attribute_template(template_id) - - def delete_attribute_template(self, template_id: str): - """删除属性模板""" - conn = self.get_conn() - conn.execute("DELETE FROM attribute_templates WHERE id = ?", (template_id,)) - conn.commit() - conn.close() - - # Phase 5: Entity Attribute operations - def get_entity_attributes(self, entity_id: str) -> List[dict]: - """获取实体的所有属性""" - conn = self.get_conn() - rows = conn.execute( - """SELECT a.*, t.name as template_name, t.description as template_description - FROM entity_attributes a - LEFT JOIN attribute_templates t ON a.template_id = t.id - WHERE a.entity_id = ? - ORDER BY t.sort_order, a.created_at""", - (entity_id,) - ).fetchall() - conn.close() - - attributes = [] - for row in rows: - data = dict(row) - if data.get('options'): - data['options'] = json.loads(data['options']) - # 解析 value 根据 type - if data['type'] == 'number' and data['value']: - try: - data['value'] = float(data['value']) - except: - pass - elif data['type'] == 'multiselect' and data['value']: - try: - data['value'] = json.loads(data['value']) - except: - pass - attributes.append(data) - return attributes - - def get_entity_attribute(self, attribute_id: str) -> Optional[dict]: - """获取单个属性详情""" - conn = self.get_conn() - row = conn.execute( - "SELECT * FROM entity_attributes WHERE id = ?", - (attribute_id,) - ).fetchone() - conn.close() - - if row: - data = dict(row) - if data.get('options'): - data['options'] = json.loads(data['options']) - return data - return None - - def set_entity_attribute(self, entity_id: str, attr_data: dict, changed_by: str = "system") -> dict: - """设置实体属性值(创建或更新)""" - conn = self.get_conn() - now = datetime.now().isoformat() - - # 检查是否已存在 - existing = conn.execute( - "SELECT * FROM entity_attributes WHERE entity_id = ? AND name = ?", - (entity_id, attr_data['name']) - ).fetchone() - - # 处理 value 存储 - value = attr_data['value'] - if attr_data['type'] == 'multiselect' and isinstance(value, list): - value = json.dumps(value) - elif value is not None: - value = str(value) - - if existing: - # 记录历史 - old_value = existing['value'] - conn.execute( - """INSERT INTO attribute_history - (id, entity_id, attribute_name, old_value, new_value, changed_by, changed_at, change_reason) - VALUES (?, ?, ?, ?, ?, ?, ?, ?)""", - (str(uuid.uuid4())[:8], entity_id, attr_data['name'], old_value, value, - changed_by, now, attr_data.get('change_reason', '')) - ) - - # 更新属性 - conn.execute( - """UPDATE entity_attributes - SET value = ?, type = ?, options = ?, updated_at = ? - WHERE id = ?""", - (value, attr_data['type'], - json.dumps(attr_data.get('options', [])) if attr_data.get('options') else existing['options'], - now, existing['id']) - ) - attribute_id = existing['id'] - else: - # 创建新属性 - attribute_id = str(uuid.uuid4())[:8] - conn.execute( - """INSERT INTO entity_attributes - (id, entity_id, template_id, name, type, value, options, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""", - (attribute_id, entity_id, attr_data.get('template_id'), - attr_data['name'], attr_data['type'], value, - json.dumps(attr_data.get('options', [])) if attr_data.get('options') else None, - now, now) - ) - - # 记录历史 - conn.execute( - """INSERT INTO attribute_history - (id, entity_id, attribute_name, old_value, new_value, changed_by, changed_at, change_reason) - VALUES (?, ?, ?, ?, ?, ?, ?, ?)""", - (str(uuid.uuid4())[:8], entity_id, attr_data['name'], None, value, - changed_by, now, attr_data.get('change_reason', '创建属性')) - ) - - conn.commit() - conn.close() - return self.get_entity_attribute(attribute_id) - - def update_entity_attribute(self, attribute_id: str, attr_data: dict, changed_by: str = "system") -> Optional[dict]: - """更新实体属性""" - conn = self.get_conn() - now = datetime.now().isoformat() - - existing = conn.execute( - "SELECT * FROM entity_attributes WHERE id = ?", - (attribute_id,) - ).fetchone() - - if not existing: - conn.close() - return None - - # 处理 value - value = attr_data.get('value') - if value is not None: - if attr_data.get('type', existing['type']) == 'multiselect' and isinstance(value, list): - value = json.dumps(value) - else: - value = str(value) - - # 记录历史 - conn.execute( - """INSERT INTO attribute_history - (id, entity_id, attribute_name, old_value, new_value, changed_by, changed_at, change_reason) - VALUES (?, ?, ?, ?, ?, ?, ?, ?)""", - (str(uuid.uuid4())[:8], existing['entity_id'], existing['name'], - existing['value'], value, changed_by, now, - attr_data.get('change_reason', '')) - ) - - # 更新字段 - updates = [] - values = [] - - if 'value' in attr_data: - updates.append("value = ?") - values.append(value) - - if 'type' in attr_data: - updates.append("type = ?") - values.append(attr_data['type']) - - if 'options' in attr_data: - updates.append("options = ?") - values.append(json.dumps(attr_data['options']) if attr_data['options'] else None) + values.append(kwargs[field]) if updates: updates.append("updated_at = ?") - values.append(now) - values.append(attribute_id) - - query = f"UPDATE entity_attributes SET {', '.join(updates)} WHERE id = ?" + values.append(datetime.now().isoformat()) + values.append(template_id) + query = f"UPDATE attribute_templates SET {', '.join(updates)} WHERE id = ?" conn.execute(query, values) conn.commit() conn.close() - return self.get_entity_attribute(attribute_id) - - def delete_entity_attribute(self, attribute_id: str, changed_by: str = "system"): - """删除实体属性""" - conn = self.get_conn() - - existing = conn.execute( - "SELECT * FROM entity_attributes WHERE id = ?", - (attribute_id,) - ).fetchone() - - if existing: - # 记录历史 - conn.execute( - """INSERT INTO attribute_history - (id, entity_id, attribute_name, old_value, new_value, changed_by, changed_at, change_reason) - VALUES (?, ?, ?, ?, ?, ?, ?, ?)""", - (str(uuid.uuid4())[:8], existing['entity_id'], existing['name'], - existing['value'], None, changed_by, datetime.now().isoformat(), '删除属性') - ) - - conn.execute("DELETE FROM entity_attributes WHERE id = ?", (attribute_id,)) - conn.commit() - - conn.close() - - def get_attribute_history(self, entity_id: str, attribute_name: str = None) -> List[dict]: - """获取属性变更历史""" - conn = self.get_conn() - - if attribute_name: - rows = conn.execute( - """SELECT * FROM attribute_history - WHERE entity_id = ? AND attribute_name = ? - ORDER BY changed_at DESC""", - (entity_id, attribute_name) - ).fetchall() - else: - rows = conn.execute( - """SELECT * FROM attribute_history - WHERE entity_id = ? - ORDER BY changed_at DESC""", - (entity_id,) - ).fetchall() - - conn.close() - return [dict(r) for r in rows] - - def search_entities_by_attributes(self, project_id: str, filters: List[dict]) -> List[Entity]: - """根据属性筛选搜索实体""" - conn = self.get_conn() - - # 基础查询 - base_query = "SELECT DISTINCT e.* FROM entities e" - where_conditions = ["e.project_id = ?"] - params = [project_id] - - # 为每个过滤条件添加 JOIN - join_clauses = [] - for i, f in enumerate(filters): - alias = f"a{i}" - join_clauses.append( - f"JOIN entity_attributes {alias} ON e.id = {alias}.entity_id AND {alias}.name = ?" - ) - params.append(f['name']) - - operator = f.get('operator', 'eq') - if operator == 'eq': - where_conditions.append(f"{alias}.value = ?") - params.append(str(f['value'])) - elif operator == 'contains': - where_conditions.append(f"{alias}.value LIKE ?") - params.append(f"%{f['value']}%") - elif operator == 'gt': - where_conditions.append(f"CAST({alias}.value AS REAL) > ?") - params.append(float(f['value'])) - elif operator == 'lt': - where_conditions.append(f"CAST({alias}.value AS REAL) < ?") - params.append(float(f['value'])) - - query = base_query + " " + " ".join(join_clauses) + " WHERE " + " AND ".join(where_conditions) - - rows = conn.execute(query, params).fetchall() - conn.close() - - entities = [] - for row in rows: - data = dict(row) - data['aliases'] = json.loads(data['aliases']) if data['aliases'] else [] - entities.append(Entity(**data)) - return entities - - def get_transcript_context(self, transcript_id: str, position: int, context_chars: int = 200) -> str: - """获取转录文本的上下文""" - conn = self.get_conn() - row = conn.execute( - "SELECT full_text FROM transcripts WHERE id = ?", - (transcript_id,) - ).fetchone() - conn.close() - - if not row: - return "" - - text = row['full_text'] - start = max(0, position - context_chars) - end = min(len(text), position + context_chars) - return text[start:end] - - # ==================== Phase 5: 实体属性管理 ==================== - - # ---- 属性模板管理 ---- - - def create_attribute_template(self, template: AttributeTemplate) -> AttributeTemplate: - """创建属性模板""" - conn = self.get_conn() - now = datetime.now().isoformat() - conn.execute( - """INSERT INTO attribute_templates - (id, project_id, name, type, options, default_value, description, is_required, display_order, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", - (template.id, template.project_id, template.name, template.type, - json.dumps(template.options) if template.options else None, - template.default_value, template.description, template.is_required, - template.display_order, now, now) - ) - conn.commit() - conn.close() - return template - - def get_attribute_template(self, template_id: str) -> Optional[AttributeTemplate]: - """获取属性模板""" - conn = self.get_conn() - row = conn.execute( - "SELECT * FROM attribute_templates WHERE id = ?", - (template_id,) - ).fetchone() - conn.close() - if row: - data = dict(row) - data['options'] = json.loads(data['options']) if data['options'] else [] - return AttributeTemplate(**data) - return None - - def list_attribute_templates(self, project_id: str) -> List[AttributeTemplate]: - """列出项目的所有属性模板""" - conn = self.get_conn() - rows = conn.execute( - """SELECT * FROM attribute_templates - WHERE project_id = ? - ORDER BY display_order, created_at""", - (project_id,) - ).fetchall() - conn.close() - - templates = [] - for row in rows: - data = dict(row) - data['options'] = json.loads(data['options']) if data['options'] else [] - templates.append(AttributeTemplate(**data)) - return templates - - def update_attribute_template(self, template_id: str, **kwargs) -> Optional[AttributeTemplate]: - """更新属性模板""" - conn = self.get_conn() - - allowed_fields = ['name', 'type', 'options', 'default_value', - 'description', 'is_required', 'display_order'] - updates = [] - values = [] - - for field in allowed_fields: - if field in kwargs: - updates.append(f"{field} = ?") - if field == 'options': - values.append(json.dumps(kwargs[field]) if kwargs[field] else None) - else: - values.append(kwargs[field]) - - if not updates: - conn.close() - return self.get_attribute_template(template_id) - - updates.append("updated_at = ?") - values.append(datetime.now().isoformat()) - values.append(template_id) - - query = f"UPDATE attribute_templates SET {', '.join(updates)} WHERE id = ?" - conn.execute(query, values) - conn.commit() - conn.close() - return self.get_attribute_template(template_id) def delete_attribute_template(self, template_id: str): - """删除属性模板(会级联删除相关属性值)""" conn = self.get_conn() conn.execute("DELETE FROM attribute_templates WHERE id = ?", (template_id,)) conn.commit() conn.close() - # ---- 实体属性值管理 ---- - - def set_entity_attribute(self, attr: EntityAttribute, - changed_by: str = "system", - change_reason: str = "") -> EntityAttribute: - """设置实体属性值,自动记录历史""" + def set_entity_attribute(self, attr: EntityAttribute, changed_by: str = "system", change_reason: str = "") -> EntityAttribute: conn = self.get_conn() now = datetime.now().isoformat() - # 获取旧值 old_row = conn.execute( "SELECT value FROM entity_attributes WHERE entity_id = ? AND template_id = ?", (attr.entity_id, attr.template_id) ).fetchone() old_value = old_row['value'] if old_row else None - # 记录变更历史 if old_value != attr.value: conn.execute( """INSERT INTO attribute_history @@ -1303,7 +742,6 @@ class DatabaseManager: old_value, attr.value, changed_by, now, change_reason) ) - # 插入或更新属性值 conn.execute( """INSERT OR REPLACE INTO entity_attributes (id, entity_id, template_id, value, created_at, updated_at) @@ -1311,8 +749,7 @@ class DatabaseManager: COALESCE((SELECT id FROM entity_attributes WHERE entity_id = ? AND template_id = ?), ?), ?, ?, ?, COALESCE((SELECT created_at FROM entity_attributes WHERE entity_id = ? AND template_id = ?), ?), - ? - )""", + ?)""", (attr.entity_id, attr.template_id, attr.id, attr.entity_id, attr.template_id, attr.value, attr.entity_id, attr.template_id, now, now) @@ -1323,85 +760,58 @@ class DatabaseManager: return attr def get_entity_attributes(self, entity_id: str) -> List[EntityAttribute]: - """获取实体的所有属性值""" conn = self.get_conn() rows = conn.execute( """SELECT ea.*, at.name as template_name, at.type as template_type FROM entity_attributes ea JOIN attribute_templates at ON ea.template_id = at.id - WHERE ea.entity_id = ? - ORDER BY at.display_order""", + WHERE ea.entity_id = ? ORDER BY at.display_order""", (entity_id,) ).fetchall() conn.close() - return [EntityAttribute(**dict(r)) for r in rows] def get_entity_with_attributes(self, entity_id: str) -> Optional[Entity]: - """获取实体详情,包含属性""" entity = self.get_entity(entity_id) if not entity: return None - - # 获取属性 attrs = self.get_entity_attributes(entity_id) entity.attributes = { - attr.template_name: { - 'value': attr.value, - 'type': attr.template_type, - 'template_id': attr.template_id - } + attr.template_name: {'value': attr.value, 'type': attr.template_type, 'template_id': attr.template_id} for attr in attrs } - return entity - def delete_entity_attribute(self, entity_id: str, template_id: str, - changed_by: str = "system", change_reason: str = ""): - """删除实体属性值""" + def delete_entity_attribute(self, entity_id: str, template_id: str, changed_by: str = "system", change_reason: str = ""): conn = self.get_conn() - - # 获取旧值 old_row = conn.execute( "SELECT value FROM entity_attributes WHERE entity_id = ? AND template_id = ?", (entity_id, template_id) ).fetchone() if old_row: - # 记录删除历史 conn.execute( """INSERT INTO attribute_history (id, entity_id, template_id, old_value, new_value, changed_by, changed_at, change_reason) VALUES (?, ?, ?, ?, ?, ?, ?, ?)""", (str(uuid.uuid4())[:8], entity_id, template_id, - old_row['value'], None, changed_by, datetime.now().isoformat(), - change_reason or "属性删除") + old_row['value'], None, changed_by, datetime.now().isoformat(), change_reason or "属性删除") ) - - # 删除属性 conn.execute( "DELETE FROM entity_attributes WHERE entity_id = ? AND template_id = ?", (entity_id, template_id) ) conn.commit() - conn.close() - # ---- 属性历史管理 ---- - - def get_attribute_history(self, entity_id: str = None, - template_id: str = None, - limit: int = 50) -> List[AttributeHistory]: - """获取属性变更历史""" + def get_attribute_history(self, entity_id: str = None, template_id: str = None, limit: int = 50) -> List[AttributeHistory]: conn = self.get_conn() - conditions = [] params = [] if entity_id: conditions.append("ah.entity_id = ?") params.append(entity_id) - if template_id: conditions.append("ah.template_id = ?") params.append(template_id) @@ -1413,31 +823,22 @@ class DatabaseManager: FROM attribute_history ah JOIN attribute_templates at ON ah.template_id = at.id WHERE {where_clause} - ORDER BY ah.changed_at DESC - LIMIT ?""", + ORDER BY ah.changed_at DESC LIMIT ?""", params + [limit] ).fetchall() conn.close() - return [AttributeHistory(**dict(r)) for r in rows] - def search_entities_by_attributes(self, project_id: str, - attribute_filters: Dict[str, str]) -> List[Entity]: - """根据属性筛选搜索实体""" - conn = self.get_conn() - - # 获取项目所有实体 + def search_entities_by_attributes(self, project_id: str, attribute_filters: Dict[str, str]) -> List[Entity]: entities = self.list_project_entities(project_id) - if not attribute_filters: return entities - # 获取所有实体的属性 entity_ids = [e.id for e in entities] if not entity_ids: return [] - # 构建查询条件 + conn = self.get_conn() placeholders = ','.join(['?' for _ in entity_ids]) rows = conn.execute( f"""SELECT ea.*, at.name as template_name @@ -1446,10 +847,8 @@ class DatabaseManager: WHERE ea.entity_id IN ({placeholders})""", entity_ids ).fetchall() - conn.close() - # 按实体ID分组属性 entity_attrs = {} for row in rows: eid = row['entity_id'] @@ -1457,7 +856,6 @@ class DatabaseManager: entity_attrs[eid] = {} entity_attrs[eid][row['template_name']] = row['value'] - # 过滤实体 filtered = [] for entity in entities: attrs = entity_attrs.get(entity.id, {}) @@ -1469,314 +867,12 @@ class DatabaseManager: if match: entity.attributes = attrs filtered.append(entity) - return filtered # Singleton instance _db_manager = None - -def get_db_manager() -> DatabaseManager: - global _db_manager - if _db_manager is None: - _db_manager = DatabaseManager() - return _db_manager - end = min(len(text), position + context_chars) - return text[start:end] - - # ==================== Phase 5: 实体属性管理 ==================== - - # ---- 属性模板管理 ---- - - def create_attribute_template(self, template: AttributeTemplate) -> AttributeTemplate: - """创建属性模板""" - conn = self.get_conn() - now = datetime.now().isoformat() - conn.execute( - """INSERT INTO attribute_templates - (id, project_id, name, type, options, default_value, description, is_required, display_order, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", - (template.id, template.project_id, template.name, template.type, - json.dumps(template.options) if template.options else None, - template.default_value, template.description, template.is_required, - template.display_order, now, now) - ) - conn.commit() - conn.close() - return template - - def get_attribute_template(self, template_id: str) -> Optional[AttributeTemplate]: - """获取属性模板""" - conn = self.get_conn() - row = conn.execute( - "SELECT * FROM attribute_templates WHERE id = ?", - (template_id,) - ).fetchone() - conn.close() - if row: - data = dict(row) - data['options'] = json.loads(data['options']) if data['options'] else [] - return AttributeTemplate(**data) - return None - - def list_attribute_templates(self, project_id: str) -> List[AttributeTemplate]: - """列出项目的所有属性模板""" - conn = self.get_conn() - rows = conn.execute( - """SELECT * FROM attribute_templates - WHERE project_id = ? - ORDER BY display_order, created_at""", - (project_id,) - ).fetchall() - conn.close() - - templates = [] - for row in rows: - data = dict(row) - data['options'] = json.loads(data['options']) if data['options'] else [] - templates.append(AttributeTemplate(**data)) - return templates - - def update_attribute_template(self, template_id: str, **kwargs) -> Optional[AttributeTemplate]: - """更新属性模板""" - conn = self.get_conn() - - allowed_fields = ['name', 'type', 'options', 'default_value', - 'description', 'is_required', 'display_order'] - updates = [] - values = [] - - for field in allowed_fields: - if field in kwargs: - updates.append(f"{field} = ?") - if field == 'options': - values.append(json.dumps(kwargs[field]) if kwargs[field] else None) - else: - values.append(kwargs[field]) - - if not updates: - conn.close() - return self.get_attribute_template(template_id) - - updates.append("updated_at = ?") - values.append(datetime.now().isoformat()) - values.append(template_id) - - query = f"UPDATE attribute_templates SET {', '.join(updates)} WHERE id = ?" - conn.execute(query, values) - conn.commit() - conn.close() - - return self.get_attribute_template(template_id) - - def delete_attribute_template(self, template_id: str): - """删除属性模板(会级联删除相关属性值)""" - conn = self.get_conn() - conn.execute("DELETE FROM attribute_templates WHERE id = ?", (template_id,)) - conn.commit() - conn.close() - - # ---- 实体属性值管理 ---- - - def set_entity_attribute(self, attr: EntityAttribute, - changed_by: str = "system", - change_reason: str = "") -> EntityAttribute: - """设置实体属性值,自动记录历史""" - conn = self.get_conn() - now = datetime.now().isoformat() - - # 获取旧值 - old_row = conn.execute( - "SELECT value FROM entity_attributes WHERE entity_id = ? AND template_id = ?", - (attr.entity_id, attr.template_id) - ).fetchone() - old_value = old_row['value'] if old_row else None - - # 记录变更历史 - if old_value != attr.value: - conn.execute( - """INSERT INTO attribute_history - (id, entity_id, template_id, old_value, new_value, changed_by, changed_at, change_reason) - VALUES (?, ?, ?, ?, ?, ?, ?, ?)""", - (str(uuid.uuid4())[:8], attr.entity_id, attr.template_id, - old_value, attr.value, changed_by, now, change_reason) - ) - - # 插入或更新属性值 - conn.execute( - """INSERT OR REPLACE INTO entity_attributes - (id, entity_id, template_id, value, created_at, updated_at) - VALUES ( - COALESCE((SELECT id FROM entity_attributes WHERE entity_id = ? AND template_id = ?), ?), - ?, ?, ?, - COALESCE((SELECT created_at FROM entity_attributes WHERE entity_id = ? AND template_id = ?), ?), - ? - )""", - (attr.entity_id, attr.template_id, attr.id, - attr.entity_id, attr.template_id, attr.value, - attr.entity_id, attr.template_id, now, now) - ) - - conn.commit() - conn.close() - return attr - - def get_entity_attributes(self, entity_id: str) -> List[EntityAttribute]: - """获取实体的所有属性值""" - conn = self.get_conn() - rows = conn.execute( - """SELECT ea.*, at.name as template_name, at.type as template_type - FROM entity_attributes ea - JOIN attribute_templates at ON ea.template_id = at.id - WHERE ea.entity_id = ? - ORDER BY at.display_order""", - (entity_id,) - ).fetchall() - conn.close() - - return [EntityAttribute(**dict(r)) for r in rows] - - def get_entity_with_attributes(self, entity_id: str) -> Optional[Entity]: - """获取实体详情,包含属性""" - entity = self.get_entity(entity_id) - if not entity: - return None - - # 获取属性 - attrs = self.get_entity_attributes(entity_id) - entity.attributes = { - attr.template_name: { - 'value': attr.value, - 'type': attr.template_type, - 'template_id': attr.template_id - } - for attr in attrs - } - - return entity - - def delete_entity_attribute(self, entity_id: str, template_id: str, - changed_by: str = "system", change_reason: str = ""): - """删除实体属性值""" - conn = self.get_conn() - - # 获取旧值 - old_row = conn.execute( - "SELECT value FROM entity_attributes WHERE entity_id = ? AND template_id = ?", - (entity_id, template_id) - ).fetchone() - - if old_row: - # 记录删除历史 - conn.execute( - """INSERT INTO attribute_history - (id, entity_id, template_id, old_value, new_value, changed_by, changed_at, change_reason) - VALUES (?, ?, ?, ?, ?, ?, ?, ?)""", - (str(uuid.uuid4())[:8], entity_id, template_id, - old_row['value'], None, changed_by, datetime.now().isoformat(), - change_reason or "属性删除") - ) - - # 删除属性 - conn.execute( - "DELETE FROM entity_attributes WHERE entity_id = ? AND template_id = ?", - (entity_id, template_id) - ) - conn.commit() - - conn.close() - - # ---- 属性历史管理 ---- - - def get_attribute_history(self, entity_id: str = None, - template_id: str = None, - limit: int = 50) -> List[AttributeHistory]: - """获取属性变更历史""" - conn = self.get_conn() - - conditions = [] - params = [] - - if entity_id: - conditions.append("ah.entity_id = ?") - params.append(entity_id) - - if template_id: - conditions.append("ah.template_id = ?") - params.append(template_id) - - where_clause = " AND ".join(conditions) if conditions else "1=1" - - rows = conn.execute( - f"""SELECT ah.*, at.name as template_name - FROM attribute_history ah - JOIN attribute_templates at ON ah.template_id = at.id - WHERE {where_clause} - ORDER BY ah.changed_at DESC - LIMIT ?""", - params + [limit] - ).fetchall() - conn.close() - - return [AttributeHistory(**dict(r)) for r in rows] - - def search_entities_by_attributes(self, project_id: str, - attribute_filters: Dict[str, str]) -> List[Entity]: - """根据属性筛选搜索实体""" - conn = self.get_conn() - - # 获取项目所有实体 - entities = self.list_project_entities(project_id) - - if not attribute_filters: - return entities - - # 获取所有实体的属性 - entity_ids = [e.id for e in entities] - if not entity_ids: - return [] - - # 构建查询条件 - placeholders = ','.join(['?' for _ in entity_ids]) - rows = conn.execute( - f"""SELECT ea.*, at.name as template_name - FROM entity_attributes ea - JOIN attribute_templates at ON ea.template_id = at.id - WHERE ea.entity_id IN ({placeholders})""", - entity_ids - ).fetchall() - - conn.close() - - # 按实体ID分组属性 - entity_attrs = {} - for row in rows: - eid = row['entity_id'] - if eid not in entity_attrs: - entity_attrs[eid] = {} - entity_attrs[eid][row['template_name']] = row['value'] - - # 过滤实体 - filtered = [] - for entity in entities: - attrs = entity_attrs.get(entity.id, {}) - match = True - for attr_name, attr_value in attribute_filters.items(): - if attrs.get(attr_name) != attr_value: - match = False - break - if match: - entity.attributes = attrs - filtered.append(entity) - - return filtered - - -# Singleton instance -_db_manager = None - - def get_db_manager() -> DatabaseManager: global _db_manager if _db_manager is None: