1515sys .path .append (str (Path (__file__ ).parent .parent ))
1616
1717from vector_indexer .config .config_loader import ConfigLoader
18- from vector_indexer .document_loader import DocumentLoader
18+ from vector_indexer .document_loader import DocumentLoader , DocumentLoadError
1919from vector_indexer .contextual_processor import ContextualProcessor
2020from vector_indexer .qdrant_manager import QdrantManager
2121from vector_indexer .error_logger import ErrorLogger
@@ -169,7 +169,7 @@ async def process_all_documents(self) -> ProcessingStats:
169169
170170 # Process documents with controlled concurrency
171171 semaphore = asyncio .Semaphore (self .config .max_concurrent_documents )
172- tasks : List [asyncio .Task [tuple [int , str ]]] = []
172+ tasks : List [asyncio .Task [tuple [int , str , int ]]] = []
173173
174174 for doc_info in documents :
175175 task = asyncio .create_task (
@@ -189,6 +189,9 @@ async def process_all_documents(self) -> ProcessingStats:
189189 chunks_info : Dict [
190190 str , Dict [str , Any ]
191191 ] = {} # Track chunk counts for metadata update
192+ # Only documents that processed successfully are marked as
193+ # processed in DVC tracking, so failures are retried next run.
194+ processed_documents : List [DocumentInfo ] = []
192195 for i , result in enumerate (results ):
193196 if isinstance (result , Exception ):
194197 doc_info = documents [i ]
@@ -200,16 +203,18 @@ async def process_all_documents(self) -> ProcessingStats:
200203 doc_info .document_hash , str (result )
201204 )
202205 else :
203- # Result should be tuple of (chunk_count, content_hash)
206+ # Result should be tuple of (chunk_count, content_hash, failed_chunks )
204207 doc_info = documents [i ]
205208 self .stats .documents_processed += 1
206- if isinstance (result , tuple ) and len (result ) == 2 :
207- chunk_count , content_hash = result
209+ processed_documents .append (doc_info )
210+ if isinstance (result , tuple ) and len (result ) == 3 :
211+ chunk_count , content_hash , failed_chunks = result
208212 self .stats .total_chunks_processed += chunk_count
213+ self .stats .total_chunks_failed += failed_chunks
209214 # Track chunk count using content_hash (not directory hash)
210215 chunks_info [content_hash ] = {"chunk_count" : chunk_count }
211216 logger .info (
212- f"CHUNK COUNT: Document { doc_info .document_hash [:12 ]} ... (content: { content_hash [:12 ]} ...) -> { chunk_count } chunks"
217+ f"CHUNK COUNT: Document { doc_info .document_hash [:12 ]} ... (content: { content_hash [:12 ]} ...) -> { chunk_count } chunks ( { failed_chunks } failed) "
213218 )
214219
215220 # Log the complete chunks_info dictionary
@@ -227,10 +232,10 @@ async def process_all_documents(self) -> ProcessingStats:
227232 # Step 4: Update processed files tracking (even if no new documents processed)
228233 if diff_detector :
229234 try :
230- # Update metadata for newly processed files
231- if documents :
235+ # Update metadata for newly processed files (successful only)
236+ if processed_documents :
232237 processed_paths = [
233- doc .cleaned_txt_path for doc in documents
238+ doc .cleaned_txt_path for doc in processed_documents
234239 ]
235240 if processed_paths :
236241 logger .debug (
@@ -290,7 +295,7 @@ async def _process_single_document(
290295 doc_info : DocumentInfo ,
291296 qdrant_manager : QdrantManager ,
292297 semaphore : asyncio .Semaphore ,
293- ) -> tuple [int , str ]:
298+ ) -> tuple [int , str , int ]:
294299 """
295300 Process a single document with contextual retrieval.
296301
@@ -300,7 +305,9 @@ async def _process_single_document(
300305 semaphore: Concurrency control semaphore
301306
302307 Returns:
303- tuple: (chunk_count: int, content_hash: str) or Exception on error
308+ tuple: (chunk_count: int, content_hash: str, failed_chunks: int).
309+ Raises on any failure (including load failure or zero usable chunks),
310+ so the document is counted as failed rather than as success.
304311 """
305312 async with semaphore :
306313 logger .info (f"Processing document: { doc_info .document_hash } " )
@@ -310,29 +317,31 @@ async def _process_single_document(
310317 document = self .document_loader .load_document (doc_info )
311318
312319 if not document :
313- logger .warning (f"Could not load document: { doc_info .document_hash } " )
314- return (0 , doc_info .document_hash )
320+ raise DocumentLoadError (
321+ f"Could not load document: { doc_info .document_hash } "
322+ )
315323
316324 # Process document with contextual retrieval
317- contextual_chunks = await self .contextual_processor .process_document (
318- document
319- )
325+ (
326+ contextual_chunks ,
327+ failed_chunks ,
328+ ) = await self .contextual_processor .process_document (document )
320329
321330 if not contextual_chunks :
322- logger .warning (
323- f"No chunks created for document: { doc_info .document_hash } "
331+ raise RuntimeError (
332+ f"No chunks created for document: { doc_info .document_hash } "
333+ f"({ failed_chunks } chunks failed context generation)"
324334 )
325- return (0 , document .document_hash )
326335
327336 # Store chunks in Qdrant
328337 await qdrant_manager .store_chunks (contextual_chunks )
329338
330339 logger .info (
331340 f"Successfully processed document { doc_info .document_hash } : "
332- f"{ len (contextual_chunks )} chunks"
341+ f"{ len (contextual_chunks )} chunks ( { failed_chunks } dropped) "
333342 )
334343
335- return (len (contextual_chunks ), document .document_hash )
344+ return (len (contextual_chunks ), document .document_hash , failed_chunks )
336345
337346 except Exception as e :
338347 logger .error (f"Error processing document { doc_info .document_hash } : { e } " )
@@ -352,10 +361,12 @@ def _log_final_summary(self) -> None:
352361 logger .info (f" • Failed Chunks: { self .stats .total_chunks_failed } " )
353362
354363 if self .stats .total_documents > 0 :
355- success_rate = (
356- self .stats .documents_processed / self .stats .total_documents
357- ) * 100
358- logger .info (f"Success Rate: { success_rate :.1f} %" )
364+ logger .info (f"Success Rate: { self .stats .success_rate * 100 :.1f} %" )
365+
366+ if self .stats .total_chunks_processed + self .stats .total_chunks_failed > 0 :
367+ logger .info (
368+ f"Chunk Success Rate: { self .stats .chunk_success_rate * 100 :.1f} %"
369+ )
359370
360371 logger .info (f"Processing Duration: { self .stats .duration } " )
361372
@@ -365,6 +376,11 @@ def _log_final_summary(self) -> None:
365376 )
366377 logger .info ("Check failure logs for details" )
367378
379+ if self .stats .total_chunks_failed > 0 :
380+ logger .warning (
381+ f" { self .stats .total_chunks_failed } chunks failed processing"
382+ )
383+
368384 async def run_health_check (self ) -> bool :
369385 """
370386 Run health check on all components.
@@ -617,12 +633,20 @@ async def _execute_cleanup_operations(
617633 return total_deleted
618634
619635 def _cleanup_datasets (self ) -> None :
620- """Remove datasets folder after processing."""
636+ """Remove datasets folder contents after processing.
637+
638+ Only the folder's contents are removed, not the folder itself, since
639+ the datasets path is a mounted volume in the container.
640+ """
621641 try :
622642 datasets_path = Path (self .config .dataset_base_path )
623643 if datasets_path .exists ():
624- shutil .rmtree (str (datasets_path ))
625- logger .info (f"Datasets folder cleaned up: { datasets_path } " )
644+ for child in datasets_path .iterdir ():
645+ if child .is_dir ():
646+ shutil .rmtree (str (child ))
647+ else :
648+ child .unlink ()
649+ logger .info (f"Datasets folder contents cleaned up: { datasets_path } " )
626650 else :
627651 logger .debug (f"Datasets folder does not exist: { datasets_path } " )
628652 except Exception as e :
0 commit comments