VietCat commited on
Commit
c3199eb
·
1 Parent(s): 906da16

add chunker

Browse files
app/law_document_chunker.py ADDED
@@ -0,0 +1,372 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ import os
3
+ import uuid
4
+ from typing import List, Dict, Optional, Tuple
5
+ from dataclasses import dataclass
6
+ from loguru import logger
7
+ from .supabase_db import SupabaseClient
8
+ from .embedding import EmbeddingClient
9
+ from .config import get_settings
10
+
11
+ @dataclass
12
+ class ChunkMetadata:
13
+ """Metadata cho một chunk."""
14
+ id: str
15
+ content: str
16
+ vanbanid: int
17
+ cha: Optional[str] = None
18
+ document_title: str = ""
19
+ article_number: Optional[int] = None
20
+ article_title: str = ""
21
+ clause_number: str = ""
22
+ sub_clause_letter: str = ""
23
+ context_summary: str = ""
24
+
25
+ class LawDocumentChunker:
26
+ """Module xử lý chunking văn bản luật và tích hợp với Supabase."""
27
+
28
+ def __init__(self):
29
+ """Khởi tạo chunker với các regex patterns."""
30
+ settings = get_settings()
31
+ self.supabase_client = SupabaseClient(settings.supabase_url, settings.supabase_key)
32
+ self.embedding_client = EmbeddingClient()
33
+
34
+ # Regex patterns cho các cấp độ cấu trúc
35
+ self.PHAN_REGEX = r"(Phần|PHẦN|Phần thứ)\s+(\d+|[IVXLCDM]+|nhất|hai|ba|tư|năm|sáu|bảy|tám|chín|mười)\.?\s*\n"
36
+ self.PHU_LUC_REGEX = r"(Phụ lục|PHỤ LỤC)\s+(\d+|[A-Z]+)\.?\s*\n"
37
+ self.CHUONG_REGEX = r"(Chương|CHƯƠNG)\s+(\d+|[IVXLCDM]+)\.?\s*.*\n"
38
+ self.MUC_REGEX = r"(Mục|MỤC)\s+\d+\.?\s*.*\n"
39
+ self.DIEU_REGEX = r"Điều\s+(\d+)\.\s*(.*)"
40
+ self.KHOAN_REGEX = r"^\s*(\d+(\.\d+)*)\.\s*(.*)"
41
+ self.DIEM_REGEX_A = r"^\s*([a-zđ])\)\s*(.*)"
42
+ self.DIEM_REGEX_NUM = r"^\s*(\d+\.\d+\.\d+)\.\s*(.*)"
43
+
44
+ # Cấu hình chunking
45
+ self.CHUNK_SIZE = 500
46
+ self.CHUNK_OVERLAP = 100
47
+
48
+ logger.info("[CHUNKER] Initialized LawDocumentChunker")
49
+
50
+ def _create_data_directory(self):
51
+ """Tạo thư mục data nếu chưa tồn tại."""
52
+ data_dir = "data"
53
+ if not os.path.exists(data_dir):
54
+ os.makedirs(data_dir)
55
+ logger.info(f"[CHUNKER] Created directory: {data_dir}")
56
+ return data_dir
57
+
58
+ def _extract_document_title(self, file_path: str) -> str:
59
+ """Trích xuất tiêu đề văn bản từ tên file."""
60
+ filename = os.path.basename(file_path)
61
+ # Loại bỏ extension
62
+ name_without_ext = os.path.splitext(filename)[0]
63
+ # Thay _ bằng khoảng trắng và viết hoa chữ cái đầu
64
+ title = name_without_ext.replace('_', ' ').title()
65
+ logger.info(f"[CHUNKER] Extracted document title: {title}")
66
+ return title
67
+
68
+ def _read_document(self, file_path: str) -> str:
69
+ """Đọc nội dung văn bản từ file."""
70
+ try:
71
+ with open(file_path, 'r', encoding='utf-8') as f:
72
+ content = f.read()
73
+ logger.info(f"[CHUNKER] Read document: {file_path}, length: {len(content)}")
74
+ return content
75
+ except Exception as e:
76
+ logger.error(f"[CHUNKER] Error reading file {file_path}: {e}")
77
+ raise
78
+
79
+ def _detect_structure_level(self, line: str) -> Tuple[str, Optional[str], Optional[str]]:
80
+ """Phát hiện cấp độ cấu trúc của một dòng."""
81
+ line = line.strip()
82
+
83
+ # Phần
84
+ match = re.match(self.PHAN_REGEX, line, re.IGNORECASE)
85
+ if match:
86
+ return "PHAN", match.group(1), match.group(2)
87
+
88
+ # Phụ lục
89
+ match = re.match(self.PHU_LUC_REGEX, line, re.IGNORECASE)
90
+ if match:
91
+ return "PHU_LUC", match.group(1), match.group(2)
92
+
93
+ # Chương
94
+ match = re.match(self.CHUONG_REGEX, line, re.IGNORECASE)
95
+ if match:
96
+ return "CHUONG", match.group(1), match.group(2)
97
+
98
+ # Mục
99
+ match = re.match(self.MUC_REGEX, line, re.IGNORECASE)
100
+ if match:
101
+ return "MUC", match.group(1), match.group(2)
102
+
103
+ # Điều
104
+ match = re.match(self.DIEU_REGEX, line)
105
+ if match:
106
+ return "DIEU", match.group(1), match.group(2)
107
+
108
+ # Khoản
109
+ match = re.match(self.KHOAN_REGEX, line)
110
+ if match:
111
+ clause_num = match.group(1)
112
+ # Kiểm tra không phải điểm (có từ 3 số trở lên)
113
+ if len(clause_num.split('.')) < 3:
114
+ return "KHOAN", clause_num, match.group(3)
115
+
116
+ # Điểm chữ cái
117
+ match = re.match(self.DIEM_REGEX_A, line)
118
+ if match:
119
+ return "DIEM", match.group(1), match.group(2)
120
+
121
+ # Điểm số
122
+ match = re.match(self.DIEM_REGEX_NUM, line)
123
+ if match:
124
+ return "DIEM", match.group(1), match.group(2)
125
+
126
+ return "CONTENT", None, None
127
+
128
+ def _create_chunk_metadata(self, content: str, level: str, level_value: Optional[str],
129
+ parent_id: Optional[str], vanbanid: int,
130
+ document_title: str) -> ChunkMetadata:
131
+ """Tạo metadata cho chunk."""
132
+ chunk_id = str(uuid.uuid4())
133
+
134
+ metadata = ChunkMetadata(
135
+ id=chunk_id,
136
+ content=content,
137
+ vanbanid=vanbanid,
138
+ cha=parent_id,
139
+ document_title=document_title
140
+ )
141
+
142
+ # Điền metadata theo cấp độ
143
+ if level == "DIEU" and level_value:
144
+ metadata.article_number = int(level_value) if level_value.isdigit() else None
145
+ metadata.article_title = content.strip()
146
+ elif level == "KHOAN" and level_value:
147
+ metadata.clause_number = level_value
148
+ elif level == "DIEM" and level_value:
149
+ metadata.sub_clause_letter = level_value
150
+
151
+ return metadata
152
+
153
+ def _split_into_chunks(self, text: str, chunk_size: int, overlap: int) -> List[str]:
154
+ """Chia text thành các chunk với overlap."""
155
+ chunks = []
156
+ start = 0
157
+
158
+ while start < len(text):
159
+ end = start + chunk_size
160
+ chunk = text[start:end]
161
+
162
+ # Tìm vị trí kết thúc chunk tốt nhất (cuối câu hoặc cuối từ)
163
+ if end < len(text):
164
+ # Tìm dấu chấm hoặc xuống dòng gần nhất
165
+ last_period = chunk.rfind('.')
166
+ last_newline = chunk.rfind('\n')
167
+ best_break = max(last_period, last_newline)
168
+
169
+ if best_break > start + chunk_size * 0.7: # Chỉ break nếu không quá sớm
170
+ end = start + best_break + 1
171
+ chunk = text[start:end]
172
+
173
+ chunks.append(chunk)
174
+ start = end - overlap
175
+
176
+ if start >= len(text):
177
+ break
178
+
179
+ return chunks
180
+
181
+ def _process_document_recursive(self, content: str, vanbanid: int,
182
+ document_title: str) -> List[ChunkMetadata]:
183
+ """Xử lý văn bản theo cấu trúc phân cấp."""
184
+ lines = content.split('\n')
185
+ chunks = []
186
+ parent_stack = [] # Stack để theo dõi parent IDs
187
+ current_parent = None
188
+
189
+ current_chunk_content = ""
190
+ current_level = "CONTENT"
191
+ current_level_value = None
192
+
193
+ for line in lines:
194
+ level, level_value, level_content = self._detect_structure_level(line)
195
+
196
+ # Nếu phát hiện cấp độ mới
197
+ if level != "CONTENT" and level_value:
198
+ # Lưu chunk hiện tại nếu có
199
+ if current_chunk_content.strip():
200
+ metadata = self._create_chunk_metadata(
201
+ current_chunk_content.strip(),
202
+ current_level,
203
+ current_level_value,
204
+ current_parent,
205
+ vanbanid,
206
+ document_title
207
+ )
208
+ chunks.append(metadata)
209
+
210
+ # Cập nhật parent stack
211
+ if level in ["PHAN", "PHU_LUC", "CHUONG", "MUC"]:
212
+ # Cấp độ cao, reset stack
213
+ parent_stack = [metadata.id]
214
+ current_parent = metadata.id
215
+ elif level == "DIEU":
216
+ # Điều thuộc về cấp độ cao nhất hiện tại
217
+ current_parent = parent_stack[-1] if parent_stack else None
218
+ parent_stack.append(metadata.id)
219
+ elif level in ["KHOAN", "DIEM"]:
220
+ # Khoản/Điểm thuộc về Điều hiện tại
221
+ current_parent = parent_stack[-1] if parent_stack else None
222
+
223
+ # Bắt đầu chunk mới
224
+ current_chunk_content = line + "\n"
225
+ current_level = level
226
+ current_level_value = level_value
227
+ else:
228
+ # Thêm vào chunk hiện tại
229
+ current_chunk_content += line + "\n"
230
+
231
+ # Kiểm tra nếu chunk quá lớn
232
+ if len(current_chunk_content) > self.CHUNK_SIZE:
233
+ # Chia chunk hiện tại
234
+ sub_chunks = self._split_into_chunks(current_chunk_content, self.CHUNK_SIZE, self.CHUNK_OVERLAP)
235
+
236
+ for i, sub_chunk in enumerate(sub_chunks):
237
+ metadata = self._create_chunk_metadata(
238
+ sub_chunk.strip(),
239
+ current_level,
240
+ current_level_value,
241
+ current_parent,
242
+ vanbanid,
243
+ document_title
244
+ )
245
+ chunks.append(metadata)
246
+
247
+ current_chunk_content = ""
248
+
249
+ # Lưu chunk cuối cùng
250
+ if current_chunk_content.strip():
251
+ metadata = self._create_chunk_metadata(
252
+ current_chunk_content.strip(),
253
+ current_level,
254
+ current_level_value,
255
+ current_parent,
256
+ vanbanid,
257
+ document_title
258
+ )
259
+ chunks.append(metadata)
260
+
261
+ logger.info(f"[CHUNKER] Created {len(chunks)} chunks from document")
262
+ return chunks
263
+
264
+ async def _create_embeddings_for_chunks(self, chunks: List[ChunkMetadata]) -> List[Dict]:
265
+ """Tạo embeddings cho các chunks."""
266
+ logger.info(f"[CHUNKER] Creating embeddings for {len(chunks)} chunks")
267
+
268
+ chunk_data = []
269
+ for chunk in chunks:
270
+ try:
271
+ # Tạo embedding
272
+ embedding = await self.embedding_client.create_embedding(chunk.content)
273
+
274
+ # Chuẩn bị data cho Supabase
275
+ chunk_dict = {
276
+ 'id': chunk.id,
277
+ 'content': chunk.content,
278
+ 'embedding': embedding,
279
+ 'vanbanid': chunk.vanbanid,
280
+ 'cha': chunk.cha,
281
+ 'document_title': chunk.document_title,
282
+ 'article_number': chunk.article_number,
283
+ 'article_title': chunk.article_title,
284
+ 'clause_number': chunk.clause_number,
285
+ 'sub_clause_letter': chunk.sub_clause_letter,
286
+ 'context_summary': chunk.context_summary
287
+ }
288
+ chunk_data.append(chunk_dict)
289
+
290
+ logger.debug(f"[CHUNKER] Created embedding for chunk {chunk.id[:8]}...")
291
+
292
+ except Exception as e:
293
+ logger.error(f"[CHUNKER] Error creating embedding for chunk {chunk.id}: {e}")
294
+ continue
295
+
296
+ logger.info(f"[CHUNKER] Successfully created embeddings for {len(chunk_data)} chunks")
297
+ return chunk_data
298
+
299
+ async def _store_chunks_to_supabase(self, chunk_data: List[Dict]) -> bool:
300
+ """Lưu chunks vào Supabase."""
301
+ try:
302
+ logger.info(f"[CHUNKER] Storing {len(chunk_data)} chunks to Supabase")
303
+
304
+ # Lưu từng chunk
305
+ for chunk in chunk_data:
306
+ success = self.supabase_client.store_document_chunk(chunk)
307
+ if not success:
308
+ logger.error(f"[CHUNKER] Failed to store chunk {chunk['id']}")
309
+ return False
310
+
311
+ logger.info(f"[CHUNKER] Successfully stored all chunks to Supabase")
312
+ return True
313
+
314
+ except Exception as e:
315
+ logger.error(f"[CHUNKER] Error storing chunks to Supabase: {e}")
316
+ return False
317
+
318
+ async def process_law_document(self, file_path: str, document_id: int) -> bool:
319
+ """
320
+ Hàm chính để xử lý văn bản luật.
321
+
322
+ Args:
323
+ file_path: Đường dẫn đến file văn bản luật
324
+ document_id: ID duy nhất của văn bản luật
325
+
326
+ Returns:
327
+ bool: True nếu thành công, False nếu thất bại
328
+ """
329
+ try:
330
+ logger.info(f"[CHUNKER] Starting processing for file: {file_path}, document_id: {document_id}")
331
+
332
+ # 1. Tạo thư mục data nếu cần
333
+ self._create_data_directory()
334
+
335
+ # 2. Kiểm tra file tồn tại
336
+ if not os.path.exists(file_path):
337
+ logger.error(f"[CHUNKER] File not found: {file_path}")
338
+ return False
339
+
340
+ # 3. Đọc văn bản
341
+ content = self._read_document(file_path)
342
+
343
+ # 4. Trích xuất tiêu đề
344
+ document_title = self._extract_document_title(file_path)
345
+
346
+ # 5. Xử lý chunking theo cấu trúc
347
+ chunks = self._process_document_recursive(content, document_id, document_title)
348
+
349
+ if not chunks:
350
+ logger.warning(f"[CHUNKER] No chunks created for document {document_id}")
351
+ return False
352
+
353
+ # 6. Tạo embeddings
354
+ chunk_data = await self._create_embeddings_for_chunks(chunks)
355
+
356
+ if not chunk_data:
357
+ logger.error(f"[CHUNKER] No embeddings created for document {document_id}")
358
+ return False
359
+
360
+ # 7. Lưu vào Supabase
361
+ success = await self._store_chunks_to_supabase(chunk_data)
362
+
363
+ if success:
364
+ logger.info(f"[CHUNKER] Successfully processed document {document_id} with {len(chunk_data)} chunks")
365
+ else:
366
+ logger.error(f"[CHUNKER] Failed to store chunks for document {document_id}")
367
+
368
+ return success
369
+
370
+ except Exception as e:
371
+ logger.error(f"[CHUNKER] Error processing document {document_id}: {e}")
372
+ return False
app/main.py CHANGED
@@ -20,6 +20,7 @@ from .health import router as health_router
20
  from .llm import create_llm_client
21
  from .reranker import Reranker
22
  from .request_limit_manager import RequestLimitManager
 
23
 
24
  app = FastAPI(title="WeBot Facebook Messenger API")
25
 
@@ -74,6 +75,9 @@ llm_client = create_llm_client(
74
 
75
  reranker = Reranker()
76
 
 
 
 
77
  logger.info("[STARTUP] Mount health router...")
78
  app.include_router(health_router)
79
 
@@ -526,6 +530,200 @@ async def create_facebook_post(page_token: str, sender_id: str, history: List[Di
526
  logger.info(f"[MOCK] Creating Facebook post for sender_id={sender_id} with history={history}")
527
  return "https://facebook.com/mock_post_url"
528
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
529
  if __name__ == "__main__":
530
  import uvicorn
531
  logger.info("[STARTUP] Bắt đầu chạy uvicorn server...")
 
20
  from .llm import create_llm_client
21
  from .reranker import Reranker
22
  from .request_limit_manager import RequestLimitManager
23
+ from .law_document_chunker import LawDocumentChunker
24
 
25
  app = FastAPI(title="WeBot Facebook Messenger API")
26
 
 
75
 
76
  reranker = Reranker()
77
 
78
+ # Khởi tạo LawDocumentChunker
79
+ law_chunker = LawDocumentChunker()
80
+
81
  logger.info("[STARTUP] Mount health router...")
82
  app.include_router(health_router)
83
 
 
530
  logger.info(f"[MOCK] Creating Facebook post for sender_id={sender_id} with history={history}")
531
  return "https://facebook.com/mock_post_url"
532
 
533
+ # ==================== DOCUMENT CHUNK MANAGEMENT APIs ====================
534
+
535
+ @app.delete("/api/document-chunks/clear")
536
+ @timing_decorator_async
537
+ async def delete_all_document_chunks():
538
+ """
539
+ API xóa toàn bộ bảng document_chunks.
540
+ """
541
+ try:
542
+ logger.info("[API] Starting delete all document chunks")
543
+ success = supabase_client.delete_all_document_chunks()
544
+
545
+ if success:
546
+ logger.info("[API] Successfully deleted all document chunks")
547
+ return {"status": "success", "message": "Đã xóa toàn bộ document chunks"}
548
+ else:
549
+ logger.error("[API] Failed to delete all document chunks")
550
+ raise HTTPException(status_code=500, detail="Lỗi khi xóa document chunks")
551
+
552
+ except Exception as e:
553
+ logger.error(f"[API] Error in delete_all_document_chunks: {e}")
554
+ raise HTTPException(status_code=500, detail=f"Lỗi: {str(e)}")
555
+
556
+ @app.post("/api/document-chunks/update")
557
+ @timing_decorator_async
558
+ async def update_specific_document(file_name: str, document_id: int):
559
+ """
560
+ API cập nhật file xác định trong thư mục data.
561
+
562
+ Args:
563
+ file_name: Tên file trong thư mục data (ví dụ: "luat_giao_thong.txt")
564
+ document_id: ID văn bản luật
565
+ """
566
+ try:
567
+ logger.info(f"[API] Starting update specific document: {file_name}, document_id: {document_id}")
568
+
569
+ # Kiểm tra file tồn tại
570
+ file_path = f"data/{file_name}"
571
+ if not os.path.exists(file_path):
572
+ logger.error(f"[API] File not found: {file_path}")
573
+ raise HTTPException(status_code=404, detail=f"File không tồn tại: {file_name}")
574
+
575
+ # Xóa chunks cũ của document_id này (nếu có)
576
+ logger.info(f"[API] Deleting old chunks for document_id: {document_id}")
577
+ supabase_client.delete_document_chunks_by_vanbanid(document_id)
578
+
579
+ # Xử lý văn bản mới
580
+ logger.info(f"[API] Processing document: {file_path}")
581
+ success = await law_chunker.process_law_document(file_path, document_id)
582
+
583
+ if success:
584
+ logger.info(f"[API] Successfully updated document: {file_name}")
585
+ return {
586
+ "status": "success",
587
+ "message": f"Đã cập nhật thành công văn bản: {file_name}",
588
+ "document_id": document_id,
589
+ "file_name": file_name
590
+ }
591
+ else:
592
+ logger.error(f"[API] Failed to update document: {file_name}")
593
+ raise HTTPException(status_code=500, detail=f"Lỗi khi xử lý văn bản: {file_name}")
594
+
595
+ except HTTPException:
596
+ raise
597
+ except Exception as e:
598
+ logger.error(f"[API] Error in update_specific_document: {e}")
599
+ raise HTTPException(status_code=500, detail=f"Lỗi: {str(e)}")
600
+
601
+ @app.post("/api/document-chunks/update-all")
602
+ @timing_decorator_async
603
+ async def update_all_documents():
604
+ """
605
+ API cập nhật tự động toàn bộ file trong thư mục data.
606
+ """
607
+ try:
608
+ logger.info("[API] Starting update all documents")
609
+
610
+ # Kiểm tra thư mục data tồn tại
611
+ data_dir = "data"
612
+ if not os.path.exists(data_dir):
613
+ logger.warning(f"[API] Data directory not found: {data_dir}")
614
+ return {
615
+ "status": "warning",
616
+ "message": "Thư mục data không tồn tại",
617
+ "processed_files": [],
618
+ "failed_files": []
619
+ }
620
+
621
+ # Lấy danh sách file .txt trong thư mục data
622
+ txt_files = [f for f in os.listdir(data_dir) if f.endswith('.txt')]
623
+
624
+ if not txt_files:
625
+ logger.warning("[API] No .txt files found in data directory")
626
+ return {
627
+ "status": "warning",
628
+ "message": "Không tìm thấy file .txt nào trong thư mục data",
629
+ "processed_files": [],
630
+ "failed_files": []
631
+ }
632
+
633
+ logger.info(f"[API] Found {len(txt_files)} .txt files to process")
634
+
635
+ processed_files = []
636
+ failed_files = []
637
+
638
+ # Xử lý từng file
639
+ for i, file_name in enumerate(txt_files, 1):
640
+ try:
641
+ logger.info(f"[API] Processing file {i}/{len(txt_files)}: {file_name}")
642
+
643
+ # Sử dụng index làm document_id (có thể thay đổi logic này)
644
+ document_id = i
645
+
646
+ # Xóa chunks cũ của document_id này (nếu có)
647
+ supabase_client.delete_document_chunks_by_vanbanid(document_id)
648
+
649
+ # Xử lý văn bản
650
+ file_path = os.path.join(data_dir, file_name)
651
+ success = await law_chunker.process_law_document(file_path, document_id)
652
+
653
+ if success:
654
+ processed_files.append({
655
+ "file_name": file_name,
656
+ "document_id": document_id,
657
+ "status": "success"
658
+ })
659
+ logger.info(f"[API] Successfully processed: {file_name}")
660
+ else:
661
+ failed_files.append({
662
+ "file_name": file_name,
663
+ "document_id": document_id,
664
+ "status": "failed",
665
+ "error": "Processing failed"
666
+ })
667
+ logger.error(f"[API] Failed to process: {file_name}")
668
+
669
+ except Exception as e:
670
+ logger.error(f"[API] Error processing {file_name}: {e}")
671
+ failed_files.append({
672
+ "file_name": file_name,
673
+ "document_id": i,
674
+ "status": "failed",
675
+ "error": str(e)
676
+ })
677
+
678
+ # Tổng kết
679
+ total_files = len(txt_files)
680
+ success_count = len(processed_files)
681
+ failed_count = len(failed_files)
682
+
683
+ logger.info(f"[API] Update all completed: {success_count}/{total_files} files processed successfully")
684
+
685
+ return {
686
+ "status": "success",
687
+ "message": f"Đã xử lý {success_count}/{total_files} files thành công",
688
+ "total_files": total_files,
689
+ "processed_files": processed_files,
690
+ "failed_files": failed_files
691
+ }
692
+
693
+ except Exception as e:
694
+ logger.error(f"[API] Error in update_all_documents: {e}")
695
+ raise HTTPException(status_code=500, detail=f"Lỗi: {str(e)}")
696
+
697
+ @app.get("/api/document-chunks/status")
698
+ @timing_decorator_async
699
+ async def get_document_chunks_status():
700
+ """
701
+ API lấy thông tin trạng thái của document chunks.
702
+ """
703
+ try:
704
+ logger.info("[API] Getting document chunks status")
705
+
706
+ # Lấy thống kê từ Supabase
707
+ # Note: Cần implement method này trong SupabaseClient nếu cần
708
+
709
+ # Kiểm tra thư mục data
710
+ data_dir = "data"
711
+ txt_files = []
712
+ if os.path.exists(data_dir):
713
+ txt_files = [f for f in os.listdir(data_dir) if f.endswith('.txt')]
714
+
715
+ return {
716
+ "status": "success",
717
+ "data_directory": data_dir,
718
+ "available_files": txt_files,
719
+ "file_count": len(txt_files),
720
+ "message": f"Tìm thấy {len(txt_files)} file .txt trong thư mục data"
721
+ }
722
+
723
+ except Exception as e:
724
+ logger.error(f"[API] Error in get_document_chunks_status: {e}")
725
+ raise HTTPException(status_code=500, detail=f"Lỗi: {str(e)}")
726
+
727
  if __name__ == "__main__":
728
  import uvicorn
729
  logger.info("[STARTUP] Bắt đầu chạy uvicorn server...")
app/supabase_db.py CHANGED
@@ -76,4 +76,71 @@ class SupabaseClient:
76
  return bool(response.data)
77
  except Exception as e:
78
  logger.error(f"Error storing embedding: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  return False
 
76
  return bool(response.data)
77
  except Exception as e:
78
  logger.error(f"Error storing embedding: {e}")
79
+ return False
80
+
81
+ @timing_decorator_sync
82
+ def store_document_chunk(self, chunk_data: Dict[str, Any]) -> bool:
83
+ """
84
+ Lưu document chunk vào Supabase.
85
+ Input: chunk_data (dict) - chứa tất cả thông tin chunk
86
+ Output: bool (True nếu thành công, False nếu lỗi)
87
+ """
88
+ try:
89
+ response = self.client.table('document_chunks').insert(chunk_data).execute()
90
+
91
+ if response.data:
92
+ logger.info(f"Successfully stored chunk {chunk_data.get('id', 'unknown')}")
93
+ return True
94
+ else:
95
+ logger.error(f"Failed to store chunk {chunk_data.get('id', 'unknown')}")
96
+ return False
97
+
98
+ except Exception as e:
99
+ logger.error(f"Error storing document chunk: {e}")
100
+ return False
101
+
102
+ @timing_decorator_sync
103
+ def delete_all_document_chunks(self) -> bool:
104
+ """
105
+ Xóa toàn bộ bảng document_chunks.
106
+ Output: bool (True nếu thành công, False nếu lỗi)
107
+ """
108
+ try:
109
+ response = self.client.table('document_chunks').delete().neq('id', '').execute()
110
+ logger.info(f"Successfully deleted all document chunks")
111
+ return True
112
+ except Exception as e:
113
+ logger.error(f"Error deleting all document chunks: {e}")
114
+ return False
115
+
116
+ @timing_decorator_sync
117
+ def get_document_chunks_by_vanbanid(self, vanbanid: int) -> List[Dict[str, Any]]:
118
+ """
119
+ Lấy tất cả chunks của một văn bản theo vanbanid.
120
+ Input: vanbanid (int)
121
+ Output: List[Dict] - danh sách chunks
122
+ """
123
+ try:
124
+ response = self.client.table('document_chunks').select('*').eq('vanbanid', vanbanid).execute()
125
+ if response.data:
126
+ logger.info(f"Found {len(response.data)} chunks for vanbanid {vanbanid}")
127
+ return response.data
128
+ return []
129
+ except Exception as e:
130
+ logger.error(f"Error getting document chunks for vanbanid {vanbanid}: {e}")
131
+ return []
132
+
133
+ @timing_decorator_sync
134
+ def delete_document_chunks_by_vanbanid(self, vanbanid: int) -> bool:
135
+ """
136
+ Xóa tất cả chunks của một văn bản theo vanbanid.
137
+ Input: vanbanid (int)
138
+ Output: bool (True nếu thành công, False nếu lỗi)
139
+ """
140
+ try:
141
+ response = self.client.table('document_chunks').delete().eq('vanbanid', vanbanid).execute()
142
+ logger.info(f"Successfully deleted all chunks for vanbanid {vanbanid}")
143
+ return True
144
+ except Exception as e:
145
+ logger.error(f"Error deleting chunks for vanbanid {vanbanid}: {e}")
146
  return False
data/ND168-2024.txt ADDED
The diff for this file is too large to render. See raw diff