| 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, |
| ) |
|
|
| |
| 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" |
| } |
|
|
| |
|
|
| 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: |
| |
| subject_to_faculty[sub] = FACULTY_MAP.get(fac, fac) |
| |
| subjects_data = {} |
| for subject, count in subject_counts.items(): |
| weekly = count |
| monthly = weekly * 4 |
| |
| |
| safe_bunks = 0 |
| while monthly > 0 and ((monthly - (safe_bunks + 1)) / monthly) >= 0.75: |
| safe_bunks += 1 |
| |
| |
| classes_needed = 0 |
| |
| |
| |
| current_attended = 0 |
| 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 |
| } |
|
|
| |
|
|
| 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 |
| } |
| } |
|
|
| |
|
|
| 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()) |
|
|