Spaces:
Sleeping
Sleeping
Download tests/test_environment.py from Shivacode/rl-hackathon: direct link, hf CLI and curl.
- Browser
- Download file 8.09 kB
-
https://huggingface.co/spaces/Shivacode/rl-hackathon/resolve/main/tests/test_environment.py
- Command line
-
hf download hf://spaces/Shivacode/rl-hackathon/tests/test_environment.py
-
curl -L -o test_environment.py https://huggingface.co/spaces/Shivacode/rl-hackathon/resolve/main/tests/test_environment.py
8.09 kB
| """ | |
| tests/test_environment.py β Unit tests for the Email Triage environment. | |
| Tests: | |
| - reset() returns a valid TriageObservation | |
| - step() returns correct reward type and range | |
| - All 3 task levels complete without errors | |
| - Graders return scores in [0.0, 1.0] | |
| - Perfect action scores >= 0.9 | |
| - Wrong action scores <= 0.5 | |
| """ | |
| import pytest | |
| import sys, os | |
| sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) | |
| from email_triage_rl_hackathon.server.my_environment import EmailTriageEnvironment | |
| from email_triage_rl_hackathon.models import TriageAction | |
| from email_triage_rl_hackathon.server.graders import grade_easy, grade_medium, grade_hard | |
| from email_triage_rl_hackathon.server.email_data import EASY_EMAILS, MEDIUM_EMAILS, HARD_EMAILS | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # Helpers | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| def perfect_action(email) -> TriageAction: | |
| """Return the ground-truth action for an email.""" | |
| return TriageAction( | |
| category=email.gt_category, | |
| priority=email.gt_priority, | |
| department=email.gt_department, | |
| reply=" ".join(email.gt_reply_keywords) + " apologize refund investigate dear", | |
| ) | |
| def wrong_action() -> TriageAction: | |
| return TriageAction( | |
| category="spam", | |
| priority="low", | |
| department="support", | |
| reply="", | |
| ) | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # Environment lifecycle tests | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| class TestEnvironmentLifecycle: | |
| def test_reset_returns_valid_observation(self): | |
| env = EmailTriageEnvironment(task_level="easy") | |
| obs = env.reset() | |
| assert obs.email_id != "none" | |
| assert len(obs.subject) > 0 | |
| assert obs.task_level == "easy" | |
| assert obs.done is False | |
| assert 0.0 <= obs.score <= 1.0 | |
| def test_reset_with_seed_is_reproducible(self): | |
| env1 = EmailTriageEnvironment(task_level="easy") | |
| env2 = EmailTriageEnvironment(task_level="easy") | |
| obs1 = env1.reset(seed=42) | |
| obs2 = env2.reset(seed=42) | |
| assert obs1.email_id == obs2.email_id | |
| def test_state_returns_state_object(self): | |
| env = EmailTriageEnvironment(task_level="easy") | |
| env.reset() | |
| state = env.state | |
| assert state.episode_id is not None | |
| assert state.step_count == 0 | |
| def test_step_increments_step_count(self): | |
| env = EmailTriageEnvironment(task_level="easy") | |
| obs = env.reset() | |
| action = TriageAction(category="spam", priority="low", department="support", reply="") | |
| env.step(action) | |
| assert env.state.step_count == 1 | |
| def test_episode_ends_after_all_emails(self): | |
| for level in ["easy", "medium", "hard"]: | |
| env = EmailTriageEnvironment(task_level=level) | |
| obs = env.reset() | |
| action = wrong_action() | |
| done = False | |
| steps = 0 | |
| while not done: | |
| result = env.step(action) | |
| done = result.done | |
| steps += 1 | |
| assert steps < 20, "Episode did not terminate" | |
| assert done is True | |
| def test_reward_is_in_valid_range(self): | |
| env = EmailTriageEnvironment(task_level="easy") | |
| obs = env.reset() | |
| while not obs.done: | |
| action = TriageAction(category="billing", priority="medium", | |
| department="finance", reply="") | |
| obs = env.step(action) | |
| assert -1.0 <= obs.reward <= 1.0 | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # Grader tests | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| class TestGraders: | |
| def test_easy_perfect_score(self): | |
| email = EASY_EMAILS[1] # billing email | |
| score, _ = grade_easy(email, email.gt_category, email.gt_priority, email.gt_department) | |
| assert score == 1.0 | |
| def test_easy_all_wrong_score(self): | |
| email = EASY_EMAILS[0] # spam email | |
| score, _ = grade_easy(email, "technical", "urgent", "engineering") | |
| assert score == 0.0 | |
| def test_easy_partial_score(self): | |
| email = EASY_EMAILS[1] # billing/high/finance | |
| score, _ = grade_easy(email, "billing", "low", "support") | |
| assert 0.0 < score < 1.0 | |
| assert score == pytest.approx(0.40) # only category correct | |
| def test_medium_no_reply_penalised(self): | |
| email = MEDIUM_EMAILS[0] | |
| score, feedback = grade_medium( | |
| email, email.gt_category, email.gt_priority, email.gt_department, "" | |
| ) | |
| assert "too short" in feedback or "missing" in feedback | |
| assert score <= 0.5 | |
| def test_medium_good_reply_scores_high(self): | |
| email = MEDIUM_EMAILS[0] | |
| reply = "Dear customer, we sincerely apologize for the double charge. We will investigate and refund within 24-48 hours." | |
| score, _ = grade_medium( | |
| email, email.gt_category, email.gt_priority, email.gt_department, reply | |
| ) | |
| assert score >= 0.7 | |
| def test_hard_short_reply_penalised(self): | |
| email = HARD_EMAILS[0] | |
| score, feedback = grade_hard( | |
| email, email.gt_category, email.gt_priority, email.gt_department, "ok" | |
| ) | |
| # Classification correct = 0.40 max; reply too short adds nothing | |
| assert score <= 0.42 # no reply bonus beyond classification | |
| def test_hard_full_reply_scores_high(self): | |
| email = HARD_EMAILS[0] | |
| reply = ( | |
| "Dear customer, we sincerely apologize for the unacceptable experience with " | |
| "account ACC-00421. I am personally escalating this to our senior finance manager " | |
| "right now. We will resolve this within 24 hours and a manager will contact you " | |
| "directly. All previous case numbers have been reviewed." | |
| ) | |
| score, _ = grade_hard( | |
| email, email.gt_category, email.gt_priority, email.gt_department, reply | |
| ) | |
| assert score >= 0.6 | |
| def test_scores_always_between_0_and_1(self): | |
| for email in EASY_EMAILS + MEDIUM_EMAILS + HARD_EMAILS: | |
| if email.task_level == "easy": | |
| s, _ = grade_easy(email, "spam", "low", "support") | |
| elif email.task_level == "medium": | |
| s, _ = grade_medium(email, "spam", "low", "support", "") | |
| else: | |
| s, _ = grade_hard(email, "spam", "low", "support", "") | |
| assert 0.0 <= s <= 1.0, f"Score {s} out of range for {email.email_id}" | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # Full episode integration tests | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| class TestFullEpisode: | |
| def test_perfect_easy_episode_scores_1(self): | |
| env = EmailTriageEnvironment(task_level="easy") | |
| obs = env.reset(seed=0) | |
| total = 0.0 | |
| steps = 0 | |
| while not obs.done: | |
| # Find current email's ground truth from email_data | |
| from email_triage_rl_hackathon.server.email_data import ALL_EMAILS | |
| email = ALL_EMAILS.get(obs.email_id) | |
| assert email is not None, f"Unknown email_id: {obs.email_id}" | |
| action = perfect_action(email) | |
| obs = env.step(action) | |
| total += obs.reward or 0.0 | |
| steps += 1 | |
| assert steps > 0 | |
| avg = total / steps | |
| assert avg >= 0.9, f"Perfect easy episode should score β₯0.9, got {avg:.2f}" | |