lockIn / app.py
Kankshi's picture
Update app.py
252d356 verified
Raw
History Blame Contribute Delete
6.79 kB
import os
import re
import pdfplumber
import pytesseract
import cv2
import numpy as np
import gradio as gr
from PIL import Image
from collections import Counter
from assistant.storage import (
generate_doc_id,
save_document,
load_document,
save_metadata,
get_all_docs,
)
# Configure Tesseract path (Windows only)
if os.name == 'nt':
pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"
FALLBACK = "This information is not available in your uploaded notes."
STOP_WORDS = {
"what", "is", "a", "an", "the", "of", "to", "in", "and", "why",
"how", "who", "when", "where", "are", "do", "does", "did", "for",
"on", "it", "this", "that", "tell", "me", "about", "define",
"explain", "give", "meaning", "can", "you", "please", "with",
"was", "will", "has", "have", "had", "be", "been", "by", "or",
"at", "from", "as", "its", "my", "your"
}
FACULTY_MAP = {
"KKD": "Dr. Kailas Devadkar",
"NR": "Dr. Nataasha Raul",
"JS": "Prof. Jignesh Sisodia"
}
# --- TIMETABLE LOGIC ---
def extract_text(file_path):
if file_path is None: return ""
ext = os.path.splitext(file_path)[1].lower()
text = ""
if ext == ".pdf":
with pdfplumber.open(file_path) as pdf:
for page in pdf.pages:
extracted = page.extract_text()
if extracted: text += extracted + "\n"
elif ext in [".png", ".jpg", ".jpeg"]:
img = cv2.imread(file_path)
if img is None:
img = Image.open(file_path)
text = pytesseract.image_to_string(img)
else:
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
text = pytesseract.image_to_string(thresh)
return text
def calculate_attendance_logic(raw_list):
"""Internal logic to calculate attendance metrics exactly as requested."""
subject_counts = Counter()
subject_to_faculty = {}
for entry in raw_list:
sub = entry.get("subject")
fac = entry.get("faculty")
subject_counts[sub] += 1
if fac and sub not in subject_to_faculty:
# Resolve actual faculty name if it exists in map, else keep code
subject_to_faculty[sub] = FACULTY_MAP.get(fac, fac)
subjects_data = {}
for subject, count in subject_counts.items():
weekly = count
monthly = weekly * 4
# Calculate safe_bunks (attendance >= 75%)
safe_bunks = 0
while monthly > 0 and ((monthly - (safe_bunks + 1)) / monthly) >= 0.75:
safe_bunks += 1
# Calculate classes_needed (attendance >= 75%)
classes_needed = 0
# If classes_needed/monthly < 0.75, it means we currently have 0% attendance
# Requirement: get to 75% of (monthly + extra_classes)
# However, following the user's previously working logic provided in file:
current_attended = 0 # assuming starting from scratch for simplicity as per previous logic
while monthly > 0 and (classes_needed / monthly) < 0.75:
classes_needed += 1
subjects_data[subject] = {
"faculty": subject_to_faculty.get(subject, "Unknown"),
"weekly": weekly,
"monthly": monthly,
"safe_bunks": safe_bunks,
"classes_needed": classes_needed
}
return subjects_data
def api_process_timetable(file):
if file is None: return {"error": "No file"}
text = extract_text(file.name)
raw_list = []
pattern = r"([A-Z]{2,})\s*/\s*(?:([A-Z]?)\s*/\s*)?([A-Z0-9]{2,})"
matches = re.finditer(pattern, text)
for match in matches:
raw_list.append({
"subject": match.group(1).strip(),
"faculty": match.group(3).strip()
})
subjects_data = calculate_attendance_logic(raw_list)
return {
"raw": raw_list,
"subjects": subjects_data
}
# --- ASSISTANT LOGIC (PURE RAG) ---
def api_upload_notes(file):
if file is None: return {"error": "No file"}
text = extract_text(file.name)
if not text.strip(): return {"error": "Empty text"}
doc_id = generate_doc_id()
save_document(doc_id, text)
save_metadata(doc_id, os.path.basename(file.name))
return {"doc_id": doc_id, "message": "Saved successfully"}
def _find_context(text, keywords):
sentences = re.split(r'[.!?\n]+', text)
matched = []
for i, sentence in enumerate(sentences):
sentence = sentence.strip()
if len(sentence) < 5: continue
sent_lower = sentence.lower()
for kw in keywords:
if kw in sent_lower:
matched.append({"text": sentence.replace('\n', ' '), "line": i+1})
break
if len(matched) >= 3: break
return matched
def api_ask_question(doc_id, question):
text = load_document(doc_id)
if text is None: return {"answer": "Doc not found"}
words = re.findall(r'\b\w+\b', question.lower())
keywords = [w for w in words if w not in STOP_WORDS and len(w) > 1]
keywords = keywords if keywords else words
matches = _find_context(text, keywords)
if not matches: return {"answer": FALLBACK, "source": None}
context = " ".join([m["text"] for m in matches])
first_match = matches[0]
return {
"answer": f"According to your notes: {context}",
"source": {
"line": first_match["line"],
"column": first_match["text"].lower().find(keywords[0]) + 1 if keywords else 1
}
}
# --- GRADIO UI ---
with gr.Blocks(title="Lumina Backend API") as demo:
gr.Markdown("# 🚀 Lumina Backend (Gradio API)")
with gr.Tab("Timetable Parser"):
tt_file = gr.File(label="Upload Timetable")
tt_btn = gr.Button("Parse")
tt_out = gr.JSON(label="Parsed Output")
tt_btn.click(api_process_timetable, inputs=tt_file, outputs=tt_out, api_name="process_timetable")
with gr.Tab("AI Assistant"):
with gr.Row():
note_file = gr.File(label="Upload Notes")
note_btn = gr.Button("Upload & Process")
note_out = gr.JSON(label="Doc ID Output")
with gr.Row():
ask_id = gr.Textbox(label="Document ID")
ask_q = gr.Textbox(label="Question")
ask_btn = gr.Button("Ask AI")
ask_out = gr.JSON(label="AI Answer")
note_btn.click(api_upload_notes, inputs=note_file, outputs=note_out, api_name="upload_notes")
ask_btn.click(api_ask_question, inputs=[ask_id, ask_q], outputs=ask_out, api_name="ask_question")
if __name__ == "__main__":
demo.launch(theme=gr.themes.Soft())