Acytel commited on
Commit
8154521
Β·
1 Parent(s): ffd464d

feat: unified SSOT architecture and scaled vector database ingestion

Browse files
ingestion/__pycache__/ocr_processor.cpython-312.pyc ADDED
Binary file (2.64 kB). View file
 
ingestion/ocr_processor_runner.py CHANGED
@@ -2,19 +2,15 @@ import os
2
  import sys
3
  import hashlib
4
  import httpx
5
- from supabase import create_client
6
 
7
  sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
8
  from ingestion.ocr_processor import download_and_extract
9
 
10
- SUPABASE_URL = os.environ["SUPABASE_URL"]
11
- SUPABASE_KEY = os.environ["SUPABASE_KEY"]
12
  HF_SPACE_URL = os.environ.get("HF_SPACE_URL", "https://harshrawat18-govbridge-api.hf.space")
13
  ADMIN_SECRET = os.environ.get("ADMIN_SECRET", "")
14
  PDF_URL = os.environ.get("PDF_URL", "")
15
 
16
- supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
17
-
18
  def chunk_text(text: str, chunk_size: int = 300) -> list:
19
  """Split text into word-based chunks with overlap."""
20
  words = text.split()
@@ -27,8 +23,8 @@ def chunk_text(text: str, chunk_size: int = 300) -> list:
27
  chunks.append(chunk)
28
  return chunks
29
 
30
- def ingest_to_supabase(chunks: list, metadata: dict):
31
- """Send chunks to the HF Space ingest endpoint."""
32
  for i, chunk in enumerate(chunks):
33
  content_hash = hashlib.sha256(
34
  f"{metadata['source_url']}_{i}".encode()
@@ -39,8 +35,8 @@ def ingest_to_supabase(chunks: list, metadata: dict):
39
  "text": chunk,
40
  "source_url": metadata["source_url"],
41
  "ministry": metadata.get("ministry", "Government of India"),
42
- "state": metadata.get("state", "Central"),
43
- "doc_type": metadata.get("doc_type", "gazette"),
44
  "content_hash": content_hash,
45
  "chunk_index": i
46
  }
@@ -54,7 +50,7 @@ def ingest_to_supabase(chunks: list, metadata: dict):
54
  timeout=30.0
55
  )
56
  if response.status_code == 200:
57
- print(f"βœ… Chunk {i+1}/{len(chunks)} ingested")
58
  else:
59
  print(f"⚠️ Chunk {i+1} failed: {response.status_code} {response.text}")
60
  except Exception as e:
@@ -62,34 +58,38 @@ def ingest_to_supabase(chunks: list, metadata: dict):
62
 
63
  def main():
64
  if not PDF_URL:
65
- print("❌ No PDF_URL provided")
 
 
 
 
66
  sys.exit(1)
67
 
68
- print(f"πŸš€ Starting PDF extraction: {PDF_URL}")
69
 
70
  try:
71
  result = download_and_extract(PDF_URL)
72
  text = result["text"]
73
  word_count = len(text.split())
74
- print(f"βœ… Extracted {word_count} words via {result['extraction_method']}")
75
- print(f"πŸ“„ Preview: {text[:200]}...")
76
-
77
  chunks = chunk_text(text, chunk_size=300)
78
- print(f"πŸ“¦ Created {len(chunks)} chunks")
79
 
80
- ingest_to_supabase(chunks, {
 
81
  "source_url": PDF_URL,
82
- "title": "Government PDF Document",
83
- "ministry": "Government of India",
84
- "state": "Central",
85
- "doc_type": "gazette"
86
  })
87
 
88
- print(f"πŸŽ‰ Done. {len(chunks)} chunks ingested to Supabase.")
89
 
90
  except Exception as e:
91
  print(f"❌ Critical failure: {e}")
92
  sys.exit(1)
93
 
94
  if __name__ == "__main__":
95
- main()
 
2
  import sys
3
  import hashlib
4
  import httpx
 
5
 
6
  sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
7
  from ingestion.ocr_processor import download_and_extract
8
 
9
+ # --- SSOT INGESTION CONFIGURATION ---
 
10
  HF_SPACE_URL = os.environ.get("HF_SPACE_URL", "https://harshrawat18-govbridge-api.hf.space")
11
  ADMIN_SECRET = os.environ.get("ADMIN_SECRET", "")
12
  PDF_URL = os.environ.get("PDF_URL", "")
13
 
 
 
14
  def chunk_text(text: str, chunk_size: int = 300) -> list:
15
  """Split text into word-based chunks with overlap."""
16
  words = text.split()
 
23
  chunks.append(chunk)
24
  return chunks
25
 
26
+ def ingest_to_hf(chunks: list, metadata: dict):
27
+ """Send chunks straight to the AI Vector Database (HF Space)."""
28
  for i, chunk in enumerate(chunks):
29
  content_hash = hashlib.sha256(
30
  f"{metadata['source_url']}_{i}".encode()
 
35
  "text": chunk,
36
  "source_url": metadata["source_url"],
37
  "ministry": metadata.get("ministry", "Government of India"),
38
+ "state": metadata.get("state", "National"),
39
+ "doc_type": metadata.get("doc_type", "document"),
40
  "content_hash": content_hash,
41
  "chunk_index": i
42
  }
 
50
  timeout=30.0
51
  )
52
  if response.status_code == 200:
53
+ print(f"βœ… Chunk {i+1}/{len(chunks)} injected into AI Brain")
54
  else:
55
  print(f"⚠️ Chunk {i+1} failed: {response.status_code} {response.text}")
56
  except Exception as e:
 
58
 
59
  def main():
60
  if not PDF_URL:
61
+ print("❌ No PDF_URL provided in terminal")
62
+ sys.exit(1)
63
+
64
+ if not ADMIN_SECRET:
65
+ print("❌ πŸ”’ SECURITY HALT: ADMIN_SECRET environment variable is missing!")
66
  sys.exit(1)
67
 
68
+ print(f"πŸš€ Starting PDF memory extraction: {PDF_URL}")
69
 
70
  try:
71
  result = download_and_extract(PDF_URL)
72
  text = result["text"]
73
  word_count = len(text.split())
74
+ print(f"βœ… Extracted {word_count} words in memory via {result['extraction_method']}")
75
+
 
76
  chunks = chunk_text(text, chunk_size=300)
77
+ print(f"πŸ“¦ Sliced into {len(chunks)} high-density AI chunks")
78
 
79
+ # Dynamically injecting NEP Metadata for this specific run
80
+ ingest_to_hf(chunks, {
81
  "source_url": PDF_URL,
82
+ "title": "National Education Policy 2020",
83
+ "ministry": "Ministry of Education",
84
+ "state": "National",
85
+ "doc_type": "document"
86
  })
87
 
88
+ print(f"πŸŽ‰ MASSIVE W! {len(chunks)} chunks ingested to Vector DB.")
89
 
90
  except Exception as e:
91
  print(f"❌ Critical failure: {e}")
92
  sys.exit(1)
93
 
94
  if __name__ == "__main__":
95
+ main()
scraper.py CHANGED
@@ -2,20 +2,17 @@ import os
2
  import asyncio
3
  import hashlib
4
  import random
 
5
  from playwright.async_api import async_playwright
6
- from supabase import create_client, Client
7
  import logging
8
 
9
  # Setup logging
10
  logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
11
  logger = logging.getLogger(__name__)
12
 
13
- # --- 1. SETUP YOUR SUPABASE VAULT HERE ---
14
- SUPABASE_URL = os.environ.get("SUPABASE_URL")
15
- SUPABASE_KEY = os.environ.get("SUPABASE_KEY")
16
-
17
- # Connect to the database
18
- supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
19
 
20
  # --- STEALTH SCRIPT COLLECTION ---
21
  STEALTH_SCRIPTS = [
@@ -148,26 +145,56 @@ GOVERNMENT_SITES = [
148
  "url": "https://www.myscheme.gov.in/search",
149
  "source": "MyScheme Portal",
150
  "selector": "h2",
151
- "extract_type": "myscheme"
 
152
  },
153
  {
154
  "name": "PM-KISAN Scheme",
155
  "url": "https://pmkisan.gov.in/",
156
  "source": "Ministry of Agriculture",
157
  "selector": "h2, h3, div.content",
158
- "extract_type": "generic"
 
159
  },
160
- # Truncated list for efficiency; add additional sites as needed
161
  ]
162
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  async def scrape_myscheme(page, site_config):
164
  """Specialized scraper for MyScheme portal"""
165
  try:
166
  logger.info(f"🎯 Scraping {site_config['name']}...")
167
- await asyncio.sleep(random.uniform(1.5, 3.5)) # Human-like delay
168
- await page.goto(site_config["url"], timeout=30000, wait_until="networkidle")
169
 
170
- # Human-like scrolling
171
  await page.evaluate("window.scrollTo(0, document.body.scrollHeight / 3)")
172
  await asyncio.sleep(random.uniform(0.8, 1.8))
173
 
@@ -178,25 +205,18 @@ async def scrape_myscheme(page, site_config):
178
  if len(schemes) >= 2:
179
  for i in range(0, len(schemes) - 1, 2):
180
  try:
181
- inner_text = schemes[i]
182
- content_hash = hashlib.sha256(inner_text.encode('utf-8')).hexdigest()
183
 
184
- existing = supabase.table("official_documents").select("id").eq("content_hash", content_hash).execute()
185
- if existing.data:
186
- print('Duplicate detected, skipping')
187
- continue
188
-
189
- data_to_save = {
190
- "title": inner_text,
191
- "department": schemes[i + 1] if i + 1 < len(schemes) else "Unknown",
192
- "document_type": "Government Scheme",
193
- "file_url": site_config["url"],
194
- "authentic_source_url": site_config["source"],
195
- "content_hash": content_hash
196
- }
197
- supabase.table("official_documents").insert(data_to_save).execute()
198
- logger.info(f"βœ… Uploaded: {schemes[i]}")
199
- results.append(True)
200
  except Exception as e:
201
  logger.error(f"❌ Failed to upload {schemes[i]}: {str(e)}")
202
  results.append(False)
@@ -211,7 +231,7 @@ async def scrape_generic(page, site_config):
211
  try:
212
  logger.info(f"🎯 Scraping {site_config['name']}...")
213
  await asyncio.sleep(random.uniform(1.5, 3.5))
214
- await page.goto(site_config["url"], timeout=30000, wait_until="networkidle")
215
 
216
  await page.evaluate("window.scrollTo(0, document.body.scrollHeight / 3)")
217
  await asyncio.sleep(random.uniform(0.8, 1.8))
@@ -229,25 +249,17 @@ async def scrape_generic(page, site_config):
229
  if all_text:
230
  for i, text in enumerate(all_text[:10]):
231
  try:
232
- content_hash = hashlib.sha256(text.encode('utf-8')).hexdigest()
233
- existing = supabase.table("official_documents").select("id").eq("content_hash", content_hash).execute()
234
- if existing.data:
235
- print('Duplicate detected, skipping')
236
- continue
237
-
238
- data_to_save = {
239
- "title": text,
240
- "department": site_config["source"],
241
- "document_type": "Government Content",
242
- "file_url": site_config["url"],
243
- "authentic_source_url": site_config["source"],
244
- "content_hash": content_hash
245
- }
246
- supabase.table("official_documents").insert(data_to_save).execute()
247
- logger.info(f"βœ… Uploaded from {site_config['name']}: {text[:50]}...")
248
- results.append(True)
249
  except Exception as e:
250
- logger.warning(f"⚠️ Skipped duplicate or error: {str(e)[:50]}")
251
  results.append(False)
252
  return results
253
  return []
@@ -256,10 +268,10 @@ async def scrape_generic(page, site_config):
256
  return []
257
 
258
  async def scrape_and_upload():
259
- print("πŸ₯· Booting up GovBridge Multi-Site Bot (Zero-Dependency Stealth Mode)...\n")
260
 
261
  async with async_playwright() as p:
262
- # Launch hardened browser per Claude's specifications
263
  browser = await p.chromium.launch(
264
  headless=True,
265
  args=[
@@ -314,7 +326,7 @@ async def scrape_and_upload():
314
  await asyncio.sleep(random.uniform(2.0, 4.0)) # Delay between sites
315
 
316
  print(f"\n--- πŸ“Š FINAL REPORT ---")
317
- print(f"βœ… Total items uploaded: {total_uploaded}")
318
  print("\nShutting down bot.")
319
  await browser.close()
320
 
 
2
  import asyncio
3
  import hashlib
4
  import random
5
+ import httpx
6
  from playwright.async_api import async_playwright
 
7
  import logging
8
 
9
  # Setup logging
10
  logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
11
  logger = logging.getLogger(__name__)
12
 
13
+ # --- UNIFIED INGESTION CONFIGURATION (OPTION A) ---
14
+ HF_SPACE_URL = os.environ.get("HF_SPACE_URL", "https://harshrawat18-govbridge-api.hf.space")
15
+ ADMIN_SECRET = os.environ.get("ADMIN_SECRET", "")
 
 
 
16
 
17
  # --- STEALTH SCRIPT COLLECTION ---
18
  STEALTH_SCRIPTS = [
 
145
  "url": "https://www.myscheme.gov.in/search",
146
  "source": "MyScheme Portal",
147
  "selector": "h2",
148
+ "extract_type": "myscheme",
149
+ "doc_type": "scheme"
150
  },
151
  {
152
  "name": "PM-KISAN Scheme",
153
  "url": "https://pmkisan.gov.in/",
154
  "source": "Ministry of Agriculture",
155
  "selector": "h2, h3, div.content",
156
+ "extract_type": "generic",
157
+ "doc_type": "scheme"
158
  },
 
159
  ]
160
 
161
+ async def push_to_hf_api(title: str, text: str, source_url: str, ministry: str, doc_type: str):
162
+ """Pushes scraped data directly to the Hugging Face Ingestion Engine"""
163
+ payload = {
164
+ "title": title,
165
+ "text": text,
166
+ "ministry": ministry,
167
+ "state": "National",
168
+ "source_url": source_url,
169
+ "doc_type": doc_type
170
+ }
171
+
172
+ async with httpx.AsyncClient() as client:
173
+ try:
174
+ resp = await client.post(
175
+ f"{HF_SPACE_URL}/api/admin/ingest",
176
+ json=payload,
177
+ params={"admin_key": ADMIN_SECRET},
178
+ headers={"X-Admin-Secret": ADMIN_SECRET},
179
+ timeout=30.0
180
+ )
181
+ if resp.status_code == 200:
182
+ logger.info(f"βœ… Ingested to Vector DB: {title[:40]}...")
183
+ return True
184
+ else:
185
+ logger.error(f"❌ HF API Error: {resp.status_code} - {resp.text}")
186
+ return False
187
+ except Exception as e:
188
+ logger.error(f"⚠️ Network Error pushing {title[:30]}: {e}")
189
+ return False
190
+
191
  async def scrape_myscheme(page, site_config):
192
  """Specialized scraper for MyScheme portal"""
193
  try:
194
  logger.info(f"🎯 Scraping {site_config['name']}...")
195
+ await asyncio.sleep(random.uniform(1.5, 3.5))
196
+ await page.goto(site_config["url"], timeout=60000, wait_until="domcontentloaded")
197
 
 
198
  await page.evaluate("window.scrollTo(0, document.body.scrollHeight / 3)")
199
  await asyncio.sleep(random.uniform(0.8, 1.8))
200
 
 
205
  if len(schemes) >= 2:
206
  for i in range(0, len(schemes) - 1, 2):
207
  try:
208
+ title = schemes[i]
209
+ department = schemes[i + 1] if i + 1 < len(schemes) else "Unknown"
210
 
211
+ # Push directly to HF Engine
212
+ success = await push_to_hf_api(
213
+ title=title,
214
+ text=f"Details regarding {title} provided by {department}.",
215
+ source_url=site_config["url"],
216
+ ministry=department,
217
+ doc_type=site_config["doc_type"]
218
+ )
219
+ results.append(success)
 
 
 
 
 
 
 
220
  except Exception as e:
221
  logger.error(f"❌ Failed to upload {schemes[i]}: {str(e)}")
222
  results.append(False)
 
231
  try:
232
  logger.info(f"🎯 Scraping {site_config['name']}...")
233
  await asyncio.sleep(random.uniform(1.5, 3.5))
234
+ await page.goto(site_config["url"], timeout=60000, wait_until="domcontentloaded")
235
 
236
  await page.evaluate("window.scrollTo(0, document.body.scrollHeight / 3)")
237
  await asyncio.sleep(random.uniform(0.8, 1.8))
 
249
  if all_text:
250
  for i, text in enumerate(all_text[:10]):
251
  try:
252
+ # Push directly to HF Engine
253
+ success = await push_to_hf_api(
254
+ title=text,
255
+ text=text,
256
+ source_url=site_config["url"],
257
+ ministry=site_config["source"],
258
+ doc_type=site_config["doc_type"]
259
+ )
260
+ results.append(success)
 
 
 
 
 
 
 
 
261
  except Exception as e:
262
+ logger.warning(f"⚠️ Error: {str(e)[:50]}")
263
  results.append(False)
264
  return results
265
  return []
 
268
  return []
269
 
270
  async def scrape_and_upload():
271
+ print("πŸ₯· Booting up GovBridge UNIFIED Scraper (Sending to HF API)...\n")
272
 
273
  async with async_playwright() as p:
274
+ # Launch hardened browser
275
  browser = await p.chromium.launch(
276
  headless=True,
277
  args=[
 
326
  await asyncio.sleep(random.uniform(2.0, 4.0)) # Delay between sites
327
 
328
  print(f"\n--- πŸ“Š FINAL REPORT ---")
329
+ print(f"βœ… Total items pushed to Vector Engine: {total_uploaded}")
330
  print("\nShutting down bot.")
331
  await browser.close()
332