File size: 6,785 Bytes
c35855b
 
 
 
 
 
 
 
252d356
c35855b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252d356
 
 
 
 
 
c35855b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252d356
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c35855b
 
 
 
252d356
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c35855b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
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())