File size: 10,791 Bytes
feeaf83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
import re
from typing import Dict, List, Any
from datetime import datetime

class StudentClassifier:
    """
    Lightweight, fast NLP classifier for Student News that avoids heavy ML/LLM models.
    Uses keyword heuristics and regex to categorize, summarize, and extract metadata.
    """
    
    CATEGORIES = {
        "Scholarships & Internships": ["scholarship", "internship", "fellowship", "stipend", "student grant", "student funding", "financial assistance for students", "startup grant", "startup funding", "training program", "summer internship"],
        "Exams & Results": ["exam date", "results declared", "scorecard", "admit card", "nta", "jee main", "neet ug", "upsc prelims", "cut-off marks", "answer key", "date sheet", "counseling", "mock test", "merit list", "board exams", "entrance exam", "examination", "test result", "passing marks", "results out"],
        "Admissions & Courses": ["admission open", "apply now", "enrollment", "course curriculum", "degree program", "admission-notice", "university admission", "college admission", "enroll now", "last date to apply"],
        "Career & Jobs": ["campus placement", "fresher hiring", "graduate recruitment", "university placement", "off-campus drive", "fresher vacancy", "job for students", "career fair", "recruitment drive", "employment news", "hiring", "job alert"],
        "Education & Policy": ["education policy", "syllabus", "ugc", "cbse", "aicte", "nep 2020", "ministry of education", "school board", "educational reform", "government school", "higher education", "campus life", "university news", "school news", "student union", "academic research", "research paper", "phd thesis"]
    }

    PROFILES = {
        "School Student (10th/12th)": ["cbse", "ncert", "class 10", "class 12", "board exam", "icse", "state board", "school student", "secondary education"],
        "Engineering Aspirant": ["jee", "b.tech", "engineering entrance", "gate exam", "iit", "nit", "bitsat", "josaa", "engineering student", "iitian"],
        "Medical Aspirant": ["neet", "mbbs admission", "aiims", "bds", "pharmacy entrance", "ayush counseling", "jipmer", "pgimer", "medical student"],
        "Govt Job Aspirant": ["upsc notification", "ssc cgl", "bank po entrance", "rrb ntpc", "ibps po", "nda exam", "cds notification", "civil services prelims", "sbi po", "state psc", "government job", "employment news"],
        "Graduate/Techie": ["fresher", "placement", "startup", "coding", "software engineer", "developer", "internship", "graduation"]
    }

    AUTHORITIES = ["UGC", "CBSE", "AICTE", "NTA", "UPSC", "SSC", "Ministry of Education", "State Board", "University Grants Commission"]

    URGENCY_KEYWORDS = {
        "High": ["deadline", "tomorrow", "today", "urgent", "breaking", "last date", "closing", "alert"],
        "Medium": ["upcoming", "soon", "next week", "announced", "scheduled", "notification"],
        "Low": ["proposed", "planned", "expected", "future", "report", "study"]
    }
    
    STRICT_KEYWORDS = [
        "university admission", "college admission", "campus placement", "scholarship", 
        "exam result", "admit card", "student visa", "study abroad", "cut-off marks",
        "board exam", "jee main", "neet ug", "education policy", "fellowship", "internship",
        "syllabus", "ncert", "ugc", "cbse", "nta", "upsc notification", "ssc cgl",
        "startup grant", "startup funding", "hackathon", "coding competition",
        "educational", "learning", "academic", "placement drive", "hiring fresher",
        "recruitment drive", "campus hiring", "student achievement", "campus life",
        "exam update", "university news", "school news", "career guide",
        "intern", "stipend", "fresher", "placement", "graduation", "degree", "diploma",
        "research paper", "higher education", "student protest", "tuition fees"
    ]
    
    def _extract_specific_exam(self, text: str) -> str | None:
        # Match standard Indian exams as whole words
        exams = ["NEET", "JEE", "UPSC", "SSC", "CBSE", "CAT", "CLAT", "GATE", "ICSE", "CUET", "NDA", "CDS", "IBPS"]
        for exam in exams:
            if re.search(rf'\b{exam}\b', text, re.IGNORECASE):
                return exam
        return None
    
    def process_article(self, title: str, content: str) -> Dict[str, Any] | None:
        """
        Main pipeline to process a raw article into a structured student news item.
        Returns None if it does not strictly cover Student News keywords.
        """
        combined_text = f"{title} {content}".lower()
        
        # Strict context guard
        strict_matches = 0
        for kw in self.STRICT_KEYWORDS:
            if re.search(rf'\b{kw}\b', combined_text, re.IGNORECASE):
                strict_matches += 1
                
        specific_exam = self._extract_specific_exam(combined_text)
                
        # Relaxation: if the category is Education or it hits at least one strict keyword, it's a match
        if strict_matches == 0 and not specific_exam:
             # Check if it's generally about education
             if "education" in combined_text or "student" in combined_text:
                 # Allow it but maybe with lower trend score
                 pass 
             else:
                 return None
             
        category = self._assign_category(combined_text)
        tags = self._generate_tags(combined_text, category)
        profiles = self._assign_profiles(combined_text)
        direct_links = self._extract_links(text=content)  # Extract links only from content
        
        # Inject exact matching exam if found
        if specific_exam:
            tags.insert(0, f"#{specific_exam}")
            
        dates = self._extract_dates(combined_text)
        authority = self._extract_authority(combined_text)
        urgency = self._determine_urgency(combined_text)
        summary = self._generate_summary(content)
        trend_score = self._calculate_trend_score(combined_text, urgency)
        
        return {
            "title": title,
            "summary": summary,
            "category": category,
            "tags": tags,
            "profiles": profiles,
            "direct_links": direct_links,
            "important_dates": dates,
            "authority": authority,
            "urgency": urgency,
            "trend_score": trend_score
        }

    def _assign_category(self, text: str) -> str:
        best_match = "General Student News"
        max_score = 0
        
        for cat, keywords in self.CATEGORIES.items():
            score = 0
            for kw in keywords:
                if re.search(rf'\b{kw}\b', text, re.IGNORECASE):
                    score += 1
                    
            if score > max_score:
                max_score = score
                best_match = cat
                
        return best_match

    def _generate_tags(self, text: str, category: str) -> List[str]:
        tags = set()
        
        # Base tag on category
        if "Exam" in category: tags.add("#Exam")
        if "Scholarship" in category: tags.add("#Scholarship")
        if "Job" in category or "Career" in category: tags.add("#Job")
        if "Policy" in category: tags.add("#Policy")
        if "Abroad" in category: tags.add("#StudyAbroad")
        if "Tech" in category: tags.add("#Tech")
            
        # Specific keyword tags
        if "nta" in text or "jee" in text or "neet" in text: tags.add("#CompetitiveExams")
        if "cbse" in text or "board" in text: tags.add("#BoardExams")
        if "internship" in text: tags.add("#Internship")
        if "hackathon" in text or "coding" in text: tags.add("#Coding")
            
        return list(tags)[:4] # max 4 tags

    def _extract_dates(self, text: str) -> List[str]:
        # Very lightweight date extraction regex (e.g. 15th Jan, March 20, 2024-05-12)
        dates = []
        # Basic pattern for "DD Month" or "Month DD"
        month_pattern = r'(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*'
        date_pattern = rf'\b(\d{{1,2}}(?:st|nd|rd|th)?\s+{month_pattern}|{month_pattern}\s+\d{{1,2}})\b'
        
        matches = re.findall(date_pattern, text, re.IGNORECASE)
        # matches returns tuples because of groups in regex, clean it up
        for match in re.finditer(date_pattern, text, re.IGNORECASE):
            dates.append(match.group(0).title())
            
        return list(set(dates))[:2] # Top 2 dates extracted

    def _extract_authority(self, text: str) -> str:
        for auth in self.AUTHORITIES:
            if auth.lower() in text:
                return auth
        return "General"

    def _determine_urgency(self, text: str) -> str:
        for level, keywords in self.URGENCY_KEYWORDS.items():
            if any(kw in text for kw in keywords):
                return level
        return "Low"

    def _generate_summary(self, content: str) -> str:
        # Fast 3-line summary approximation by splitting sentences
        # Not perfect NLP, but extremely fast
        if not content:
            return ""
            
        # Clean text slightly
        text = re.sub(r'\s+', ' ', content).strip()
        
        # Split by periods roughly
        sentences = [s.strip() + "." for s in text.split('.') if len(s.strip()) > 15]
        
        if len(sentences) <= 3:
            return " ".join(sentences)
            
        # Take first 2 sentences and try to find one with numbers/dates for the 3rd
        summary_sents = sentences[:2]
        
        best_third = sentences[2]
        for s in sentences[2:6]:
            if re.search(r'\d', s):
                best_third = s
                break
                
        summary_sents.append(best_third)
        return " ".join(summary_sents)

    def _calculate_trend_score(self, text: str, urgency: str) -> int:
        score = 10
        if urgency == "High":
            score += 40
        elif urgency == "Medium":
            score += 20
            
        # Boost score based on strong keywords
        hot_keywords = ["deadline", "released", "declared", "announce", "breaking", "major"]
        score += sum(15 for kw in hot_keywords if kw in text)
        
        return min(score, 100)

    def _assign_profiles(self, text: str) -> List[str]:
        profiles = []
        for profile, keywords in self.PROFILES.items():
            if any(kw in text for kw in keywords):
                profiles.append(profile)
        # default to all if none matched
        if not profiles:
            profiles.append("General Student")
        return profiles

    def _extract_links(self, text: str) -> List[str]:
        # Extract http/https links
        urls = re.findall(r'https?://[^\s<>"]+|www\.[^\s<>"]+', text)
        return list(set(urls))[:2]