sangsangfinder / tests /test_query_routing_toggle.py
cksleigen's picture
Initial clean deploy
54656fc
Raw
History Blame Contribute Delete
6.9 kB
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()