Numinfo / app.py
sauravsingh2111's picture
Upload app.py
5f08fbc verified
Raw
History Blame Contribute Delete
15.9 kB
import duckdb
import json
import re
from datetime import datetime, timezone
from fastapi import FastAPI, Query
from huggingface_hub import hf_hub_url
from typing import Optional
import threading
import gc
app = FastAPI()
# ===== RESULT CACHE =====
result_cache = {}
cache_timestamps = {}
CACHE_TTL = 300 # 5 minutes
def get_cached(key):
if key in result_cache:
if (datetime.now().timestamp() - cache_timestamps.get(key, 0)) < CACHE_TTL:
return result_cache[key]
return None
def set_cache(key, value):
if len(result_cache) >= 500:
oldest = min(cache_timestamps, key=cache_timestamps.get)
del result_cache[oldest]
del cache_timestamps[oldest]
result_cache[key] = value
cache_timestamps[key] = datetime.now().timestamp()
# ===== Thread-local connection =====
thread_local = threading.local()
def get_db_connection():
"""Get thread-local DuckDB connection"""
if not hasattr(thread_local, "connection") or thread_local.connection is None:
try:
thread_local.connection = duckdb.connect()
thread_local.connection.execute("PRAGMA memory_limit='4GB'") # CHANGE 1
thread_local.connection.execute("PRAGMA temp_directory='/tmp'")
thread_local.connection.execute("PRAGMA read_only=true") # CHANGE 2
thread_local.connection.execute("PRAGMA threads=4") # CHANGE 2
except Exception as e:
print(f"Error creating connection: {e}")
thread_local.connection = duckdb.connect()
return thread_local.connection
# index_map.json load karein
try:
with open("index_map.json", "r") as f:
index_map = json.load(f)
except:
index_map = []
REPO_ID = "tfqdeadlo/850MindData"
# ===== HELPER FUNCTIONS ===== (SAME - NO CHANGE)
def format_address(address):
"""Format address from string to structured object"""
if not address:
return {"full_address": "N/A", "landmark": "", "village_city": "", "district": "", "state": "", "pincode": ""}
address = str(address).strip()
# Clean the address
clean_address = address.replace('!-!-', ', ').replace('!!', ', ').replace('!', ', ')
clean_address = re.sub(r'\s+', ' ', clean_address).strip()
# Split by comma
parts = [p.strip() for p in clean_address.split(',') if p.strip()]
if not parts:
return {
"full_address": address,
"landmark": address,
"village_city": "",
"district": "",
"state": "",
"pincode": ""
}
# If only one part, try to extract pincode and state
if len(parts) == 1:
pincode = ''
state = ''
single_part = parts[0]
# Extract pincode (6 digits)
pincode_match = re.search(r'\b(\d{6})\b', single_part)
if pincode_match:
pincode = pincode_match.group(1)
single_part = single_part.replace(pincode, '').strip()
# Extract state
states = ['BIHAR', 'UTTAR PRADESH', 'DELHI', 'MAHARASHTRA', 'WEST BENGAL',
'TAMIL NADU', 'KARNATAKA', 'GUJARAT', 'RAJASTHAN', 'PUNJAB', 'HARYANA',
'KERALA', 'ANDHRA PRADESH', 'TELANGANA', 'ODISHA', 'MADHYA PRADESH',
'UTTARAKHAND', 'CHHATTISGARH', 'JHARKHAND', 'ASSAM']
for st in states:
if st in single_part.upper():
state = st.title()
single_part = single_part.replace(st, '').strip()
break
single_part = re.sub(r'^[\s\-]+|[\s\-]+$', '', single_part)
return {
"full_address": address,
"landmark": single_part if single_part else address,
"village_city": "",
"district": "",
"state": state,
"pincode": pincode
}
# Multiple parts
result = {
"full_address": clean_address,
"landmark": parts[0] if len(parts) > 0 else '',
"village_city": parts[1] if len(parts) > 1 else '',
"district": parts[2] if len(parts) > 2 else '',
"state": parts[3] if len(parts) > 3 else '',
"pincode": parts[4] if len(parts) > 4 else ''
}
# Try to extract pincode and state if not found
if not result['state'] or not result['pincode']:
full_text = ' '.join(parts)
if not result['pincode']:
pincode_match = re.search(r'\b(\d{6})\b', full_text)
if pincode_match:
result['pincode'] = pincode_match.group(1)
if not result['state']:
states = ['BIHAR', 'UTTAR PRADESH', 'DELHI', 'MAHARASHTRA', 'WEST BENGAL',
'TAMIL NADU', 'KARNATAKA', 'GUJARAT', 'RAJASTHAN', 'PUNJAB', 'HARYANA',
'KERALA', 'ANDHRA PRADESH', 'TELANGANA', 'ODISHA', 'MADHYA PRADESH',
'UTTARAKHAND', 'CHHATTISGARH', 'JHARKHAND', 'ASSAM']
for st in states:
if st in full_text.upper():
result['state'] = st.title()
break
return result
def extract_operator(circle):
"""Extract operator from circle"""
if not circle:
return 'Unknown'
circle_upper = str(circle).upper()
if 'JIO' in circle_upper:
return 'Jio'
elif 'AIRTEL' in circle_upper:
return 'Airtel'
elif 'VODAFONE' in circle_upper or 'VI' in circle_upper:
return 'Vodafone Idea (Vi)'
elif 'BSNL' in circle_upper:
return 'BSNL'
return circle.split()[0] if circle else 'Unknown'
def extract_state(address):
"""Extract state from address"""
if not address:
return 'Unknown'
address = str(address)
states = ['BIHAR', 'UTTAR PRADESH', 'DELHI', 'MAHARASHTRA', 'WEST BENGAL',
'TAMIL NADU', 'KARNATAKA', 'GUJARAT', 'RAJASTHAN', 'PUNJAB', 'HARYANA',
'KERALA', 'ANDHRA PRADESH', 'TELANGANA', 'ODISHA', 'MADHYA PRADESH',
'UTTARAKHAND', 'CHHATTISGARH', 'JHARKHAND', 'ASSAM']
address_upper = address.upper()
for state in states:
if state in address_upper:
return state.title()
return 'Unknown'
# ===== SEARCH FUNCTION ===== (WITH CACHE)
def search_mobile_number(m, con):
"""Search a mobile number and return results"""
m_str = str(m)
# ===== CACHE CHECK ===== (CHANGE 4)
cache_key = f"mobile:{m_str}"
cached = get_cached(cache_key)
if cached is not None:
return cached
# Find files to scan
files_to_scan = []
for i in range(len(index_map)):
curr_start = index_map[i]["start_mobile"]
next_start = index_map[i+1]["start_mobile"] if i+1 < len(index_map) else "0"
low = min(curr_start, next_start)
high = max(curr_start, next_start)
if low <= m <= high or m.startswith(curr_start[:4]):
files_to_scan.append(index_map[i]["file"])
safety_parts = [
"data/Hi-Tek_Part_851.parquet",
"data/Hi-Tek_Part_850.parquet"
]
files_to_scan = list(dict.fromkeys(files_to_scan + safety_parts))[:10]
all_results = []
columns = ['mobile', 'name', 'fname', 'address', 'alt', 'circle', 'id', 'email']
for f_name in files_to_scan:
try:
file_url = hf_hub_url(repo_id=REPO_ID, filename=f_name, repo_type="dataset")
query = f"SELECT * FROM read_parquet('{file_url}') WHERE CAST(mobile AS VARCHAR) = '{m}'"
result = con.execute(query).fetchall()
if result:
for row in result:
row_dict = dict(zip(columns, row))
all_results.append({
"name": row_dict.get('name', 'N/A'),
"father_name": row_dict.get('fname', 'N/A'),
"address": format_address(row_dict.get('address', '')),
"alternate_number": row_dict.get('alt', 'N/A'),
"circle": row_dict.get('circle', 'N/A'),
"operator": extract_operator(row_dict.get('circle', '')),
"state": extract_state(row_dict.get('address', '')),
"email": row_dict.get('email', None),
"id": row_dict.get('id', 'N/A')
})
del result
except Exception as e:
print(f"Error in {f_name}: {e}")
continue
gc.collect()
# ===== CACHE SAVE ===== (CHANGE 4)
set_cache(cache_key, all_results)
return all_results
# ===== SEARCH WITH ALTERNATE NUMBERS =====
def search_with_alternates(m, con):
"""Search main number + all alternate numbers and combine results"""
all_final_results = []
processed_numbers = set()
numbers_to_search = [m]
# 3 levels deep
for level in range(3):
current_numbers = numbers_to_search.copy()
numbers_to_search = []
for num in current_numbers:
if num in processed_numbers:
continue
processed_numbers.add(num)
# Search this number
results = search_mobile_number(num, con)
if results:
all_final_results.extend(results)
# Collect alternate numbers
for result in results:
alt = result.get('alternate_number')
if alt and alt != 'N/A' and len(str(alt)) == 10:
alt_str = str(alt)
if alt_str not in processed_numbers and alt_str not in numbers_to_search:
numbers_to_search.append(alt_str)
return all_final_results
# ===== SEARCH FUNCTIONS =====
@app.get("/search/{mobile}")
def search_by_mobile(mobile: str):
m = str(mobile).strip()
con = get_db_connection()
try:
# Search main + all alternate numbers
results = search_with_alternates(m, con)
if results:
return {
"success": True,
"api_by": "@sauravsingh2111",
"timestamp": datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z'),
"mobile": m,
"total_results": len(results),
"results": results,
"message": f"Found {len(results)} records for {m} (including alternate numbers)"
}
return {
"success": False,
"api_by": "@sauravsingh2111",
"mobile": m,
"message": f"No details found for {m}",
"contact": "Please contact @sauravsingh2111 for support"
}
except Exception as e:
print(f"Search error: {e}")
return {
"success": False,
"api_by": "@sauravsingh2111",
"mobile": m,
"error": str(e),
"message": "Error occurred during search"
}
# ===== ID SEARCH ===== (WITH CACHE)
@app.get("/search/id/{id_value}")
def search_by_id(id_value: str):
"""Search ID and return ALL results"""
search_id = str(id_value).strip()
# ===== CACHE CHECK ===== (CHANGE 5)
cache_key = f"id:{search_id}"
cached = get_cached(cache_key)
if cached is not None:
return cached
con = get_db_connection()
all_results = []
all_files = [item["file"] for item in index_map][:20]
columns = ['mobile', 'name', 'fname', 'address', 'alt', 'circle', 'id', 'email']
for f_name in all_files:
try:
file_url = hf_hub_url(repo_id=REPO_ID, filename=f_name, repo_type="dataset")
query = f"SELECT * FROM read_parquet('{file_url}') WHERE CAST(id AS VARCHAR) = '{search_id}'"
result = con.execute(query).fetchall()
if result:
for row in result:
row_dict = dict(zip(columns, row))
all_results.append({
"name": row_dict.get('name', 'N/A'),
"father_name": row_dict.get('fname', 'N/A'),
"address": format_address(row_dict.get('address', '')),
"alternate_number": row_dict.get('alt', 'N/A'),
"circle": row_dict.get('circle', 'N/A'),
"operator": extract_operator(row_dict.get('circle', '')),
"state": extract_state(row_dict.get('address', '')),
"email": row_dict.get('email', None),
"id": row_dict.get('id', 'N/A')
})
del result
except Exception as e:
print(f"Error in {f_name}: {e}")
continue
if all_results:
# Search alternate numbers
alt_numbers = set()
for result in all_results:
alt = result.get('alternate_number')
if alt and alt != 'N/A' and len(str(alt)) == 10:
alt_numbers.add(str(alt))
for alt_num in list(alt_numbers)[:3]:
alt_results = search_mobile_number(alt_num, con)
if alt_results:
all_results.extend(alt_results)
response = {
"success": True,
"api_by": "@sauravsingh2111",
"timestamp": datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z'),
"id": search_id,
"total_results": len(all_results),
"results": all_results,
"message": f"Found {len(all_results)} records for ID {search_id}"
}
else:
response = {
"success": False,
"api_by": "@sauravsingh2111",
"id": search_id,
"message": f"No details found for ID {search_id}",
"contact": "Please contact @sauravsingh2111 for support"
}
# ===== CACHE SAVE ===== (CHANGE 5)
set_cache(cache_key, response)
return response
# ===== HEALTH AND STATUS ENDPOINTS =====
@app.get("/search/advanced")
def advanced_search(
mobile: Optional[str] = Query(None),
id: Optional[str] = Query(None),
name: Optional[str] = Query(None),
fname: Optional[str] = Query(None),
circle: Optional[str] = Query(None),
alt: Optional[str] = Query(None)
):
if id:
return search_by_id(id)
if mobile:
return search_by_mobile(mobile)
return {
"status": "error",
"message": "Please use /search/{mobile} or /search/id/{id} for faster results."
}
@app.get("/stats")
def get_stats():
return {
"status": "success",
"total_files": len(index_map),
"total_records": "~85 Crore",
"cache_size": len(result_cache),
"search_methods": ["mobile", "id"]
}
@app.get("/health")
def health():
return {"status": "healthy", "database": "connected", "cache_size": len(result_cache)}
@app.get("/clear-cache")
def clear_cache():
"""Clear the result cache"""
global result_cache, cache_timestamps
result_cache = {}
cache_timestamps = {}
return {
"status": "success",
"message": "Cache cleared",
"api_by": "@sauravsingh2111"
}
@app.get("/")
def home():
return {
"status": "online",
"total_indexed": len(index_map),
"cache_size": len(result_cache),
"endpoints": {
"search_by_mobile": "/search/{mobile}",
"search_by_id": "/search/id/{id}",
"stats": "/stats",
"health": "/health",
"clear_cache": "/clear-cache"
}
}
@app.on_event("shutdown")
def shutdown_event():
if hasattr(thread_local, "connection"):
try:
thread_local.connection.close()
except:
pass