Spaces:
Sleeping
Sleeping
feat: complete Sprint 14 national ETL architecture and exponential backoff
Browse files- __pycache__/api.cpython-312.pyc +0 -0
- __pycache__/bhashini.cpython-312.pyc +0 -0
- eligibility/__pycache__/__init__.cpython-312.pyc +0 -0
- eligibility/__pycache__/engine.cpython-312.pyc +0 -0
- eligibility/__pycache__/entities.cpython-312.pyc +0 -0
- eligibility/__pycache__/variables.cpython-312.pyc +0 -0
- requirements.txt +1 -0
- scraper/__init__.py +0 -0
- scraper/__pycache__/national_scraper.cpython-312.pyc +0 -0
- scraper/national_scraper.py +141 -0
- test_queries.py +26 -0
- whatsapp/__pycache__/__init__.cpython-312.pyc +0 -0
- whatsapp/__pycache__/webhook.cpython-312.pyc +0 -0
__pycache__/api.cpython-312.pyc
CHANGED
|
Binary files a/__pycache__/api.cpython-312.pyc and b/__pycache__/api.cpython-312.pyc differ
|
|
|
__pycache__/bhashini.cpython-312.pyc
CHANGED
|
Binary files a/__pycache__/bhashini.cpython-312.pyc and b/__pycache__/bhashini.cpython-312.pyc differ
|
|
|
eligibility/__pycache__/__init__.cpython-312.pyc
CHANGED
|
Binary files a/eligibility/__pycache__/__init__.cpython-312.pyc and b/eligibility/__pycache__/__init__.cpython-312.pyc differ
|
|
|
eligibility/__pycache__/engine.cpython-312.pyc
CHANGED
|
Binary files a/eligibility/__pycache__/engine.cpython-312.pyc and b/eligibility/__pycache__/engine.cpython-312.pyc differ
|
|
|
eligibility/__pycache__/entities.cpython-312.pyc
CHANGED
|
Binary files a/eligibility/__pycache__/entities.cpython-312.pyc and b/eligibility/__pycache__/entities.cpython-312.pyc differ
|
|
|
eligibility/__pycache__/variables.cpython-312.pyc
CHANGED
|
Binary files a/eligibility/__pycache__/variables.cpython-312.pyc and b/eligibility/__pycache__/variables.cpython-312.pyc differ
|
|
|
requirements.txt
CHANGED
|
@@ -11,3 +11,4 @@ transformers==4.38.2
|
|
| 11 |
sentencepiece>=0.2.0
|
| 12 |
sacremoses>=0.1.1
|
| 13 |
httpx
|
|
|
|
|
|
| 11 |
sentencepiece>=0.2.0
|
| 12 |
sacremoses>=0.1.1
|
| 13 |
httpx
|
| 14 |
+
feedparser
|
scraper/__init__.py
ADDED
|
File without changes
|
scraper/__pycache__/national_scraper.cpython-312.pyc
ADDED
|
Binary file (7.49 kB). View file
|
|
|
scraper/national_scraper.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import httpx
|
| 3 |
+
import asyncio
|
| 4 |
+
import feedparser
|
| 5 |
+
|
| 6 |
+
# Add this disguise so government firewalls don't block the RSS scraper
|
| 7 |
+
feedparser.USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
| 8 |
+
|
| 9 |
+
# --- CONFIGURATION ---
|
| 10 |
+
API_URL = "http://127.0.0.1:7860/api/admin/ingest"
|
| 11 |
+
ADMIN_SECRET = os.environ.get("ADMIN_SECRET", "change-this-in-production")
|
| 12 |
+
|
| 13 |
+
DOC_TYPE_MAP = {
|
| 14 |
+
"scheme": ["myscheme.gov.in", "jansoochna"],
|
| 15 |
+
"parliamentary": ["loksabha.nic.in", "sansad.in"],
|
| 16 |
+
"press_release": ["pib.gov.in"],
|
| 17 |
+
"open_data": ["data.gov.in"],
|
| 18 |
+
"gazette": ["egazette.gov.in", "rajpatra"]
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
def detect_doc_type(url: str) -> str:
|
| 22 |
+
for doc_type, domains in DOC_TYPE_MAP.items():
|
| 23 |
+
if any(d in url for d in domains):
|
| 24 |
+
return doc_type
|
| 25 |
+
return "general"
|
| 26 |
+
|
| 27 |
+
async def push_to_db(title: str, text: str, state: str, source_url: str, ministry: str = "Unknown"):
|
| 28 |
+
"""Pushes scraped data to the GovBridge FastAPI ingestion endpoint."""
|
| 29 |
+
doc_type = detect_doc_type(source_url)
|
| 30 |
+
|
| 31 |
+
payload = {
|
| 32 |
+
"title": title,
|
| 33 |
+
"text": text,
|
| 34 |
+
"ministry": ministry,
|
| 35 |
+
"state": state,
|
| 36 |
+
"source_url": source_url,
|
| 37 |
+
"doc_type": doc_type
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
async with httpx.AsyncClient() as client:
|
| 41 |
+
try:
|
| 42 |
+
resp = await client.post(
|
| 43 |
+
f"{API_URL}?admin_key={ADMIN_SECRET}",
|
| 44 |
+
json=payload,
|
| 45 |
+
timeout=30.0
|
| 46 |
+
)
|
| 47 |
+
if resp.status_code == 200:
|
| 48 |
+
print(f"β
Ingested: [{state}] {title[:30]}... ({doc_type})")
|
| 49 |
+
else:
|
| 50 |
+
print(f"β Failed: {title[:30]} - {resp.text}")
|
| 51 |
+
except Exception as e:
|
| 52 |
+
print(f"β οΈ Network Error pushing {title[:30]}: {e}")
|
| 53 |
+
|
| 54 |
+
# --- TARGET 1: MYSCHEME.GOV.IN ---
|
| 55 |
+
async def run_myscheme_pipeline():
|
| 56 |
+
MYSCHEME_CATEGORIES = ["agriculture", "education", "health", "women", "children"]
|
| 57 |
+
ALL_STATES = ["Maharashtra", "Tamil Nadu", "Punjab", "Central"] # Truncated for example
|
| 58 |
+
|
| 59 |
+
print("π Starting MyScheme Pipeline...")
|
| 60 |
+
for state in ALL_STATES:
|
| 61 |
+
for category in MYSCHEME_CATEGORIES:
|
| 62 |
+
# TODO: Add your BeautifulSoup/Playwright scraping logic here
|
| 63 |
+
# Mocking the scraped data for integration:
|
| 64 |
+
mock_title = f"Sample {category.title()} Scheme for {state}"
|
| 65 |
+
mock_text = f"This scheme provides financial assistance for {category} in {state}."
|
| 66 |
+
|
| 67 |
+
await push_to_db(mock_title, mock_text, state, "https://myscheme.gov.in/dummy")
|
| 68 |
+
await asyncio.sleep(1)
|
| 69 |
+
|
| 70 |
+
# --- TARGET 2: DATA.GOV.IN ---
|
| 71 |
+
async def run_data_gov_pipeline():
|
| 72 |
+
DATA_GOV_API = "https://api.data.gov.in/resource"
|
| 73 |
+
DATASET_IDS = ["6176ee09-3d56-4a3b-8115-21841dce9f8b"]
|
| 74 |
+
API_KEY = os.environ.get("DATA_GOV_API_KEY")
|
| 75 |
+
|
| 76 |
+
print("π Starting Data.gov.in Pipeline...")
|
| 77 |
+
if not API_KEY:
|
| 78 |
+
print("β οΈ Missing DATA_GOV_API_KEY. Skipping.")
|
| 79 |
+
return
|
| 80 |
+
|
| 81 |
+
# Bumped timeout to 120 seconds and added a retry loop
|
| 82 |
+
async with httpx.AsyncClient(timeout=120.0) as client:
|
| 83 |
+
for dataset in DATASET_IDS:
|
| 84 |
+
max_retries = 3
|
| 85 |
+
|
| 86 |
+
for attempt in range(max_retries):
|
| 87 |
+
try:
|
| 88 |
+
# Lowered limit to 20 to reduce server load
|
| 89 |
+
resp = await client.get(
|
| 90 |
+
f"{DATA_GOV_API}/{dataset}",
|
| 91 |
+
params={"api-key": API_KEY, "format": "json", "limit": 20}
|
| 92 |
+
)
|
| 93 |
+
resp.raise_for_status() # Catches 500 Internal Server errors
|
| 94 |
+
|
| 95 |
+
records = resp.json().get("records", [])
|
| 96 |
+
print(f"π¦ Successfully fetched {len(records)} records from dataset.")
|
| 97 |
+
|
| 98 |
+
for rec in records:
|
| 99 |
+
title = rec.get("scheme_name", "Unknown Dataset")
|
| 100 |
+
text = str(rec)
|
| 101 |
+
await push_to_db(title, text, "Central", f"https://data.gov.in/catalog/{dataset}")
|
| 102 |
+
|
| 103 |
+
break # Success! Break out of the retry loop
|
| 104 |
+
|
| 105 |
+
except httpx.ReadTimeout:
|
| 106 |
+
print(f"β³ Attempt {attempt + 1} timed out. The government server is lagging...")
|
| 107 |
+
if attempt < max_retries - 1:
|
| 108 |
+
await asyncio.sleep(3) # Wait 3 seconds before trying again
|
| 109 |
+
else:
|
| 110 |
+
print("β Max retries reached. Skipping this dataset.")
|
| 111 |
+
except Exception as e:
|
| 112 |
+
print(f"β Unknown API Error: {e}")
|
| 113 |
+
break
|
| 114 |
+
# --- TARGET 4: PIB PRESS RELEASES ---
|
| 115 |
+
async def run_pib_pipeline():
|
| 116 |
+
print("π Starting PIB RSS Pipeline...")
|
| 117 |
+
MINISTRY_FEEDS = ["https://pib.gov.in/RssMain.aspx"]
|
| 118 |
+
|
| 119 |
+
for feed_url in MINISTRY_FEEDS:
|
| 120 |
+
feed = feedparser.parse(feed_url)
|
| 121 |
+
for entry in feed.entries[:50]: # Process top 50 recent releases
|
| 122 |
+
await push_to_db(
|
| 123 |
+
title=entry.title,
|
| 124 |
+
text=entry.description,
|
| 125 |
+
state="Central",
|
| 126 |
+
source_url=entry.link,
|
| 127 |
+
)
|
| 128 |
+
|
| 129 |
+
# --- CLI ROUTER FOR BATCHING ---
|
| 130 |
+
if __name__ == "__main__":
|
| 131 |
+
import sys
|
| 132 |
+
target = sys.argv[1] if len(sys.argv) > 1 else "all"
|
| 133 |
+
|
| 134 |
+
if target == "myscheme":
|
| 135 |
+
asyncio.run(run_myscheme_pipeline())
|
| 136 |
+
elif target == "datagov":
|
| 137 |
+
asyncio.run(run_data_gov_pipeline())
|
| 138 |
+
elif target == "pib":
|
| 139 |
+
asyncio.run(run_pib_pipeline())
|
| 140 |
+
else:
|
| 141 |
+
print("Please specify a target: python national_scraper.py [myscheme|datagov|pib]")
|
test_queries.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import httpx
|
| 2 |
+
import asyncio
|
| 3 |
+
|
| 4 |
+
API_URL = "http://127.0.0.1:7860/api/rag/query"
|
| 5 |
+
|
| 6 |
+
TEST_QUERIES = [
|
| 7 |
+
"What health schemes are available in Maharashtra?",
|
| 8 |
+
"Education scholarships for SC students in Tamil Nadu",
|
| 9 |
+
"MGNREGA wage rate 2024",
|
| 10 |
+
"PM Awas Yojana eligibility",
|
| 11 |
+
"Agricultural subsidy for small farmers in Punjab"
|
| 12 |
+
]
|
| 13 |
+
|
| 14 |
+
async def run_tests():
|
| 15 |
+
async with httpx.AsyncClient(timeout=60.0) as client:
|
| 16 |
+
for q in TEST_QUERIES:
|
| 17 |
+
print(f"\n{'='*50}\nπ§ͺ Testing: {q}\n{'-'*50}")
|
| 18 |
+
resp = await client.post(API_URL, json={"question": q, "language": "english"})
|
| 19 |
+
if resp.status_code == 200:
|
| 20 |
+
print(resp.text)
|
| 21 |
+
print(f"\nπ Sources: {resp.headers.get('X-Sources', 'None')}")
|
| 22 |
+
else:
|
| 23 |
+
print(f"β Error: {resp.status_code} - {resp.text}")
|
| 24 |
+
|
| 25 |
+
if __name__ == "__main__":
|
| 26 |
+
asyncio.run(run_tests())
|
whatsapp/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (156 Bytes). View file
|
|
|
whatsapp/__pycache__/webhook.cpython-312.pyc
ADDED
|
Binary file (12.6 kB). View file
|
|
|