| import random |
| import re |
| import json |
| import os |
|
|
| _HERE = os.path.dirname(os.path.abspath(__file__)) |
| CLUSTER_FILE = os.path.join(_HERE, "..", "tamil_clusters_v3.json") |
|
|
| |
| |
| |
|
|
| SEED_CLUSTERS: dict[str, list[str]] = { |
| "tamil_numbers": [ |
| "ஒன்று", "இரண்டு", "மூன்று", "நான்கு", "ஐந்து", |
| "ஆறு", "ஏழு", "எட்டு", "ஒன்பது", "பத்து", |
| "பதினொன்று", "பன்னிரண்டு", "பதினைந்து", "இருபது", "முப்பது", |
| ], |
| "numeric_years": [ |
| "1947", "1950", "1965", "1971", "1975", |
| "1983", "1991", "2000", "2004", "2009", "2011", "2019", "2023", |
| ], |
| "centuries": [ |
| "16ஆம் நூற்றாண்டு", "17ஆம் நூற்றாண்டு", "18ஆம் நூற்றாண்டு", |
| "19ஆம் நூற்றாண்டு", "20ஆம் நூற்றாண்டு", "21ஆம் நூற்றாண்டு", |
| ], |
| "cricket_nicknames": [ |
| "ஹிட்மேன்", "கேப்டன் கூல்", "லிட்டில் மாஸ்டர்", |
| "கிரிக்கெட் கடவுள்", "இந்திய சிங்கம்", "மாஸ்டர் பிளாஸ்டர்", |
| "ரன் மெஷின்", "வால் ஆஃப் இந்தியா", |
| ], |
| "cricket_phases": [ |
| "ஆரம்ப ஓவர்கள்", "மிடில் ஓவர்கள்", "டெத் ஓவர்கள்", |
| "பவர்பிளே", "இறுதி ஓவர்கள்", "பின்னர் ஓவர்கள்", |
| ], |
| "cricket_roles": [ |
| "தூணாக", "கேப்டனாக", "திறவுகோலாக", "முதுகெலும்பாக", |
| "வலிமையாக", "ஆதாரமாக", "தலைவராக", "நம்பகமான வீரராக", |
| ], |
| "cricket_double_centuries": [ |
| "ஒன்று", "இரண்டு", "மூன்று", "நான்கு", "ஐந்து", |
| ], |
| "cricket_players": [ |
| "விராட் கோஹ்லி", "ரோஹித் சர்மா", "எம்.எஸ். தோனி", |
| "சீமன் ஹெட்மையர்", "கேன் வில்லியம்சன்", "ஜோ ரூட்", |
| "டேவிட் வார்னர்", "ஸ்டீவ் ஸ்மித்", "பெயின் ஸ்டோக்ஸ்", |
| ], |
| "cricket_shots": [ |
| "சிக்ஸர்", "ஃபோர்", "கட்", "புல்", "ஹூக்", |
| "ஸ்வீப்", "டிரைவ்", "ஃப்ளிக்", "லேப்", |
| ], |
| "match_formats": [ |
| "டெஸ்ட் போட்டி", "ஒருநாள் போட்டி", "T20 போட்டி", |
| "T10 போட்டி", "IPL போட்டி", "உலகக்கோப்பை போட்டி", |
| ], |
| "bowling_types": [ |
| "வேகப்பந்து", "ஸ்பின் பந்து", "மீடியம் பேஸ்", |
| "ஆஃப் ஸ்பின்", "லெக் ஸ்பின்", "யார்க்கர்", "பவுன்சர்", |
| ], |
| "indian_states": [ |
| "தமிழ்நாடு", "கேரளா", "கர்நாடகா", "ஆந்திரா", |
| "தெலுங்கானா", "மகாராஷ்டிரா", "குஜராத்", "ராஜஸ்தான்", |
| "உத்திரப்பிரதேசம்", "மேற்கு வங்காளம்", "பஞ்சாப்", |
| ], |
| "tamilnadu_cities": [ |
| "சென்னை", "மதுரை", "கோயம்புத்தூர்", "திருச்சி", |
| "சேலம்", "திருநெல்வேலி", "தஞ்சாவூர்", "வேலூர்", |
| "ஈரோடு", "திருப்பூர்", "கன்னியாகுமரி", |
| ], |
| "countries": [ |
| "இந்தியா", "இலங்கை", "பாகிஸ்தான்", "வங்கதேசம்", |
| "ஆஸ்திரேலியா", "இங்கிலாந்து", "தென்னாப்பிரிக்கா", |
| "நியூஸிலாந்து", "வெஸ்ட் இண்டீஸ்", "சீனா", "அமெரிக்கா", |
| ], |
| "dynasties": [ |
| "சோழர்", "சேரர்", "பாண்டியர்", "பல்லவர்", |
| "நாயக்கர்", "மராத்தியர்", "விஜயநகரம்", "முகலாயர்", |
| ], |
| "historical_eras": [ |
| "பழங்கற்காலம்", "புதிய கற்காலம்", "வெண்கலக் காலம்", |
| "இரும்புக்காலம்", "சங்ககாலம்", "இடைக்காலம்", |
| "காலனித்துவ காலம்", "நவீன காலம்", |
| ], |
| "tamil_literature": [ |
| "திருக்குறள்", "சிலப்பதிகாரம்", "மணிமேகலை", |
| "புறநானூறு", "அகநானூறு", "தொல்காப்பியம்", |
| "கம்பராமாயணம்", "பதிற்றுப்பத்து", |
| ], |
| "tamil_poets": [ |
| "திருவள்ளுவர்", "இளங்கோவடிகள்", "கம்பர்", |
| "ஔவையார்", "சுப்பிரமணிய பாரதி", "பாரதிதாசன்", |
| "கண்ணதாசன்", "வாலி", |
| ], |
| "freedom_fighters": [ |
| "மகாத்மா காந்தி", "சுபாஷ் சந்திர போஸ்", "பகத் சிங்", |
| "வீரபாண்டிய கட்டபொம்மன்", "சுப்பிரமணிய பாரதி", |
| "வ.உ.சிதம்பரனார்", "திலகர்", "லாலா லஜபதி ராய்", |
| ], |
| "indian_rivers": [ |
| "காவிரி", "கங்கை", "யமுனை", "கோதாவரி", |
| "கிருஷ்ணா", "நர்மதா", "தாமிரபரணி", "வைகை", |
| ], |
| "directions": [ |
| "வடக்கு", "தெற்கு", "கிழக்கு", "மேற்கு", |
| "வடகிழக்கு", "தென்கிழக்கு", "வடமேற்கு", "தென்மேற்கு", |
| ], |
| "tamil_months": [ |
| "தை", "மாசி", "பங்குனி", "சித்திரை", |
| "வைகாசி", "ஆனி", "ஆடி", "ஆவணி", |
| "புரட்டாசி", "ஐப்பசி", "கார்த்திகை", "மார்கழி", |
| ], |
| "days_of_week": [ |
| "திங்கள்", "செவ்வாய்", "புதன்", "வியாழன்", |
| "வெள்ளி", "சனி", "ஞாயிறு", |
| ], |
| "colours": [ |
| "சிவப்பு", "நீலம்", "பச்சை", "மஞ்சள்", |
| "வெள்ளை", "கருப்பு", "ஆரஞ்சு", "வெள்ளி நிறம்", |
| ], |
| "animals": [ |
| "சிங்கம்", "புலி", "யானை", "குதிரை", |
| "மான்", "நரி", "ஓநாய்", "கரடி", |
| ], |
| "politicians": [ |
| "ஜவகர்லால் நேரு", "இந்திரா காந்தி", "ராஜீவ் காந்தி", |
| "அடல் பிஹாரி வாஜ்பாயி", "மன்மோகன் சிங்", |
| "நரேந்திர மோடி", "எம். கே. ஸ்டாலின்", "எடப்பாடி பழனிசாமி", |
| "ஜெயலலிதா", "கருணாநிதி", "அம்பேத்கர்", |
| ], |
| "ipl_teams": [ |
| "சென்னை சூப்பர் கிங்ஸ்", "மும்பை இந்தியன்ஸ்", |
| "ரோயல் சேலஞ்சர்ஸ் பெங்களூரு", "கோல்கத்தா நைட் ரைடர்ஸ்", |
| "டெல்லி கேபிடல்ஸ்", "பஞ்சாப் கிங்ஸ்", |
| "ராஜஸ்தான் ராயல்ஸ்", "சன்ரைஸர்ஸ் ஹைதராபாத்", |
| "லக்னோ சூப்பர் ஜெயன்ட்ஸ்", "குஜராத் டைட்டன்ஸ்", |
| ], |
| |
| "political_bodies": [ |
| "சட்டமன்றம்", "நாடாளுமன்றம்", "அமைச்சரவை", "தேர்தல் ஆணையம்", |
| "உச்சநீதிமன்றம்", "உயர்நீதிமன்றம்", "ஆளுநர்", "மாநில அரசு", |
| "மத்திய அரசு", "நிர்வாகம்", "அரசமைப்பு", "பாராளுமன்றம்", |
| "சட்டமன்றத்தில்", "நாடாளுமன்றத்தில்", "அமைச்சரவையின்", |
| "மாநகராட்சி", "பேரூராட்சி", "ஊராட்சி", "மாவட்ட நிர்வாகம்", |
| "அரசு துறைகள்", "துறை செயலகம்", "ஆட்சியர் அலுவலகம்", |
| ], |
| "political_entities": [ |
| "கட்சிகள்", "கூட்டணிகள்", "இயக்கங்கள்", "அமைப்புகள்", |
| "தொண்டர்கள்", "தலைவர்கள்", "வேட்பாளர்கள்", "வாக்காளர்கள்", |
| "செயற்குழு", "மாவட்ட கமிட்டி", "தேர்தல் பிரசாரம்", |
| "கட்சி", "கூட்டணி", "இயக்கம்", "அமைப்பு", |
| "முன்னணிகள்", "கூட்டமைப்புகள்", "அரசியல் கட்சிகள்", |
| "எதிர்க்கட்சிகள்", "ஆளுங்கட்சி", "பொதுக்கூட்டங்கள்", |
| ], |
| "political_processes": [ |
| "தேர்தல்", "வாக்கெடுப்பு", "வாக்குப்பதிவு", "தொகுதி", |
| "மறுசீரமைப்பு", "பதிவுகள்", "கட்டுப்பாடுகள்", "விதிகள்", |
| "கொள்கைகள்", "மசோதாக்கள்", "சட்டங்கள்", "ஒதுக்கீடு", |
| ], |
| "political_concepts": [ |
| "அரசியல்", "ஜனநாயகம்", "சமூக நீதி", "பொருளாதாரம்", |
| "கல்வி உரிமை", "ஒதுக்கீட்டு", "இட ஒதுக்கீடு", "சுதந்திரம்", |
| "சமத்துவம்", "மதச்சார்பின்மை", "கூட்டாட்சி", |
| ], |
| "tamil_regions": [ |
| "தமிழ்நாட்டின்", "தமிழகத்தின்", "சென்னையின்", "மதுரையின்", |
| "கோயம்புத்தூரின்", "திருச்சியின்", "மாநிலத்தின்", "மாவட்டத்தின்", |
| "பகுதியின்", "நகரத்தின்", "கிராமத்தின்", |
| "இந்தியாவின்", "கேரளாவின்", "கர்நாடகாவின்", "ஆந்திராவின்", |
| "வட இந்தியாவின்", "தென்னிந்தியாவின்", "வட மாநிலத்தின்", |
| "பிராந்தியத்தின்", "நாட்டின்", "மாநகரத்தின்", |
| ], |
| "governance_terms": [ |
| "ஆணையத்தின்", "துறையின்", "அமைப்பின்", "நிறுவனத்தின்", |
| "குழுவின்", "கமிட்டியின்", "மன்றத்தின்", "சபையின்", |
| "அரசின்", "நிர்வாகத்தின்", "அதிகாரிகளின்", |
| ], |
| |
| "economics_terms": [ |
| "சந்தை", "வங்கி", "முதலீடு", "ஏற்றுமதி", "இறக்குமதி", |
| "உற்பத்தி", "தொழில்", "வர்த்தகம்", "பொருளாதாரம்", "வளர்ச்சி", |
| "நிதி", "பட்ஜெட்", "வரி", "கடன்", "வட்டி", |
| ], |
| |
| "science_terms": [ |
| "விஞ்ஞானம்", "தொழில்நுட்பம்", "ஆராய்ச்சி", "கண்டுபிடிப்பு", |
| "விண்வெளி", "அணு ஆற்றல்", "மருத்துவம்", "இயற்பியல்", |
| "வேதியியல்", "உயிரியல்", "கணினி", "செயற்கோள்", |
| ], |
| |
| "education_terms": [ |
| "பள்ளி", "கல்லூரி", "பல்கலைக்கழகம்", "மாணவர்கள்", |
| "ஆசிரியர்கள்", "பாடத்திட்டம்", "தேர்வு", "படிப்பு", |
| "சான்றிதழ்", "பட்டம்", "கல்வி கட்டணம்", "உதவித்தொகை", |
| ], |
| |
| "cinema_terms": [ |
| "திரைப்படம்", "நடிகர்", "நடிகை", "இயக்குநர்", "தயாரிப்பாளர்", |
| "இசையமைப்பாளர்", "வசனகர்த்தா", "விருது", "பாடல்", |
| "படப்பிடிப்பு", "வெளியீடு", "வசூல்", |
| ], |
| |
| "health_terms": [ |
| "மருத்துவமனை", "நோய்", "சிகிச்சை", "மருந்து", "டாக்டர்", |
| "தடுப்பூசி", "ஆரோக்கியம்", "அறுவை சிகிச்சை", "நோயாளி", |
| "மருத்துவர்", "ஆய்வகம்", "சுகாதாரம்", |
| ], |
| |
| "environment_terms": [ |
| "காடு", "மரம்", "ஆறு", "மழை", "காலநிலை", |
| "மாசுபாடு", "பசுமை", "இயற்கை", "சுற்றுச்சூழல்", |
| "கடல்", "மலை", "பாலைவனம்", "வெள்ளம்", |
| ], |
| |
| "history_terms": [ |
| "சரித்திரம்", "போர்", "மன்னர்", "சாம்ராஜ்யம்", |
| "படையெடுப்பு", "நினைவுச்சின்னம்", "கல்வெட்டு", |
| "வரலாறு", "பண்டைய காலம்", "நாகரிகம்", |
| ], |
| } |
|
|
| |
| |
| |
|
|
| KEYWORD_TO_CLUSTER: list[tuple[list[str], str]] = [ |
| |
| (["எண்ணிக்கை", "எத்தனை", "மொத்தம்", "இரட்டைச் சதம்"], "cricket_double_centuries"), |
| (["சிறப்புப் பெயர்", "அழைக்கப்படுகிறார்", "செல்லப்பெயர்"], "cricket_nicknames"), |
| (["ஓவர்", "பவர்பிளே", "இனிங்ஸ்"], "cricket_phases"), |
| (["அணியின்", "விளங்கினார்", "ஆட்டத்தால்"], "cricket_roles"), |
| (["ஃபார்மேட்", "போட்டி வகை"], "match_formats"), |
| (["ஷாட்", "அடி"], "cricket_shots"), |
| (["பந்து வீசும்", "போலிங்"], "bowling_types"), |
| (["கிரிக்கெட் வீரர்", "பேட்ஸ்மேன்"], "cricket_players"), |
| (["முறை", "தடவை", "சாம்பியன்"], "tamil_numbers"), |
| (["அணி", "கிளப்", "ஐபிஎல்", "IPL"], "ipl_teams"), |
| |
| (["வம்சம்", "மன்னர்", "பேரரசு"], "dynasties"), |
| (["காலம்", "நூற்றாண்டு", "யுகம்"], "historical_eras"), |
| (["நூல்", "காவியம்", "இலக்கியம்", "எழுதியவர்", "படைப்பு"], "tamil_literature"), |
| (["கவிஞர்", "புலவர்", "இயற்றியவர்"], "tamil_poets"), |
| (["விடுதலை", "போராட்டம்", "தியாகி", "சுதந்திரம்"], "freedom_fighters"), |
| |
| (["மாவட்டம்", "நகரம்", "தலைநகரம்"], "tamilnadu_cities"), |
| (["மாநிலம்"], "indian_states"), |
| (["நாடு", "தேசம்"], "countries"), |
| (["ஆறு", "நதி"], "indian_rivers"), |
| (["திசை"], "directions"), |
| |
| (["மாதம்", "மாசம்"], "tamil_months"), |
| (["நாள்", "வாரம்"], "days_of_week"), |
| |
| (["நிறம்", "வர்ணம்"], "colours"), |
| (["விலங்கு", "மிருகம்", "தேசிய விலங்கு", "தேசிய பறவை", "பறவை"], "animals"), |
| |
| (["சட்டமன்றம்", "நாடாளுமன்றம்", "மசோதா"], "political_bodies"), |
| (["அமைச்சரவை", "துறைகள்", "நிர்வாக"], "political_bodies"), |
| (["கட்சி", "கூட்டணி", "இயக்கம்", "வேட்பாளர்", "வாக்காளர்"], "political_entities"), |
| (["கூட்டணி", "காலகட்டம்", "பிராந்திய"], "political_entities"), |
| (["தேர்தல்", "வாக்கு", "தொகுதி", "மறுசீரமைப்பு"], "political_processes"), |
| (["அரசியல்", "ஜனநாயகம்", "சமூக நீதி", "ஒதுக்கீடு", "பண்பாடு"], "political_concepts"), |
| (["தமிழ்நாட்டின்", "தமிழகம்", "பரிணாமம்"], "tamil_regions"), |
| (["ஆணையம்", "குழு", "கமிட்டி", "நிறுவனம்"], "governance_terms"), |
| (["பிரதமர்", "ஜனாதிபதி", "முதல்வர்", "ஆளுநர்"], "politicians"), |
| |
| (["பொருளாதார", "வர்த்தக", "சந்தை", "வங்கி", "முதலீடு"], "economics_terms"), |
| (["விஞ்ஞான", "தொழில்நுட்ப", "ஆராய்ச்சி", "விண்வெளி"], "science_terms"), |
| (["கல்வி", "பள்ளி", "கல்லூரி", "மாணவர்", "ஆசிரியர்"], "education_terms"), |
| (["சினிமா", "திரைப்படம்", "நடிகர்", "இயக்குநர்", "விருது"], "cinema_terms"), |
| (["மருத்துவ", "நோய்", "சிகிச்சை", "மருந்து", "டாக்டர்"], "health_terms"), |
| (["சுற்றுச்சூழல்", "காடு", "மரம்", "காலநிலை", "மாசுபாடு"], "environment_terms"), |
| (["வரலாற்று", "சரித்திர", "போர்", "படையெடுப்பு"], "history_terms"), |
| ] |
|
|
| |
| |
| |
|
|
| DOMAIN_SIGNALS: list[tuple[list[str], str]] = [ |
| (["அரசியல்", "கட்சி", "தேர்தல்", "சட்டமன்றம்", "நாடாளுமன்றம்", |
| "அமைச்சரவை", "வாக்காளர்", "ஆணையம்", "மசோதா", "கூட்டணி", |
| "நிர்வாக", "துறை", "சட்டம்", "கொள்கை", "பிரதமர்", |
| "முதல்வர்", "ஆளுநர்", "ஒதுக்கீடு", "பண்பாடு", "பரிணாமம்", |
| "ஜனநாயகம்", "சமூக நீதி", "மறுசீரமைப்பு", "வாக்குப்பதிவு"], |
| "auto_politics"), |
|
|
| (["பொருளாதார", "வர்த்தக", "சந்தை", "வங்கி", "முதலீடு", |
| "ஏற்றுமதி", "இறக்குமதி", "வளர்ச்சி", "உற்பத்தி", "தொழில்", |
| "நிதி", "பட்ஜெட்", "வரி", "கடன்"], |
| "auto_economics"), |
|
|
| (["விஞ்ஞான", "தொழில்நுட்ப", "ஆராய்ச்சி", "கண்டுபிடிப்பு", |
| "விண்வெளி", "அணு", "இயற்பியல்", "வேதியியல்", "உயிரியல்", |
| "கணினி", "செயற்கோள்", "நுட்பம்"], |
| "auto_science"), |
|
|
| (["கல்வி", "பள்ளி", "கல்லூரி", "பல்கலைக்கழக", "மாணவர்", |
| "ஆசிரியர்", "பாடம்", "தேர்வு", "படிப்பு", "சான்றிதழ்", |
| "பட்டம்", "உதவித்தொகை"], |
| "auto_education"), |
|
|
| (["சினிமா", "திரைப்படம்", "நடிகர்", "இயக்குநர்", "இசை", |
| "பாடல்", "விருது", "நடிகை", "தயாரிப்பாளர்", "வசூல்"], |
| "auto_cinema"), |
|
|
| (["விளையாட்டு", "கிரிக்கெட்", "ஃபுட்பால்", "டென்னிஸ்", |
| "ஒலிம்பிக்", "போட்டி", "வெற்றி", "தோல்வி", "வீரர்", |
| "பயிற்சியாளர்", "மைதானம்"], |
| "auto_sports"), |
|
|
| (["மருத்துவ", "மருத்துவமனை", "நோய்", "சிகிச்சை", "மருந்து", |
| "டாக்டர்", "தடுப்பூசி", "ஆரோக்கியம்", "அறுவை", "நோயாளி", |
| "சுகாதார"], |
| "auto_health"), |
|
|
| (["சுற்றுச்சூழல்", "காடு", "மரம்", "மழை", "காலநிலை", |
| "மாசுபாடு", "பசுமை", "இயற்கை", "கடல்", "மலை", |
| "வெள்ளம்", "வறட்சி"], |
| "auto_environment"), |
|
|
| (["வரலாற்று", "சரித்திர", "போர்", "மன்னர்", "சாம்ராஜ்யம்", |
| "படையெடுப்பு", "நினைவுச்சின்னம்", "கல்வெட்டு", "நாகரிகம்"], |
| "auto_history"), |
|
|
| (["சமயம்", "மதம்", "கோயில்", "பூஜை", "திருவிழா", |
| "பண்டிகை", "வழிபாடு", "ஆகமம்", "வேதம்"], |
| "auto_religion"), |
|
|
| (["விவசாய", "நில", "பயிர்", "உழவு", "நீர்ப்பாசனம்", |
| "விளை", "தோட்டம்", "கால்நடை", "உரம்"], |
| "auto_agriculture"), |
| ] |
|
|
| |
| |
| |
|
|
| def load_clusters() -> dict[str, list[str]]: |
| clusters = {k: list(v) for k, v in SEED_CLUSTERS.items()} |
| if os.path.exists(CLUSTER_FILE): |
| try: |
| with open(CLUSTER_FILE, "r", encoding="utf-8") as f: |
| saved = json.load(f) |
| for cname, items in saved.items(): |
| bucket = clusters.setdefault(cname, []) |
| for item in items: |
| if item not in bucket: |
| bucket.append(item) |
| except (json.JSONDecodeError, OSError): |
| pass |
| return clusters |
|
|
|
|
| def save_clusters(clusters: dict[str, list[str]]) -> None: |
| with open(CLUSTER_FILE, "w", encoding="utf-8") as f: |
| json.dump(clusters, f, ensure_ascii=False, indent=2) |
|
|
|
|
| CLUSTERS: dict[str, list[str]] = load_clusters() |
|
|
| |
| |
| |
|
|
| def _answer_shape(answer: str) -> str: |
| answer = answer.strip() |
| if re.fullmatch(r"\d{4}", answer): |
| return "digits_4" |
| if re.fullmatch(r"\d+", answer): |
| return f"digits_{len(answer)}" |
| if re.search(r"\d", answer): |
| return "mixed_digit_tamil" |
| suffix_shapes = [ |
| ("போர்", "shape_war"), ("இயக்கம்", "shape_movement"), |
| ("காலம்", "shape_era"), ("நூல்", "shape_book"), |
| ("ஆறு", "shape_river"), ("நகர்", "shape_city"), |
| ("மாநிலம்", "shape_state"), ("நாடு", "shape_country"), |
| ("மாதம்", "shape_month"), ("நாள்", "shape_day"), |
| ("ஆக", "shape_role_suffix"), ("ராக", "shape_role_suffix"), |
| ("வராக", "shape_role_suffix"), ("ஓவர்கள்", "shape_overs"), |
| ("கிங்ஸ்", "shape_ipl_team"), ("ரைடர்ஸ்", "shape_ipl_team"), |
| ("வாரியர்ஸ்", "shape_ipl_team"), ("இந்தியன்ஸ்", "shape_ipl_team"), |
| ] |
| for suffix, shape in suffix_shapes: |
| if answer.endswith(suffix): |
| return shape |
| wc = len(answer.split()) |
| if wc >= 3: |
| return "tamil_phrase_long" |
| if wc == 2: |
| return "tamil_phrase_2w" |
| return "tamil_single_word" |
|
|
| |
| |
| |
|
|
| def detect_cluster_for(answer: str, question: str, context: str = "") -> str: |
| combined = question + " " + context |
|
|
| |
| for cname, items in CLUSTERS.items(): |
| if answer in items: |
| return cname |
|
|
| |
| if re.fullmatch(r"\d{4}", answer): |
| return "numeric_years" |
| if re.search(r"\d+ஆம் நூற்றாண்டு", answer): |
| return "centuries" |
| if any(k in answer for k in ["காலம்", "கி.மு", "கி.பி"]): |
| return "historical_eras" |
| if re.fullmatch(r"\d+", answer): |
| return "tamil_numbers" |
|
|
| |
| for keywords, cluster in KEYWORD_TO_CLUSTER: |
| if any(kw in combined for kw in keywords): |
| return cluster |
|
|
| |
| if answer.endswith("காலம்"): return "historical_eras" |
| if answer.endswith("நூற்றாண்டு"): return "centuries" |
| if answer.endswith(("ஆக", "ராக", "வராக")): return "cricket_roles" |
| if answer.endswith(("ஓவர்கள்", "பிளே")): return "cricket_phases" |
| if answer.endswith(("நாடு", "தேசம்")): return "countries" |
| if answer.endswith("மாதம்"): return "tamil_months" |
| if answer.endswith(("கிங்ஸ்", "ரைடர்ஸ்", |
| "இந்தியன்ஸ்", "வாரியர்ஸ்", |
| "கேபிடல்ஸ்", "டைட்டன்ஸ்", |
| "ஜெயன்ட்ஸ்", "ராயல்ஸ்")): return "ipl_teams" |
|
|
| return "__unknown__" |
|
|
|
|
| def _find_or_create_domain_cluster(answer: str, question: str, context: str) -> str: |
| """ |
| Use sentence context to detect the domain and place the unknown word |
| into a meaningful auto_<domain> cluster instead of a shape-based one. |
| Falls back to shape only when domain is completely undetectable. |
| """ |
| combined = (question + " " + context).strip() |
|
|
| for keywords, cluster_name in DOMAIN_SIGNALS: |
| if any(kw in combined for kw in keywords): |
| if cluster_name not in CLUSTERS: |
| CLUSTERS[cluster_name] = [] |
| print(f" [NEW DOMAIN CLUSTER] '{cluster_name}' created for: '{answer}'") |
| return cluster_name |
|
|
| |
| shape = _answer_shape(answer) |
| cluster_name = f"auto_{shape}" |
| if cluster_name not in CLUSTERS: |
| CLUSTERS[cluster_name] = [] |
| return cluster_name |
|
|
| |
| |
| |
|
|
| def _pos_domain_routing_table() -> dict: |
| return { |
| ("PROPN", "auto_politics"): "politicians", |
| ("NOUN", "auto_politics"): "political_entities", |
| ("PROPN", "auto_history"): "freedom_fighters", |
| ("NOUN", "auto_history"): "history_terms", |
| ("PROPN", "auto_sports"): "cricket_players", |
| ("NOUN", "auto_sports"): "match_formats", |
| ("PROPN", "auto_cinema"): "cinema_terms", |
| ("NOUN", "auto_cinema"): "cinema_terms", |
| ("PROPN", "auto_geography"): "tamilnadu_cities", |
| ("NOUN", "auto_geography"): "indian_states", |
| ("NOUN", "auto_economics"): "economics_terms", |
| ("PROPN", "auto_economics"): "economics_terms", |
| ("NOUN", "auto_science"): "science_terms", |
| ("NOUN", "auto_education"): "education_terms", |
| ("NOUN", "auto_health"): "health_terms", |
| ("NOUN", "auto_environment"): "environment_terms", |
| ("PROPN", "auto_literature"): "tamil_poets", |
| ("NOUN", "auto_literature"): "tamil_literature", |
| } |
|
|
|
|
| def _find_most_similar_cluster(answer: str, domain_cluster: str, |
| pos_tag: str = "NOUN") -> str: |
| """ |
| Refine a broad auto_<domain> cluster to the best named sub-cluster |
| using the POS+domain routing table built by _pos_domain_routing_table(). |
| |
| Falls back to domain_cluster if no rule matches or the target cluster |
| does not exist yet in CLUSTERS. |
| """ |
| routing = _pos_domain_routing_table() |
|
|
| |
| target = routing.get((pos_tag, domain_cluster)) |
| if target and target in CLUSTERS: |
| return target |
|
|
| |
| target = routing.get(("NOUN", domain_cluster)) |
| if target and target in CLUSTERS: |
| return target |
|
|
| return domain_cluster |
|
|
|
|
| def auto_register(answer: str, question: str, context: str = "", |
| pos_tag: str = "NOUN") -> str: |
| """ |
| Detect the best cluster for `answer`, register it, and persist to disk. |
| |
| Parameters |
| ---------- |
| answer : root form of the answer word |
| question : the MCQ question string (used for keyword matching) |
| context : original sentence (used for domain detection) |
| pos_tag : Stanza UPOS tag of the answer word — NOUN / PROPN / VERB etc. |
| Drives sub-cluster routing when the word is not yet in the KB. |
| |
| Flow |
| ---- |
| 1. Direct lookup → already in KB, return immediately (no write). |
| 2. Structural pattern (4-digit year, century string, plain digit). |
| 3. Keyword match from question+context → named cluster. |
| 4. Answer suffix heuristics. |
| 5. Domain detection from context → broad auto_<domain> bucket. |
| 6. POS + domain routing → refined named sub-cluster. |
| """ |
| cluster_name = detect_cluster_for(answer, question, context) |
|
|
| if cluster_name == "__unknown__": |
| domain_cluster = _find_or_create_domain_cluster(answer, question, context) |
| cluster_name = _find_most_similar_cluster(answer, domain_cluster, pos_tag) |
| print(f" [auto_register] '{answer}' ({pos_tag}) " |
| f"→ '{cluster_name}' (domain: {domain_cluster})") |
|
|
| bucket = CLUSTERS.setdefault(cluster_name, []) |
| if answer not in bucket: |
| bucket.append(answer) |
| save_clusters(CLUSTERS) |
| print(f" [KB] Added '{answer}' to cluster '{cluster_name}'") |
|
|
| return cluster_name |
|
|
|
|
| |
| CRICKET_ONLY_CLUSTERS = { |
| "cricket_shots", "cricket_nicknames", "cricket_phases", |
| "cricket_roles", "cricket_players", "bowling_types", |
| "cricket_double_centuries", "match_formats", "ipl_teams", |
| } |
|
|
| |
| CLUSTER_GROUPS: list[set[str]] = [ |
| {"political_bodies", "political_entities", "political_processes", |
| "political_concepts", "governance_terms", "tamil_regions", |
| "politicians", "auto_politics"}, |
| {"tamil_literature", "tamil_poets", "freedom_fighters", |
| "dynasties", "historical_eras", "history_terms", "auto_history"}, |
| {"indian_states", "tamilnadu_cities", "countries", |
| "indian_rivers", "directions", "tamil_regions"}, |
| {"economics_terms", "auto_economics"}, |
| {"science_terms", "auto_science"}, |
| {"education_terms", "auto_education"}, |
| {"cinema_terms", "auto_cinema"}, |
| {"health_terms", "auto_health"}, |
| {"environment_terms", "auto_environment"}, |
| {"auto_religion"}, |
| {"auto_agriculture"}, |
| {"cricket_shots", "cricket_nicknames", "cricket_phases", |
| "cricket_roles", "cricket_players", "bowling_types", |
| "cricket_double_centuries", "match_formats", "ipl_teams", |
| "auto_sports"}, |
| ] |
|
|
|
|
| def _related_clusters(cluster_name: str) -> set[str]: |
| """Return all cluster names in the same group as cluster_name.""" |
| for group in CLUSTER_GROUPS: |
| if cluster_name in group: |
| return group - {cluster_name} |
| return set() |
|
|
|
|
| def _is_cricket_cluster(cluster_name: str) -> bool: |
| return cluster_name in CRICKET_ONLY_CLUSTERS |
|
|
|
|
| def generate_distractors(answer: str, question: str, |
| context: str = "", k: int = 3, |
| pos_tag: str = "NOUN") -> list[str]: |
| """Return k plausible-but-wrong options for the given answer.""" |
| cluster_name = auto_register(answer, question, context, pos_tag=pos_tag) |
| is_cricket = _is_cricket_cluster(cluster_name) |
|
|
| |
| pool = [c for c in CLUSTERS.get(cluster_name, []) if c != answer] |
|
|
| |
| if len(pool) < k: |
| related = _related_clusters(cluster_name) |
| for cname in related: |
| for it in CLUSTERS.get(cname, []): |
| if it not in pool and it != answer: |
| pool.append(it) |
|
|
| |
| |
| if len(pool) < k: |
| my_shape = _answer_shape(answer) |
| for cname, items in CLUSTERS.items(): |
| if cname == cluster_name: |
| continue |
| if not is_cricket and _is_cricket_cluster(cname): |
| continue |
| for it in items: |
| if _answer_shape(it) == my_shape and it not in pool and it != answer: |
| pool.append(it) |
|
|
| |
| if len(pool) < k: |
| for cname, items in CLUSTERS.items(): |
| if not is_cricket and _is_cricket_cluster(cname): |
| continue |
| for it in items: |
| if it != answer and it not in pool: |
| pool.append(it) |
|
|
| random.shuffle(pool) |
| return pool[:k] |
|
|
| def build_mcq(question: str, answer: str, context: str = "") -> dict: |
| distractors = generate_distractors(answer, question, context, k=3) |
| options = list(dict.fromkeys([answer] + distractors))[:4] |
| if len(options) < 4: |
| cname = auto_register(answer, question, context) |
| extra = [c for c in CLUSTERS.get(cname, []) if c not in options] |
| random.shuffle(extra) |
| options += extra[:4 - len(options)] |
| random.shuffle(options) |
| labeled = {chr(65 + i): opt for i, opt in enumerate(options[:4])} |
| correct_label = next(lbl for lbl, val in labeled.items() if val == answer) |
| return { |
| "question": question, |
| "options": labeled, |
| "answer": answer, |
| "correct_label": correct_label, |
| } |