manikandan-n-07 commited on
Commit
3e4ef62
Β·
1 Parent(s): e23f30b

Final infrastructure and documentation fix for Phase 2 validation

Browse files
README.md CHANGED
@@ -82,9 +82,12 @@ The codebase follows a clean separation-of-concerns architecture across four dis
82
 
83
  ```
84
  .
 
 
 
 
85
  β”œβ”€β”€ core/ # Simulation Logic Layer
86
  β”‚ β”œβ”€β”€ drone.py # Movement physics & battery drain
87
- β”‚ β”œβ”€β”€ graders.py # Unified scoring logic (0.01 - 0.99)
88
  β”‚ β”œβ”€β”€ grid_generator.py # Map generation logic
89
  β”‚ β”œβ”€β”€ obstacles.py # Collision & terrain detection
90
  β”‚ β”œβ”€β”€ state_manager.py # Episodic state management
@@ -471,7 +474,7 @@ MODEL_NAME=Qwen/Qwen2.5-72B-Instruct python inference.py
471
  The runner emits structured benchmark-compatible log lines:
472
 
473
  ```
474
- [START] task=drone_env.core.graders:grade_easy env=drone_env_v1 model=Qwen/Qwen2.5-7B-Instruct
475
  [STEP] step=1 action=RIGHT reward=0.10 done=false error=null
476
  [STEP] step=2 action=RIGHT reward=0.10 done=false error=null
477
  [STEP] step=3 action=RIGHT reward=0.10 done=false error=null
@@ -568,15 +571,15 @@ app: drone_env.server.app:app
568
  port: 8000
569
  tasks:
570
  - id: easy_delivery
571
- grader: drone_env.core.graders:grade_easy
572
  - id: medium_delivery
573
- grader: drone_env.core.graders:grade_medium
574
  - id: hard_delivery
575
- grader: drone_env.core.graders:grade_hard
576
  graders:
577
- - id: drone_env.core.graders:grade_easy
578
- - id: drone_env.core.graders:grade_medium
579
- - id: drone_env.core.graders:grade_hard
580
  ```
581
 
582
  ### Validate Before Submission
@@ -611,8 +614,20 @@ docker build .
611
  openenv validate
612
  ```
613
 
 
 
 
 
 
 
614
  A passing run produces:
615
  ```
 
 
 
 
 
 
616
  ========================================
617
  All 3/3 checks passed!
618
  Your submission is ready to submit.
@@ -712,7 +727,7 @@ $$r_{\text{shaping}} = (d_{\text{before}} - d_{\text{after}}) \times 0.05$$
712
 
713
  ## Grading & Evaluation
714
 
715
- Scores are computed by `core/graders.py` using a unified formula:
716
 
717
  $$\text{score} = 0.8 \times \underbrace{\frac{\text{deliveries done}}{\text{deliveries total}}}_{\text{delivery ratio}} + 0.2 \times \underbrace{\left( 0.5 \cdot \text{battery} + 0.5 \cdot \left(1 - \frac{\text{steps}}{\text{max steps}}\right) \right)}_{\text{efficiency}}$$
718
 
@@ -774,19 +789,21 @@ The **SkyRelic** ecosystem is divided into four primary layers, interconnected v
774
 
775
  ```
776
  drone_env/
777
- β”œβ”€β”€ core/
778
- β”‚ β”œβ”€β”€ drone.py # compute_next_pos(), drain_battery()
779
- β”‚ β”œβ”€β”€ graders.py # grade_easy/medium/hard(), GRADERS dict
780
- β”‚ β”œβ”€β”€ grid_generator.py # generate_city_map(), EMOJI, LEGEND
781
- β”‚ β”œβ”€β”€ obstacles.py # check_move() β†’ outcome, cell_type
782
- β”‚ β”œβ”€β”€ state_manager.py # new_episode_state() β†’ DroneState
783
- β”‚ └── tasks.py # TASK_CONFIG dict (all difficulty params)
784
- β”œβ”€β”€ rl/
785
- β”‚ β”œβ”€β”€ model.py # MapEncoder, PathQNet, ACTIONS, CELL2IDX
786
- β”‚ β”œβ”€β”€ policy.py # EpsilonGreedyPolicy
787
- β”‚ └── trainer.py # record_episode(), PathLearner, get_action_from_policy()
788
- β”œβ”€β”€ server/
789
- β”‚ β”œβ”€β”€ app.py # FastAPI app, all routes, TerminalLogManager
 
 
790
  β”‚ β”œβ”€β”€ grid_world_environment.py # DroneDeliveryEnvironment (OpenEnv base)
791
  β”‚ β”œβ”€β”€ Dockerfile # Multi-stage production image
792
  β”‚ └── static/ # Browser dashboard (HTML/JS/CSS)
@@ -887,8 +904,8 @@ Build system uses [Meta's BSD-licensed](https://opensource.org/licenses/BSD-3-Cl
887
  The **SkyRelic** environment has been updated to fully comply with the **Meta PyTorch Hackathon Phase 2 Deep Validation** requirements.
888
 
889
  ### πŸ›‘οΈ Validation Fixes
890
- - **Strict Score Clamping**: All mission scores and rewards are now strictly clamped to the **(0.01, 0.99)** range in `core/graders.py` and `server/grid_world_environment.py`. This prevents the "out of range" (exactly 0.0 or 1.0) failures reported by the automated validator.
891
- - **Full Identity Sync (Grader Discovery)**: Task and grader identifiers have been synchronized across the manifest (`openenv.yaml`), backend API, and simulation core using full Python module paths (e.g., `drone_env.core.graders:grade_easy`). This ensures the Meta validator can successfully discover and import the grading functions.
892
  - **Differentiated Reward Scalars**: To provide clearer learning signals, reward scalars for step, wait, and collision penalties have been updated to difficulty-specific tiers:
893
  - **Easy Mission**: 0.10 (10%)
894
  - **Medium Mission**: 0.15 (15%)
 
82
 
83
  ```
84
  .
85
+ β”œβ”€β”€ graders/ # Unified Graders Package (Root)
86
+ β”‚ β”œβ”€β”€ easy.py # Easy task scoring logic
87
+ β”‚ β”œβ”€β”€ medium.py # Medium task scoring logic
88
+ β”‚ └── hard.py # Hard task scoring logic
89
  β”œβ”€β”€ core/ # Simulation Logic Layer
90
  β”‚ β”œβ”€β”€ drone.py # Movement physics & battery drain
 
91
  β”‚ β”œβ”€β”€ grid_generator.py # Map generation logic
92
  β”‚ β”œβ”€β”€ obstacles.py # Collision & terrain detection
93
  β”‚ β”œβ”€β”€ state_manager.py # Episodic state management
 
474
  The runner emits structured benchmark-compatible log lines:
475
 
476
  ```
477
+ [START] task=drone_env.graders:grade_easy env=drone_env_v1 model=Qwen/Qwen2.5-7B-Instruct
478
  [STEP] step=1 action=RIGHT reward=0.10 done=false error=null
479
  [STEP] step=2 action=RIGHT reward=0.10 done=false error=null
480
  [STEP] step=3 action=RIGHT reward=0.10 done=false error=null
 
571
  port: 8000
572
  tasks:
573
  - id: easy_delivery
574
+ grader: drone_env.graders:grade_easy
575
  - id: medium_delivery
576
+ grader: drone_env.graders:grade_medium
577
  - id: hard_delivery
578
+ grader: drone_env.graders:grade_hard
579
  graders:
580
+ - id: drone_env.graders:grade_easy
581
+ - id: drone_env.graders:grade_medium
582
+ - id: drone_env.graders:grade_hard
583
  ```
584
 
585
  ### Validate Before Submission
 
614
  openenv validate
615
  ```
616
 
617
+ #### Local Grader Check
618
+ To verify that all 3 tasks have valid, resolvable graders before pushing:
619
+ ```bash
620
+ python check_graders.py
621
+ ```
622
+
623
  A passing run produces:
624
  ```
625
+ ========================================
626
+ Summary: 3 valid graders found.
627
+ πŸš€ LOCAL CHECK PASSED.
628
+ ========================================
629
+ ```
630
+
631
  ========================================
632
  All 3/3 checks passed!
633
  Your submission is ready to submit.
 
727
 
728
  ## Grading & Evaluation
729
 
730
+ Scores are computed by the `graders/` package using a unified formula:
731
 
732
  $$\text{score} = 0.8 \times \underbrace{\frac{\text{deliveries done}}{\text{deliveries total}}}_{\text{delivery ratio}} + 0.2 \times \underbrace{\left( 0.5 \cdot \text{battery} + 0.5 \cdot \left(1 - \frac{\text{steps}}{\text{max steps}}\right) \right)}_{\text{efficiency}}$$
733
 
 
789
 
790
  ```
791
  drone_env/
792
+ β”œβ”€β”€ graders/ # Unified grader package
793
+ β”‚ β”œβ”€β”€ __init__.py # GRADERS lookup dictionary
794
+ β”‚ β”œβ”€β”€ easy.py # Easy task logic
795
+ β”‚ β”œβ”€β”€ medium.py # Medium task logic
796
+ β”‚ └── hard.py # Hard task logic
797
+ β”œβ”€β”€ core/ # Simulation physics & tasks
798
+ β”œβ”€β”€ rl/ # DQN & Neural Training
799
+ β”œβ”€β”€ server/ # FastAPI & Dashboard
800
+ β”œβ”€β”€ data/ # Persistence (Memory & Logs)
801
+ β”œβ”€β”€ tests/ # Unit & API tests
802
+ β”œβ”€β”€ check_graders.py # Local grader validation script
803
+ β”œβ”€β”€ openenv.yaml # Mission Manifest
804
+ β”œβ”€β”€ pyproject.toml # Package configuration
805
+ └── validate-submission.sh # Submission validator
806
+ ```
807
  β”‚ β”œβ”€β”€ grid_world_environment.py # DroneDeliveryEnvironment (OpenEnv base)
808
  β”‚ β”œβ”€β”€ Dockerfile # Multi-stage production image
809
  β”‚ └── static/ # Browser dashboard (HTML/JS/CSS)
 
904
  The **SkyRelic** environment has been updated to fully comply with the **Meta PyTorch Hackathon Phase 2 Deep Validation** requirements.
905
 
906
  ### πŸ›‘οΈ Validation Fixes
907
+ - **Strict Score Clamping**: All mission scores and rewards are now strictly clamped to the **(0.01, 0.99)** range in the `graders/` package and `server/grid_world_environment.py`. This prevents the "out of range" (exactly 0.0 or 1.0) failures reported by the automated validator.
908
+ - **Full Identity Sync (Grader Discovery)**: Task and grader identifiers have been synchronized across the manifest (`openenv.yaml`), backend API, and simulation core using full Python module paths (e.g., `drone_env.graders:grade_easy`). This ensures the Meta validator can successfully discover and import the grading functions.
909
  - **Differentiated Reward Scalars**: To provide clearer learning signals, reward scalars for step, wait, and collision penalties have been updated to difficulty-specific tiers:
910
  - **Easy Mission**: 0.10 (10%)
911
  - **Medium Mission**: 0.15 (15%)
check_graders.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import yaml
2
+ import importlib
3
+ import sys
4
+ import os
5
+ from pathlib import Path
6
+
7
+ def check_graders():
8
+ # Ensure current directory is in path
9
+ sys.path.insert(0, os.getcwd())
10
+
11
+ yaml_path = Path("openenv.yaml")
12
+ if not yaml_path.exists():
13
+ print("❌ Error: openenv.yaml not found!")
14
+ return
15
+
16
+ try:
17
+ with open(yaml_path, 'r') as f:
18
+ spec = yaml.safe_load(f)
19
+ except Exception as e:
20
+ print(f"❌ Error parsing openenv.yaml: {e}")
21
+ return
22
+
23
+ tasks = spec.get("tasks", [])
24
+ print(f"πŸ” Found {len(tasks)} tasks in openenv.yaml\n")
25
+
26
+ valid_count = 0
27
+
28
+ for task in tasks:
29
+ task_id = task.get("id")
30
+ grader_str = task.get("grader")
31
+
32
+ print(f"--- Task: {task_id} ---")
33
+ if not grader_str:
34
+ print(" ❌ No grader field defined.")
35
+ continue
36
+
37
+ print(f" Attempting to resolve: {grader_str}")
38
+ try:
39
+ if ":" not in grader_str:
40
+ print(f" ❌ Invalid format. Expected 'module:function'")
41
+ continue
42
+
43
+ module_path, func_name = grader_str.split(":")
44
+
45
+ module = None
46
+ # Try full path first (as it will be in the platform)
47
+ try:
48
+ module = importlib.import_module(module_path)
49
+ except ImportError:
50
+ # Local fallback: try without 'drone_env.' prefix if we're in the project root
51
+ if module_path.startswith("drone_env."):
52
+ alt_path = module_path.replace("drone_env.", "", 1)
53
+ try:
54
+ module = importlib.import_module(alt_path)
55
+ except ImportError as eb:
56
+ print(f" ❌ Module search failed (tried {module_path} and {alt_path}): {eb}")
57
+
58
+ if module:
59
+ func = getattr(module, func_name, None)
60
+ if func and callable(func):
61
+ print(f" βœ… SUCCESS: Found {func_name} in {module.__name__}")
62
+ valid_count += 1
63
+ else:
64
+ print(f" ❌ {func_name} not found or not callable in {module.__name__}")
65
+ else:
66
+ pass # Already printed error
67
+ except Exception as e:
68
+ print(f" ❌ Error: {e}")
69
+
70
+ print(f"\nSummary: {valid_count} valid graders found.")
71
+ if valid_count >= 3:
72
+ print("πŸš€ LOCAL CHECK PASSED.")
73
+ else:
74
+ print("🚨 LOCAL CHECK FAILED.")
75
+
76
+ if __name__ == "__main__":
77
+ check_graders()
core/__init__.py CHANGED
@@ -1,4 +1,13 @@
1
- # drone_env.core package
2
- from .graders import grade_easy, grade_medium, grade_hard
 
 
 
 
 
 
 
 
 
3
 
4
  __all__ = ["grade_easy", "grade_medium", "grade_hard"]
 
1
+ def grade_easy(state):
2
+ from drone_env.graders.easy import grade_easy as fn
3
+ return fn(state)
4
+
5
+ def grade_medium(state):
6
+ from drone_env.graders.medium import grade_medium as fn
7
+ return fn(state)
8
+
9
+ def grade_hard(state):
10
+ from drone_env.graders.hard import grade_hard as fn
11
+ return fn(state)
12
 
13
  __all__ = ["grade_easy", "grade_medium", "grade_hard"]
core/tasks.py CHANGED
@@ -4,7 +4,7 @@ Mission configurations for Drone Delivery missions.
4
  """
5
 
6
  TASK_CONFIG = {
7
- "drone_env.core.graders:grade_easy": {
8
  "width": 10,
9
  "height": 10,
10
  "n_buildings": 4,
@@ -24,7 +24,7 @@ TASK_CONFIG = {
24
  "r_wall": 0.10,
25
  "r_blocked": 0.10,
26
  },
27
- "drone_env.core.graders:grade_medium": {
28
  "width": 14,
29
  "height": 14,
30
  "n_buildings": 8,
@@ -44,7 +44,7 @@ TASK_CONFIG = {
44
  "r_wall": 0.15,
45
  "r_blocked": 0.15,
46
  },
47
- "drone_env.core.graders:grade_hard": {
48
  "width": 18,
49
  "height": 18,
50
  "n_buildings": 12,
 
4
  """
5
 
6
  TASK_CONFIG = {
7
+ "drone_env.graders.easy:grade_easy": {
8
  "width": 10,
9
  "height": 10,
10
  "n_buildings": 4,
 
24
  "r_wall": 0.10,
25
  "r_blocked": 0.10,
26
  },
27
+ "drone_env.graders.medium:grade_medium": {
28
  "width": 14,
29
  "height": 14,
30
  "n_buildings": 8,
 
44
  "r_wall": 0.15,
45
  "r_blocked": 0.15,
46
  },
47
+ "drone_env.graders.hard:grade_hard": {
48
  "width": 18,
49
  "height": 18,
50
  "n_buildings": 12,
data/memory.json CHANGED
The diff for this file is too large to render. See raw diff
 
graders/__init__.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .easy import grade_easy
2
+ from .medium import grade_medium
3
+ from .hard import grade_hard
4
+
5
+ GRADERS = {
6
+ "drone_env.graders.easy:grade_easy": grade_easy,
7
+ "drone_env.graders.medium:grade_medium": grade_medium,
8
+ "drone_env.graders.hard:grade_hard": grade_hard,
9
+ }
10
+
11
+ __all__ = ["grade_easy", "grade_medium", "grade_hard", "GRADERS"]
graders/easy.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Evaluation logic for easy_delivery task.
3
+ This file is self-contained.
4
+ """
5
+
6
+ def _get_attr(state, key, default=0):
7
+ """Safely get an attribute from a state object or dict."""
8
+ if isinstance(state, dict):
9
+ return state.get(key, default)
10
+ return getattr(state, key, default)
11
+
12
+
13
+ def compute_grade(state, max_steps: float) -> float:
14
+ """
15
+ Unified grade calculation:
16
+ - 80% weighted by deliveries completed.
17
+ - 20% weighted by efficiency (remaining battery and steps).
18
+ """
19
+ deliveries_total = _get_attr(state, "deliveries_total", 0)
20
+ deliveries_done = _get_attr(state, "deliveries_done", 0)
21
+ battery = _get_attr(state, "battery", 1.0)
22
+ step_count = _get_attr(state, "step_count", 0)
23
+
24
+ if deliveries_total == 0:
25
+ return 0.5
26
+
27
+ delivery_ratio = deliveries_done / deliveries_total
28
+ efficiency = float(battery) * 0.5 + (1.0 - (float(step_count) / float(max_steps))) * 0.5
29
+ efficiency = max(0.0, min(1.0, efficiency))
30
+
31
+ score = (delivery_ratio * 0.8) + (efficiency * 0.2)
32
+ if deliveries_done < deliveries_total:
33
+ score = min(score, 0.49)
34
+
35
+ return max(0.01, min(0.99, float(score)))
36
+
37
+
38
+ def grade_easy(state) -> float:
39
+ """Grader for easy_delivery task (10x10 grid, 1 delivery, 60 max steps)."""
40
+ return compute_grade(state, 60.0)
graders/hard.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Evaluation logic for hard_delivery task.
3
+ This file is self-contained.
4
+ """
5
+
6
+ def _get_attr(state, key, default=0):
7
+ """Safely get an attribute from a state object or dict."""
8
+ if isinstance(state, dict):
9
+ return state.get(key, default)
10
+ return getattr(state, key, default)
11
+
12
+
13
+ def compute_grade(state, max_steps: float) -> float:
14
+ """
15
+ Unified grade calculation:
16
+ - 80% weighted by deliveries completed.
17
+ - 20% weighted by efficiency (remaining battery and steps).
18
+ """
19
+ deliveries_total = _get_attr(state, "deliveries_total", 0)
20
+ deliveries_done = _get_attr(state, "deliveries_done", 0)
21
+ battery = _get_attr(state, "battery", 1.0)
22
+ step_count = _get_attr(state, "step_count", 0)
23
+
24
+ if deliveries_total == 0:
25
+ return 0.5
26
+
27
+ delivery_ratio = deliveries_done / deliveries_total
28
+ efficiency = float(battery) * 0.5 + (1.0 - (float(step_count) / float(max_steps))) * 0.5
29
+ efficiency = max(0.0, min(1.0, efficiency))
30
+
31
+ score = (delivery_ratio * 0.8) + (efficiency * 0.2)
32
+ if deliveries_done < deliveries_total:
33
+ score = min(score, 0.49)
34
+
35
+ return max(0.01, min(0.99, float(score)))
36
+
37
+
38
+ def grade_hard(state) -> float:
39
+ """Grader for hard_delivery task (18x18 grid, 5 deliveries, 160 max steps)."""
40
+ return compute_grade(state, 160.0)
core/graders.py β†’ graders/medium.py RENAMED
@@ -1,14 +1,8 @@
1
  """
2
- drone_delivery_env/core/graders.py
3
- Evaluation logic for scoring Drone Delivery missions.
4
-
5
- NOTE: This module is intentionally self-contained with NO external package imports.
6
- The OpenEnv validator imports grader functions directly via the module path
7
- (e.g. drone_env.core.graders:grade_easy). Any import failure here means the
8
- validator counts 0 working graders β€” causing "Not enough tasks with graders" error.
9
  """
10
 
11
-
12
  def _get_attr(state, key, default=0):
13
  """Safely get an attribute from a state object or dict."""
14
  if isinstance(state, dict):
@@ -21,7 +15,6 @@ def compute_grade(state, max_steps: float) -> float:
21
  Unified grade calculation:
22
  - 80% weighted by deliveries completed.
23
  - 20% weighted by efficiency (remaining battery and steps).
24
- Accepts any object or dict with the required fields.
25
  """
26
  deliveries_total = _get_attr(state, "deliveries_total", 0)
27
  deliveries_done = _get_attr(state, "deliveries_done", 0)
@@ -29,41 +22,19 @@ def compute_grade(state, max_steps: float) -> float:
29
  step_count = _get_attr(state, "step_count", 0)
30
 
31
  if deliveries_total == 0:
32
- return 0.5 # neutral score when no deliveries configured
33
 
34
  delivery_ratio = deliveries_done / deliveries_total
35
-
36
- # Efficiency factor (0.0 to 1.0)
37
  efficiency = float(battery) * 0.5 + (1.0 - (float(step_count) / float(max_steps))) * 0.5
38
  efficiency = max(0.0, min(1.0, efficiency))
39
 
40
  score = (delivery_ratio * 0.8) + (efficiency * 0.2)
41
-
42
- # Cap score if not all deliveries are done
43
  if deliveries_done < deliveries_total:
44
  score = min(score, 0.49)
45
 
46
- # Hackathon Requirement: Score must be strictly between 0.0 and 1.0
47
  return max(0.01, min(0.99, float(score)))
48
 
49
 
50
- def grade_easy(state) -> float:
51
- """Grader for easy_delivery task (10x10 grid, 1 delivery, 60 max steps)."""
52
- return compute_grade(state, 60.0)
53
-
54
-
55
  def grade_medium(state) -> float:
56
  """Grader for medium_delivery task (14x14 grid, 3 deliveries, 100 max steps)."""
57
  return compute_grade(state, 100.0)
58
-
59
-
60
- def grade_hard(state) -> float:
61
- """Grader for hard_delivery task (18x18 grid, 5 deliveries, 160 max steps)."""
62
- return compute_grade(state, 160.0)
63
-
64
-
65
- GRADERS = {
66
- "drone_env.core.graders:grade_easy": grade_easy,
67
- "drone_env.core.graders:grade_medium": grade_medium,
68
- "drone_env.core.graders:grade_hard": grade_hard,
69
- }
 
1
  """
2
+ Evaluation logic for medium_delivery task.
3
+ This file is self-contained.
 
 
 
 
 
4
  """
5
 
 
6
  def _get_attr(state, key, default=0):
7
  """Safely get an attribute from a state object or dict."""
8
  if isinstance(state, dict):
 
15
  Unified grade calculation:
16
  - 80% weighted by deliveries completed.
17
  - 20% weighted by efficiency (remaining battery and steps).
 
18
  """
19
  deliveries_total = _get_attr(state, "deliveries_total", 0)
20
  deliveries_done = _get_attr(state, "deliveries_done", 0)
 
22
  step_count = _get_attr(state, "step_count", 0)
23
 
24
  if deliveries_total == 0:
25
+ return 0.5
26
 
27
  delivery_ratio = deliveries_done / deliveries_total
 
 
28
  efficiency = float(battery) * 0.5 + (1.0 - (float(step_count) / float(max_steps))) * 0.5
29
  efficiency = max(0.0, min(1.0, efficiency))
30
 
31
  score = (delivery_ratio * 0.8) + (efficiency * 0.2)
 
 
32
  if deliveries_done < deliveries_total:
33
  score = min(score, 0.49)
34
 
 
35
  return max(0.01, min(0.99, float(score)))
36
 
37
 
 
 
 
 
 
38
  def grade_medium(state) -> float:
39
  """Grader for medium_delivery task (14x14 grid, 3 deliveries, 100 max steps)."""
40
  return compute_grade(state, 100.0)
 
 
 
 
 
 
 
 
 
 
 
 
inference.py CHANGED
@@ -44,7 +44,7 @@ API_KEY = os.getenv("HF_TOKEN") or os.getenv("API_KEY") or os.getenv("OPENAI_API
44
  API_BASE_URL = os.getenv("API_BASE_URL") or "https://router.huggingface.co/v1"
45
  MODEL_NAME = os.getenv("MODEL_NAME") or "Qwen/Qwen2.5-7B-Instruct"
46
 
47
- TASK_NAME = os.getenv("DRONE_TASK", "drone_env.core.graders:grade_easy")
48
  BENCHMARK = os.getenv("DRONE_BENCHMARK", "drone_env_v1")
49
 
50
  MAX_STEPS = 60
 
44
  API_BASE_URL = os.getenv("API_BASE_URL") or "https://router.huggingface.co/v1"
45
  MODEL_NAME = os.getenv("MODEL_NAME") or "Qwen/Qwen2.5-7B-Instruct"
46
 
47
+ TASK_NAME = os.getenv("TASK_NAME", "drone_env.graders.easy:grade_easy")
48
  BENCHMARK = os.getenv("DRONE_BENCHMARK", "drone_env_v1")
49
 
50
  MAX_STEPS = 60
models.py CHANGED
@@ -14,7 +14,7 @@ class DroneAction(BaseModel):
14
  )
15
  task_name: Optional[str] = Field(
16
  default=None,
17
- description="Task to load on reset: drone_env.core.graders:grade_easy | medium | hard",
18
  )
19
 
20
 
@@ -43,7 +43,7 @@ class DroneObservation(BaseModel):
43
 
44
  class DroneState(BaseModel):
45
  episode_id: str = ""
46
- task_name: str = "drone_env.core.graders:grade_easy"
47
  step_count: int = 0
48
  done: bool = False
49
  reward_total: float = 0.0
 
14
  )
15
  task_name: Optional[str] = Field(
16
  default=None,
17
+ description="Task to load on reset: drone_env.graders.easy:grade_easy | medium | hard",
18
  )
19
 
20
 
 
43
 
44
  class DroneState(BaseModel):
45
  episode_id: str = ""
46
+ task_name: str = "drone_env.graders.easy:grade_easy"
47
  step_count: int = 0
48
  done: bool = False
49
  reward_total: float = 0.0
openenv.yaml CHANGED
@@ -6,8 +6,8 @@ app: server.app:app
6
  port: 8000
7
  tasks:
8
  - id: easy_delivery
9
- grader: drone_env.core.graders:grade_easy
10
  - id: medium_delivery
11
- grader: drone_env.core.graders:grade_medium
12
  - id: hard_delivery
13
- grader: drone_env.core.graders:grade_hard
 
6
  port: 8000
7
  tasks:
8
  - id: easy_delivery
9
+ grader: drone_env.graders.easy:grade_easy
10
  - id: medium_delivery
11
+ grader: drone_env.graders.medium:grade_medium
12
  - id: hard_delivery
13
+ grader: drone_env.graders.hard:grade_hard
pyproject.toml CHANGED
@@ -35,5 +35,5 @@ server = "drone_env.server.app:main"
35
 
36
  [tool.setuptools]
37
  include-package-data = true
38
- packages = ["drone_env", "drone_env.server", "drone_env.core", "drone_env.rl"]
39
- package-dir = { "drone_env" = ".", "drone_env.server" = "server", "drone_env.core" = "core", "drone_env.rl" = "rl" }
 
35
 
36
  [tool.setuptools]
37
  include-package-data = true
38
+ packages = ["drone_env", "drone_env.server", "drone_env.graders", "drone_env.core", "drone_env.rl"]
39
+ package-dir = { "drone_env" = ".", "drone_env.server" = "server", "drone_env.graders" = "graders", "drone_env.core" = "core", "drone_env.rl" = "rl" }
server/app.py CHANGED
@@ -25,7 +25,7 @@ if str(BASE_DIR) not in sys.path:
25
  from drone_env.models import DroneAction, DroneObservation, DroneState
26
  from drone_env.server.grid_world_environment import DroneDeliveryEnvironment
27
  from drone_env.core.tasks import TASK_CONFIG
28
- from drone_env.core.graders import GRADERS
29
  from drone_env.rl.trainer import PathLearner, get_action_from_policy
30
 
31
  app = FastAPI(
@@ -88,9 +88,9 @@ async def metadata():
88
  "description": "SkyRelic Drone Delivery reinforcement-learning environment with easy/medium/hard delivery tasks on a grid world.",
89
  "version": "0.2.1",
90
  "tasks": [
91
- {"id": "easy_delivery", "grader": "drone_env.core.graders:grade_easy"},
92
- {"id": "medium_delivery", "grader": "drone_env.core.graders:grade_medium"},
93
- {"id": "hard_delivery", "grader": "drone_env.core.graders:grade_hard"},
94
  ],
95
  }
96
 
@@ -144,9 +144,9 @@ async def get_state():
144
 
145
  # Task ID β†’ grader key mapping (matches openenv.yaml task ids)
146
  TASK_ID_TO_GRADER = {
147
- "easy_delivery": "drone_env.core.graders:grade_easy",
148
- "medium_delivery": "drone_env.core.graders:grade_medium",
149
- "hard_delivery": "drone_env.core.graders:grade_hard",
150
  }
151
 
152
  @app.get("/grade/{task_name}")
@@ -207,7 +207,7 @@ async def get_terminal_logs():
207
  @app.get("/rewards")
208
  async def get_rewards():
209
  """Return the reward configuration for the current task."""
210
- task_name = _env.state.task_name or "drone_env.core.graders:grade_easy"
211
  config = TASK_CONFIG.get(task_name, {})
212
  # Filter only reward keys
213
  rewards = {k: v for k, v in config.items() if k.startswith("r_")}
@@ -248,7 +248,7 @@ async def get_memory_logs():
248
 
249
  @app.post("/predict")
250
  async def predict(obs: DroneObservation):
251
- task_name = _env.state.task_name or "drone_env.core.graders:grade_easy"
252
  action_str = get_action_from_policy(obs, task_name)
253
  return {"direction": action_str}
254
 
 
25
  from drone_env.models import DroneAction, DroneObservation, DroneState
26
  from drone_env.server.grid_world_environment import DroneDeliveryEnvironment
27
  from drone_env.core.tasks import TASK_CONFIG
28
+ from drone_env.graders import GRADERS
29
  from drone_env.rl.trainer import PathLearner, get_action_from_policy
30
 
31
  app = FastAPI(
 
88
  "description": "SkyRelic Drone Delivery reinforcement-learning environment with easy/medium/hard delivery tasks on a grid world.",
89
  "version": "0.2.1",
90
  "tasks": [
91
+ {"id": "easy_delivery", "grader": "drone_env.graders.easy:grade_easy"},
92
+ {"id": "medium_delivery", "grader": "drone_env.graders.medium:grade_medium"},
93
+ {"id": "hard_delivery", "grader": "drone_env.graders.hard:grade_hard"},
94
  ],
95
  }
96
 
 
144
 
145
  # Task ID β†’ grader key mapping (matches openenv.yaml task ids)
146
  TASK_ID_TO_GRADER = {
147
+ "easy_delivery": "drone_env.graders.easy:grade_easy",
148
+ "medium_delivery": "drone_env.graders.medium:grade_medium",
149
+ "hard_delivery": "drone_env.graders.hard:grade_hard",
150
  }
151
 
152
  @app.get("/grade/{task_name}")
 
207
  @app.get("/rewards")
208
  async def get_rewards():
209
  """Return the reward configuration for the current task."""
210
+ task_name = _env.state.task_name or "drone_env.graders.easy:grade_easy"
211
  config = TASK_CONFIG.get(task_name, {})
212
  # Filter only reward keys
213
  rewards = {k: v for k, v in config.items() if k.startswith("r_")}
 
248
 
249
  @app.post("/predict")
250
  async def predict(obs: DroneObservation):
251
+ task_name = _env.state.task_name or "drone_env.graders.easy:grade_easy"
252
  action_str = get_action_from_policy(obs, task_name)
253
  return {"direction": action_str}
254
 
server/grid_world_environment.py CHANGED
@@ -24,7 +24,7 @@ from drone_env.core.tasks import TASK_CONFIG
24
  from drone_env.core.drone import compute_next_pos, drain_battery
25
  from drone_env.core.obstacles import check_move
26
  from drone_env.core.state_manager import new_episode_state
27
- from drone_env.core.graders import GRADERS
28
  from drone_env.rl.trainer import record_episode
29
 
30
 
@@ -33,7 +33,7 @@ class DroneDeliveryEnvironment(Environment):
33
  def __init__(self):
34
  super().__init__()
35
  self._state = DroneState()
36
- self._cfg = TASK_CONFIG["drone_env.core.graders:grade_easy"]
37
  self._grid: List[List[str]] = []
38
  self._deliveries: List[Tuple[int, int]] = []
39
  self._delivered: List[bool] = []
@@ -51,11 +51,11 @@ class DroneDeliveryEnvironment(Environment):
51
 
52
  # Map short task IDs (from openenv.yaml) to full grader keys
53
  _TASK_ID_MAP = {
54
- "easy_delivery": "drone_env.core.graders:grade_easy",
55
- "medium_delivery": "drone_env.core.graders:grade_medium",
56
- "hard_delivery": "drone_env.core.graders:grade_hard",
57
  }
58
- task = "drone_env.core.graders:grade_easy"
59
  if action and action.task_name:
60
  name = action.task_name
61
  if name in _TASK_ID_MAP:
 
24
  from drone_env.core.drone import compute_next_pos, drain_battery
25
  from drone_env.core.obstacles import check_move
26
  from drone_env.core.state_manager import new_episode_state
27
+ from drone_env.graders import GRADERS
28
  from drone_env.rl.trainer import record_episode
29
 
30
 
 
33
  def __init__(self):
34
  super().__init__()
35
  self._state = DroneState()
36
+ self._cfg = TASK_CONFIG["drone_env.graders.easy:grade_easy"]
37
  self._grid: List[List[str]] = []
38
  self._deliveries: List[Tuple[int, int]] = []
39
  self._delivered: List[bool] = []
 
51
 
52
  # Map short task IDs (from openenv.yaml) to full grader keys
53
  _TASK_ID_MAP = {
54
+ "easy_delivery": "drone_env.graders.easy:grade_easy",
55
+ "medium_delivery": "drone_env.graders.medium:grade_medium",
56
+ "hard_delivery": "drone_env.graders.hard:grade_hard",
57
  }
58
+ task = "drone_env.graders.easy:grade_easy"
59
  if action and action.task_name:
60
  name = action.task_name
61
  if name in _TASK_ID_MAP:
server/static/index.html CHANGED
@@ -1230,17 +1230,17 @@
1230
  <div class="panel-inner">
1231
  <div class="panel-title">Mission Select</div>
1232
  <div class="task-group" id="taskGroup">
1233
- <button class="task-btn active" data-task="drone_env.core.graders:grade_easy">
1234
  <span class="dot"></span>
1235
  Easy Delivery
1236
  <span class="task-tag">10Γ—10</span>
1237
  </button>
1238
- <button class="task-btn" data-task="drone_env.core.graders:grade_medium">
1239
  <span class="dot"></span>
1240
  Medium Delivery
1241
  <span class="task-tag">14Γ—14</span>
1242
  </button>
1243
- <button class="task-btn" data-task="drone_env.core.graders:grade_hard">
1244
  <span class="dot"></span>
1245
  Hard Delivery
1246
  <span class="task-tag">18Γ—18</span>
 
1230
  <div class="panel-inner">
1231
  <div class="panel-title">Mission Select</div>
1232
  <div class="task-group" id="taskGroup">
1233
+ <button class="task-btn active" data-task="drone_env.graders.easy:grade_easy">
1234
  <span class="dot"></span>
1235
  Easy Delivery
1236
  <span class="task-tag">10Γ—10</span>
1237
  </button>
1238
+ <button class="task-btn" data-task="drone_env.graders.medium:grade_medium">
1239
  <span class="dot"></span>
1240
  Medium Delivery
1241
  <span class="task-tag">14Γ—14</span>
1242
  </button>
1243
+ <button class="task-btn" data-task="drone_env.graders.hard:grade_hard">
1244
  <span class="dot"></span>
1245
  Hard Delivery
1246
  <span class="task-tag">18Γ—18</span>
server/static/script.js CHANGED
@@ -17,7 +17,7 @@ const EMOJI = {
17
  // ═══════════════════════════════════════════════════════
18
  // STATE
19
  // ═══════════════════════════════════════════════════════
20
- let currentTask = 'drone_env.core.graders:grade_easy';
21
  let autoTimer = null;
22
  let logTimer = null;
23
  let obs = null;
@@ -601,12 +601,12 @@ function startNextTask() {
601
  closeCompletionModal();
602
 
603
  const sequence = {
604
- 'drone_env.core.graders:grade_easy': 'drone_env.core.graders:grade_medium',
605
- 'drone_env.core.graders:grade_medium': 'drone_env.core.graders:grade_hard',
606
- 'drone_env.core.graders:grade_hard': 'drone_env.core.graders:grade_easy'
607
  };
608
 
609
- const nextTask = sequence[currentTask] || 'drone_env.core.graders:grade_easy';
610
  currentTask = nextTask;
611
 
612
  // Update active state on buttons
@@ -627,7 +627,7 @@ async function updateMissionLegend() {
627
  if (!container || !data.tasks) return;
628
 
629
  // Ensure tasks are sorted Easy, Medium, Hard
630
- const order = ['drone_env.core.graders:grade_easy', 'drone_env.core.graders:grade_medium', 'drone_env.core.graders:grade_hard'];
631
  const tasks = data.tasks.sort((a, b) => order.indexOf(a.name) - order.indexOf(b.name));
632
 
633
  container.innerHTML = `
 
17
  // ═══════════════════════════════════════════════════════
18
  // STATE
19
  // ═══════════════════════════════════════════════════════
20
+ let currentTask = 'drone_env.graders.easy:grade_easy';
21
  let autoTimer = null;
22
  let logTimer = null;
23
  let obs = null;
 
601
  closeCompletionModal();
602
 
603
  const sequence = {
604
+ 'drone_env.graders.easy:grade_easy': 'drone_env.graders.medium:grade_medium',
605
+ 'drone_env.graders.medium:grade_medium': 'drone_env.graders.hard:grade_hard',
606
+ 'drone_env.graders.hard:grade_hard': 'drone_env.graders.easy:grade_easy'
607
  };
608
 
609
+ const nextTask = sequence[currentTask] || 'drone_env.graders.easy:grade_easy';
610
  currentTask = nextTask;
611
 
612
  // Update active state on buttons
 
627
  if (!container || !data.tasks) return;
628
 
629
  // Ensure tasks are sorted Easy, Medium, Hard
630
+ const order = ['drone_env.graders.easy:grade_easy', 'drone_env.graders.medium:grade_medium', 'drone_env.graders.hard:grade_hard'];
631
  const tasks = data.tasks.sort((a, b) => order.indexOf(a.name) - order.indexOf(b.name));
632
 
633
  container.innerHTML = `
tests/test_env.py CHANGED
@@ -12,14 +12,14 @@ from drone_env.core.grid_generator import generate_city_map
12
  from drone_env.core.tasks import TASK_CONFIG
13
  from drone_env.core.obstacles import check_move
14
  from drone_env.core.drone import compute_next_pos, drain_battery
15
- from drone_env.core.graders import grade_easy, grade_medium, grade_hard
16
  from drone_env.models import DroneState
17
 
18
 
19
  # ── Grid generator ────────────────────────────────────────────────────────────
20
 
21
  def test_grid_shape():
22
- cfg = TASK_CONFIG["easy_delivery"]
23
  rng = torch.Generator(); rng.manual_seed(42)
24
  grid, deliveries, start = generate_city_map(cfg, rng)
25
  assert len(grid) == cfg["height"]
@@ -30,7 +30,7 @@ def test_grid_shape():
30
 
31
 
32
  def test_start_on_road():
33
- cfg = TASK_CONFIG["easy_delivery"]
34
  rng = torch.Generator(); rng.manual_seed(7)
35
  grid, _, start = generate_city_map(cfg, rng)
36
  sx, sy = start
@@ -38,7 +38,7 @@ def test_start_on_road():
38
 
39
 
40
  def test_delivery_cells_present():
41
- cfg = TASK_CONFIG["medium_delivery"]
42
  rng = torch.Generator(); rng.manual_seed(99)
43
  grid, deliveries, _ = generate_city_map(cfg, rng)
44
  for dx, dy in deliveries:
@@ -129,9 +129,9 @@ def test_grade_medium_hard_bounds():
129
  # ── Task configs ──────────────────────────────────────────────────────────────
130
 
131
  def test_all_tasks_present():
132
- assert "easy_delivery" in TASK_CONFIG
133
- assert "medium_delivery" in TASK_CONFIG
134
- assert "hard_delivery" in TASK_CONFIG
135
 
136
  def test_task_has_required_keys():
137
  required = ["width","height","n_buildings","n_trees","n_obstacles",
@@ -142,9 +142,9 @@ def test_task_has_required_keys():
142
  assert key in cfg, f"{task} missing key: {key}"
143
 
144
  def test_task_difficulty_ordering():
145
- easy = TASK_CONFIG["easy_delivery"]
146
- medium = TASK_CONFIG["medium_delivery"]
147
- hard = TASK_CONFIG["hard_delivery"]
148
  assert easy["width"] < medium["width"] < hard["width"]
149
  assert easy["n_deliveries"] < medium["n_deliveries"] < hard["n_deliveries"]
150
  assert easy["max_steps"] < medium["max_steps"] < hard["max_steps"]
 
12
  from drone_env.core.tasks import TASK_CONFIG
13
  from drone_env.core.obstacles import check_move
14
  from drone_env.core.drone import compute_next_pos, drain_battery
15
+ from drone_env.graders import grade_easy, grade_medium, grade_hard
16
  from drone_env.models import DroneState
17
 
18
 
19
  # ── Grid generator ────────────────────────────────────────────────────────────
20
 
21
  def test_grid_shape():
22
+ cfg = TASK_CONFIG["drone_env.graders.easy:grade_easy"]
23
  rng = torch.Generator(); rng.manual_seed(42)
24
  grid, deliveries, start = generate_city_map(cfg, rng)
25
  assert len(grid) == cfg["height"]
 
30
 
31
 
32
  def test_start_on_road():
33
+ cfg = TASK_CONFIG["drone_env.graders.easy:grade_easy"]
34
  rng = torch.Generator(); rng.manual_seed(7)
35
  grid, _, start = generate_city_map(cfg, rng)
36
  sx, sy = start
 
38
 
39
 
40
  def test_delivery_cells_present():
41
+ cfg = TASK_CONFIG["drone_env.graders.medium:grade_medium"]
42
  rng = torch.Generator(); rng.manual_seed(99)
43
  grid, deliveries, _ = generate_city_map(cfg, rng)
44
  for dx, dy in deliveries:
 
129
  # ── Task configs ──────────────────────────────────────────────────────────────
130
 
131
  def test_all_tasks_present():
132
+ assert "drone_env.graders.easy:grade_easy" in TASK_CONFIG
133
+ assert "drone_env.graders.medium:grade_medium" in TASK_CONFIG
134
+ assert "drone_env.graders.hard:grade_hard" in TASK_CONFIG
135
 
136
  def test_task_has_required_keys():
137
  required = ["width","height","n_buildings","n_trees","n_obstacles",
 
142
  assert key in cfg, f"{task} missing key: {key}"
143
 
144
  def test_task_difficulty_ordering():
145
+ easy = TASK_CONFIG["drone_env.graders.easy:grade_easy"]
146
+ medium = TASK_CONFIG["drone_env.graders.medium:grade_medium"]
147
+ hard = TASK_CONFIG["drone_env.graders.hard:grade_hard"]
148
  assert easy["width"] < medium["width"] < hard["width"]
149
  assert easy["n_deliveries"] < medium["n_deliveries"] < hard["n_deliveries"]
150
  assert easy["max_steps"] < medium["max_steps"] < hard["max_steps"]