Spaces:
Sleeping
Sleeping
File size: 9,745 Bytes
a85d6bf 5884230 a85d6bf 5884230 451cdc6 0067c9d 6370d73 c697463 0067c9d c697463 6370d73 5884230 6370d73 451cdc6 6370d73 f9e7c0c 451cdc6 6370d73 5884230 0067c9d 6370d73 5884230 6370d73 0067c9d 6370d73 451cdc6 6370d73 c697463 6370d73 5884230 451cdc6 5884230 90e6b4c aa38fcf 451cdc6 aa38fcf 6370d73 aa38fcf 6370d73 aa38fcf a85d6bf 451cdc6 a85d6bf 451cdc6 a85d6bf 6370d73 a85d6bf aa38fcf a85d6bf 6370d73 aa38fcf a85d6bf 6370d73 a85d6bf 90e6b4c a85d6bf 6370d73 a85d6bf 90e6b4c 451cdc6 aa38fcf a85d6bf 90e6b4c 451cdc6 6370d73 90e6b4c 822ef8c 90e6b4c a85d6bf 90e6b4c 822ef8c f9e7c0c 451cdc6 6370d73 f9e7c0c 451cdc6 f9e7c0c 451cdc6 f9e7c0c 451cdc6 f9e7c0c 451cdc6 f9e7c0c 451cdc6 f9e7c0c 451cdc6 f9e7c0c 451cdc6 f9e7c0c 451cdc6 f9e7c0c 451cdc6 f9e7c0c 451cdc6 f9e7c0c 451cdc6 f9e7c0c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 | from llama_index.core.text_splitter import SentenceSplitter
from llama_index.core import Document
from config import CHUNK_SIZE, CHUNK_OVERLAP
from my_logging import log_message
def normalize_table_number(table_num, section):
"""Normalize table numbers for consistent retrieval"""
if not table_num or table_num == 'Неизвестно':
return 'Неизвестно'
# Clean up common prefixes
tn = str(table_num).replace('Таблица', '').replace('№', '').strip()
# Add section context for appendix tables
if section and ('Приложение' in str(section) or 'приложение' in str(section).lower()):
return f"№{tn} ({section})"
return f"№{tn}"
def create_table_content(table_data):
"""Create formatted content optimized for semantic search"""
doc_id = (
table_data.get('document_id') or
table_data.get('document') or
table_data.get('Обозначение документа') or
'Неизвестно'
)
table_num = table_data.get('table_number', 'Неизвестно')
table_title = table_data.get('table_title', 'Неизвестно')
section = (
table_data.get('section') or
table_data.get('Раздел документа') or
'Неизвестно'
)
sheet_name = table_data.get('sheet_name', '')
# Enhanced table number with appendix context
normalized_num = normalize_table_number(table_num, section)
if 'Приложени' in str(section):
# Extract appendix number
import re
appendix_match = re.search(r'Приложени[ея]\s*(\d+)', str(section))
if appendix_match:
appendix_num = appendix_match.group(1)
normalized_num = f"{normalized_num} Приложения {appendix_num}"
# Build searchable header
content = f"Документ: {doc_id}\n"
content += f"Раздел: {section}\n"
content += f"Таблица: {normalized_num}\n"
content += f"Название: {table_title}\n"
if sheet_name:
content += f"Лист: {sheet_name}\n"
content += f"\n"
headers = table_data.get('headers', [])
if headers:
header_str = ' | '.join(str(h) for h in headers)
content += f"Колонки: {header_str}\n\n"
# CRITICAL: Preserve searchable row identifiers
if 'data' in table_data and isinstance(table_data['data'], list):
for row_idx, row in enumerate(table_data['data'], start=1):
if isinstance(row, dict):
# Extract ALL key-value pairs naturally
row_parts = []
for k, v in row.items():
if v and str(v).strip() and str(v) != 'nan':
row_parts.append(f"{k}: {v}")
if row_parts:
content += ' | '.join(row_parts) + "\n"
elif isinstance(row, list):
row_str = ' | '.join([str(v) for v in row if v and str(v).strip() and str(v) != 'nan'])
if row_str:
content += row_str + "\n"
return content, normalized_num
def chunk_table_document(doc, chunk_size=None, chunk_overlap=None):
if chunk_size is None:
chunk_size = CHUNK_SIZE
if chunk_overlap is None:
chunk_overlap = CHUNK_OVERLAP
table_num = doc.metadata.get('table_number', 'unknown')
doc_id = doc.metadata.get('document_id', 'unknown')
section = doc.metadata.get('section', 'Неизвестно')
full_table_id = f"{doc_id} | {section} | {table_num}"
lines = doc.text.strip().split('\n')
# Find where data rows start
data_start_idx = 0
for i, line in enumerate(lines):
if line.startswith('Колонки:'):
data_start_idx = i + 2 # Skip header and blank line
break
table_header = '\n'.join(lines[:data_start_idx])
data_rows = lines[data_start_idx:]
if not data_rows or len(doc.text) < chunk_size * 1.5:
log_message(f" 📊 {full_table_id}: малая таблица, без разбиения")
return [doc]
log_message(f" 📋 {full_table_id}: {len(data_rows)} строк → chunking")
header_size = len(table_header)
available_size = chunk_size - header_size - 100
text_chunks = []
current_chunk_rows = []
current_size = 0
for row in data_rows:
row_size = len(row) + 1
if current_size + row_size > available_size and current_chunk_rows:
chunk_text = table_header + '\n' + '\n'.join(current_chunk_rows)
text_chunks.append(chunk_text)
# Keep last 2 rows for overlap
overlap_count = min(2, len(current_chunk_rows))
current_chunk_rows = current_chunk_rows[-overlap_count:]
current_size = sum(len(r) + 1 for r in current_chunk_rows)
current_chunk_rows.append(row)
current_size += row_size
if current_chunk_rows:
chunk_text = table_header + '\n' + '\n'.join(current_chunk_rows)
text_chunks.append(chunk_text)
log_message(f" ✂️ {full_table_id} → {len(text_chunks)} чанков")
chunked_docs = []
for i, chunk_text in enumerate(text_chunks):
chunk_metadata = doc.metadata.copy()
chunk_metadata.update({
"chunk_id": i,
"total_chunks": len(text_chunks),
"chunk_size": len(chunk_text),
"is_chunked": True,
"full_table_id": full_table_id,
"table_number_normalized": doc.metadata.get('table_number_normalized')
})
chunked_doc = Document(
text=chunk_text,
metadata=chunk_metadata
)
chunked_docs.append(chunked_doc)
return chunked_docs
def table_to_document(table_data, document_id=None):
"""Convert table data to Document with complete metadata"""
if not isinstance(table_data, dict):
return []
sheet_doc_id = (
table_data.get('document_id') or
table_data.get('document') or
table_data.get('Обозначение документа')
)
doc_id = sheet_doc_id or document_id or 'Неизвестно'
table_num = table_data.get('table_number', 'Неизвестно')
table_title = table_data.get('table_title', 'Неизвестно')
section = table_data.get('section', table_data.get('Раздел документа', 'Неизвестно'))
sheet_name = table_data.get('sheet_name', '')
table_rows = table_data.get('data', [])
if not table_rows:
log_message(f"⚠️ Таблица {table_num} ({doc_id}) пропущена: нет данных")
return []
content, normalized_num = create_table_content(table_data)
content_size = len(content)
base_doc = Document(
text=content,
metadata={
"type": "table",
"table_number": table_num,
"table_number_normalized": normalized_num,
"table_title": table_title,
"document_id": doc_id,
"section": section,
"section_id": section,
"sheet_name": sheet_name,
"total_rows": len(table_rows),
"content_size": content_size,
"full_table_id": f"{doc_id} | {section} | {normalized_num}"
}
)
if content_size > CHUNK_SIZE:
log_message(f"📊 CHUNKING: {doc_id} | {normalized_num} | {content_size} > {CHUNK_SIZE}")
return chunk_table_document(base_doc)
else:
log_message(f"✓ {doc_id} | {normalized_num} ({content_size} символов)")
return [base_doc]
def table_to_document(table_data, document_id=None):
"""Convert table data to Document with proper metadata"""
if not isinstance(table_data, dict):
return []
# FIXED: Extract sheet-level document_id first
sheet_doc_id = (
table_data.get('document_id') or
table_data.get('document') or
table_data.get('Обозначение документа')
)
# Use sheet doc_id if available, otherwise use passed document_id
doc_id = sheet_doc_id or document_id or 'Неизвестно'
table_num = table_data.get('table_number', 'Неизвестно')
table_title = table_data.get('table_title', 'Неизвестно')
section = table_data.get('section', table_data.get('Раздел документа', 'Неизвестно'))
table_rows = table_data.get('data', [])
if not table_rows:
log_message(f"⚠️ Таблица {table_num} ({doc_id}) пропущена: нет данных")
return []
content, normalized_num = create_table_content(table_data)
content_size = len(content)
base_doc = Document(
text=content,
metadata={
"type": "table",
"table_number": table_num,
"table_number_normalized": normalized_num,
"table_title": table_title,
"document_id": doc_id,
"section": section,
"section_id": section,
"total_rows": len(table_rows),
"content_size": content_size,
"full_table_id": f"{doc_id} | {section} | {normalized_num}"
}
)
if content_size > CHUNK_SIZE:
log_message(f"📊 CHUNKING: {doc_id} | {normalized_num} | {content_size} > {CHUNK_SIZE}")
return chunk_table_document(base_doc)
else:
log_message(f"✓ {doc_id} | {normalized_num} ({content_size} символов)")
return [base_doc] |