Spaces:
Runtime error
Runtime error
| import unittest | |
| from unittest import mock | |
| from api.services import search_service | |
| class QueryRoutingToggleTests(unittest.TestCase): | |
| def test_personalized_query_expands_route_with_profile_terms(self): | |
| route = { | |
| "original_query": "๋ด ํ๊ณผ์ ๋ง๋ ์ฐฝ์ ๊ณต๋ชจ์ ์ฐพ์์ค", | |
| "search_query": "๋ด ํ๊ณผ์ ๋ง๋ ์ฐฝ์ ๊ณต๋ชจ์ ์ฐพ์์ค ์ฐฝ์ ๊ณต๋ชจ์ ", | |
| "intent": "๊ธฐํ", | |
| "confidence": 0.7, | |
| "method": "heuristic", | |
| "features": { | |
| "must_keep_terms": ["๊ณต๋ชจ์ "], | |
| "expanded_terms": [], | |
| "entities": ["๊ณต๋ชจ์ "], | |
| "time_terms": [], | |
| "section_targets": [], | |
| }, | |
| } | |
| profile = { | |
| "college": "IT๊ณต๊ณผ๋ํ", | |
| "track": "์ปดํจํฐ๊ณตํ๋ถ", | |
| "grade": "3ํ๋ ", | |
| "interests": ["์ฐฝ์ ", "AI"], | |
| "region": "์์ธ", | |
| } | |
| personalized = search_service._apply_personalization_to_route( | |
| route, | |
| "๋ด ํ๊ณผ์ ๋ง๋ ์ฐฝ์ ๊ณต๋ชจ์ ์ฐพ์์ค", | |
| profile, | |
| ) | |
| self.assertEqual(personalized["intent"], "conditional") | |
| self.assertGreaterEqual(personalized["confidence"], 0.88) | |
| self.assertTrue(personalized["personalization"]["applied"]) | |
| self.assertIn("์ปดํจํฐ๊ณตํ๋ถ", personalized["search_query"]) | |
| self.assertIn("3ํ๋ ", personalized["search_query"]) | |
| self.assertIn("์ฐฝ์ ", personalized["features"]["expanded_terms"]) | |
| self.assertNotIn("AI", personalized["features"]["expanded_terms"]) | |
| self.assertNotIn("๊ด์ฌ์ฌ ์ฐฝ์ AI", personalized["search_query"]) | |
| self.assertNotIn("์ฌ์ฉ์ ํ๋กํ", personalized["search_query"]) | |
| self.assertIn("์ง์ ๊ฐ๋ฅ", personalized["features"]["expanded_terms"]) | |
| def test_it_profile_job_query_does_not_add_it_job_terms_to_query(self): | |
| profile = {"college": "์ฐฝ์์ตํฉ๋ํ", "track": "AI์์ฉํ๊ณผ", "grade": "4ํ๋ "} | |
| terms = search_service._profile_search_terms(profile, "๋ด ํ๊ณผ๋ ๋ง๋ ์ฑ์ฉ๊ณต๊ณ ์ถ์ฒํด์ค") | |
| self.assertIn("AI์์ฉํ๊ณผ", terms) | |
| self.assertNotIn("์ํํธ์จ์ด", terms) | |
| self.assertNotIn("๊ฐ๋ฐ", terms) | |
| self.assertNotIn("๋ฐฑ์๋", terms) | |
| self.assertNotIn("ํ๋ก ํธ์๋", terms) | |
| self.assertNotIn("์ธ๊ณต์ง๋ฅ", terms) | |
| def test_it_profile_non_job_query_does_not_add_it_job_terms(self): | |
| profile = {"college": "์ฐฝ์์ตํฉ๋ํ", "track": "AI์์ฉํ๊ณผ", "grade": "4ํ๋ "} | |
| terms = search_service._profile_search_terms(profile, "๋ด ํ๊ณผ์ ๋ง๋ ์ฅํ๊ธ ์ถ์ฒํด์ค") | |
| self.assertIn("AI์์ฉํ๊ณผ", terms) | |
| self.assertNotIn("๋ฐฑ์๋", terms) | |
| self.assertNotIn("ํ๋ก ํธ์๋", terms) | |
| def test_it_student_job_rerank_boosts_job_notice_and_penalizes_faculty_hiring(self): | |
| route = { | |
| "original_query": "๋ด ํ๊ณผ๋ ๋ง๋ ์ฑ์ฉ๊ณต๊ณ ์ถ์ฒํด์ค", | |
| "search_query": "๋ด ํ๊ณผ๋ ๋ง๋ ์ฑ์ฉ๊ณต๊ณ ์ถ์ฒํด์ค AI์์ฉํ๊ณผ 4ํ๋ ", | |
| "intent": "conditional", | |
| "features": {}, | |
| "personalization": { | |
| "applied": True, | |
| "profile_summary": "๋จ๊ณผ๋ํ: ์ฐฝ์์ตํฉ๋ํ; ํ๊ณผ/ํธ๋: AI์์ฉํ๊ณผ; ํ๋ : 4ํ๋ ", | |
| }, | |
| } | |
| job_notice = { | |
| "title": "[๊ต์๋ ์ถ์ฒ์ฑ์ฉ] IT ์ปจ์คํดํธ ์ ์ ์ฑ์ฉ", | |
| "category": "์ทจ์ /์ฑ์ฉ", | |
| "content": "AI ๋ฐ์ดํฐ ์ํํธ์จ์ด ๊ฐ๋ฐ ์ง๋ฌด. ์กธ์ ์์ ์ ์ง์ ๊ฐ๋ฅ.", | |
| } | |
| faculty_notice = { | |
| "title": "2026ํ๋ ๋ 2ํ๊ธฐ ์ ์๊ต์ ์ด๋น ๊ณต๊ณ ", | |
| "category": "์ทจ์ /์ฑ์ฉ", | |
| "content": "์ปดํจํฐ๊ณตํ AI ์ํํธ์จ์ด ๋ฐ์ดํฐ ๋ถ์ผ ๊ต์ ์ด๋น ๊ณต๊ณ .", | |
| } | |
| job_bonus, job_parts = search_service._score_query_features(job_notice, route) | |
| faculty_bonus, faculty_parts = search_service._score_query_features(faculty_notice, route) | |
| self.assertGreater(job_bonus, 0) | |
| self.assertIn("it_job_keyword", job_parts) | |
| self.assertIn("student_job_keyword", job_parts) | |
| self.assertLess(faculty_bonus, job_bonus) | |
| self.assertIn("academic_hiring_penalty", faculty_parts) | |
| def test_disabled_query_routing_uses_baseline_candidates_without_router(self): | |
| baseline = [{"title": "๊ณต์ง", "score": 0.7, "url": "https://example.test/notice"}] | |
| with ( | |
| mock.patch.object(search_service, "QUERY_ROUTING_ENABLED", False), | |
| mock.patch.object(search_service, "_route_query_with_gemini") as router, | |
| mock.patch.object(search_service, "_hybrid_candidate_search", return_value=(baseline, {})) as hybrid, | |
| ): | |
| search_query, candidates, diagnostics = search_service._routed_candidates_before_feature_rerank( | |
| query="์ฅํ๊ธ ๋ง๊ฐ ์ธ์ ์ผ", | |
| top_k=5, | |
| alpha=0.5, | |
| category_filter=None, | |
| candidate_k=20, | |
| profile=None, | |
| ) | |
| self.assertEqual(search_query, "์ฅํ๊ธ ๋ง๊ฐ ์ธ์ ์ผ") | |
| self.assertEqual(candidates, baseline) | |
| self.assertEqual(diagnostics["routing_action"], "disabled") | |
| self.assertEqual(diagnostics["route"]["method"], "disabled") | |
| router.assert_not_called() | |
| hybrid.assert_called_once() | |
| def test_disabled_query_routing_still_applies_personalization(self): | |
| baseline = [{"title": "๊ณต์ง", "score": 0.7, "url": "https://example.test/notice"}] | |
| profile = {"track": "์ปดํจํฐ๊ณตํ๋ถ", "grade": "3ํ๋ ", "interests": ["์ทจ์ /์ฑ์ฉ"]} | |
| with ( | |
| mock.patch.object(search_service, "QUERY_ROUTING_ENABLED", False), | |
| mock.patch.object(search_service, "_route_query_with_gemini") as router, | |
| mock.patch.object(search_service, "_hybrid_candidate_search", return_value=(baseline, {})) as hybrid, | |
| ): | |
| search_query, candidates, diagnostics = search_service._routed_candidates_before_feature_rerank( | |
| query="๋ด ํ๊ณผ์ ๋ง๋ ์ฑ์ฉ ๊ณต๊ณ ์ฐพ์์ค", | |
| top_k=5, | |
| alpha=0.5, | |
| category_filter=None, | |
| candidate_k=20, | |
| profile=profile, | |
| ) | |
| self.assertIn("์ปดํจํฐ๊ณตํ๋ถ", search_query) | |
| self.assertIn("3ํ๋ ", search_query) | |
| self.assertEqual(candidates, baseline) | |
| self.assertEqual(diagnostics["routing_action"], "disabled_personalized_query") | |
| self.assertTrue(diagnostics["route"]["personalization"]["applied"]) | |
| router.assert_not_called() | |
| hybrid.assert_called_once() | |
| if __name__ == "__main__": | |
| unittest.main() | |