shak3008 commited on
Commit
68db2d3
·
1 Parent(s): 73ef59c

feat(retrieval): persist document chunks in PostgreSQL for automatic self-healing across container rebuilds

Browse files
DocPilot/backend/app/api/documents.py CHANGED
@@ -11,6 +11,7 @@ from sqlalchemy.orm import Session
11
  import shutil
12
  import os
13
  import uuid
 
14
 
15
  from DocPilot.backend.app.services.ingestion import (
16
  process_document,
@@ -148,13 +149,16 @@ async def upload_document(
148
  db.refresh(document)
149
 
150
  try:
151
-
152
- process_document(
153
  file_path,
154
  current_user.id,
155
  document.id,
156
  mime_type=file.content_type,
157
  )
 
 
 
 
158
 
159
  except TextExtractionError as exc:
160
 
 
11
  import shutil
12
  import os
13
  import uuid
14
+ import json
15
 
16
  from DocPilot.backend.app.services.ingestion import (
17
  process_document,
 
149
  db.refresh(document)
150
 
151
  try:
152
+ chunks = process_document(
 
153
  file_path,
154
  current_user.id,
155
  document.id,
156
  mime_type=file.content_type,
157
  )
158
+ if chunks:
159
+ document.chunks_json = json.dumps(chunks)
160
+ document.chunk_count = len(chunks)
161
+ db.commit()
162
 
163
  except TextExtractionError as exc:
164
 
DocPilot/backend/app/models/document.py CHANGED
@@ -1,4 +1,4 @@
1
- from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey
2
 
3
  from sqlalchemy.sql import func
4
 
@@ -16,7 +16,6 @@ class Document(Base):
16
 
17
  filename = Column(String, nullable=False)
18
 
19
-
20
  filepath = Column(String, nullable=False)
21
 
22
  file_size = Column(Integer)
@@ -25,6 +24,8 @@ class Document(Base):
25
 
26
  chunk_count = Column(Integer)
27
 
 
 
28
  ocr_used = Column(Boolean, default=False)
29
 
30
  status = Column(String, default="processed")
 
1
+ from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, ForeignKey
2
 
3
  from sqlalchemy.sql import func
4
 
 
16
 
17
  filename = Column(String, nullable=False)
18
 
 
19
  filepath = Column(String, nullable=False)
20
 
21
  file_size = Column(Integer)
 
24
 
25
  chunk_count = Column(Integer)
26
 
27
+ chunks_json = Column(Text, nullable=True)
28
+
29
  ocr_used = Column(Boolean, default=False)
30
 
31
  status = Column(String, default="processed")
DocPilot/backend/app/services/ingestion.py CHANGED
@@ -742,3 +742,5 @@ def process_document(
742
 
743
  except Exception as e:
744
  logger.info("[TracePilot] document ingest failed: %r", e)
 
 
 
742
 
743
  except Exception as e:
744
  logger.info("[TracePilot] document ingest failed: %r", e)
745
+
746
+ return all_chunks
GaugePilot/backend/app/api/documents.py CHANGED
@@ -10,6 +10,7 @@ from sqlalchemy.orm import Session
10
 
11
  import shutil
12
  import os
 
13
 
14
  from DocPilot.backend.app.services.ingestion import (
15
  process_document,
@@ -112,12 +113,16 @@ async def upload_document(
112
  db.refresh(document)
113
 
114
  try:
115
- process_document(
116
  file_path,
117
  current_user.id,
118
  document.id,
119
  mime_type=file.content_type,
120
  )
 
 
 
 
121
  except TextExtractionError as exc:
122
  db.delete(document)
123
  db.commit()
 
10
 
11
  import shutil
12
  import os
13
+ import json
14
 
15
  from DocPilot.backend.app.services.ingestion import (
16
  process_document,
 
113
  db.refresh(document)
114
 
115
  try:
116
+ chunks = process_document(
117
  file_path,
118
  current_user.id,
119
  document.id,
120
  mime_type=file.content_type,
121
  )
122
+ if chunks:
123
+ document.chunks_json = json.dumps(chunks)
124
+ document.chunk_count = len(chunks)
125
+ db.commit()
126
  except TextExtractionError as exc:
127
  db.delete(document)
128
  db.commit()
pilotcore/retrieval/vector_store.py CHANGED
@@ -68,9 +68,43 @@ def load_user_documents(user_id: int):
68
  if os.path.exists(docs_path):
69
  try:
70
  with open(docs_path, "rb") as f:
71
- return pickle.load(f)
 
 
72
  except Exception:
73
- return []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  return []
75
 
76
 
 
68
  if os.path.exists(docs_path):
69
  try:
70
  with open(docs_path, "rb") as f:
71
+ docs = pickle.load(f)
72
+ if docs:
73
+ return docs
74
  except Exception:
75
+ pass
76
+
77
+ # Self-Healing Restoration: If container restarted and disk was wiped, restore chunks from database!
78
+ if user_id:
79
+ try:
80
+ import json
81
+ from DocPilot.backend.app.db.database import SessionLocal
82
+ from DocPilot.backend.app.models.document import Document
83
+ db = SessionLocal()
84
+ user_docs = db.query(Document).filter(Document.owner_id == user_id).all()
85
+ restored_chunks = []
86
+ for doc in user_docs:
87
+ chunks_raw = getattr(doc, "chunks_json", None)
88
+ if chunks_raw:
89
+ try:
90
+ parsed = json.loads(chunks_raw)
91
+ if isinstance(parsed, list):
92
+ restored_chunks.extend(parsed)
93
+ except Exception:
94
+ pass
95
+ db.close()
96
+
97
+ if restored_chunks:
98
+ try:
99
+ with open(docs_path, "wb") as f:
100
+ pickle.dump(restored_chunks, f)
101
+ print(f"[Self-Healing] Restored {len(restored_chunks)} document chunks from database for user {user_id}")
102
+ except Exception:
103
+ pass
104
+ return restored_chunks
105
+ except Exception as e:
106
+ print(f"[Self-Healing] Database restoration error: {e}")
107
+
108
  return []
109
 
110