A-SW Hub / Repositories
Local Gitea · READ ONLY
← 저장소 Repositories
REPOSITORY

via/fruit-multi-robot-control Private

[참조 MVP] 과채류 다중로봇 관제 — 작업 주문, 로봇 가용성, OTA 상태를 연결하는 SaaS 관제 참조 MVP입니다.

https://git.agrithing.ai/via/fruit-multi-robot-control.git
4 commits
COMMIT DETAIL

feat: 과채류 다중로봇 관제 구체 MVP 구현

기관 승인 및 자동 품질검사를 통과한 구체 MVP 0.2.0 병합 Co-authored-by: via-dev <[email protected]>

via-dev <[email protected]> · 2026. 8. 3. 오후 3:12:55
8232a9c5c119eb406c72ec79f2a47df33cd45bb9
8 changed files +81 −9 parent 9216e1f
modified README.md +7−1 View file
@@ -11,6 +11,12 @@
11 11
12 12 작업 주문, 로봇 가용성, OTA 상태를 연결하는 SaaS 관제 참조 MVP입니다.
13 13
14 + ## MVP 동작 범위
15 +
16 + - 가용·배터리·역량 조건 필터링
17 + - 최적 로봇 작업 배정
18 + - 배정 불가 주문 대기열 전환
19 +
14 20 ## 실행
15 21
16 22 ```bash
@@ -18,4 +24,4 @@ python run.py examples/input.json
18 24 python -m unittest discover -s tests -v
19 25 ```
20 26
21 실제 기관 구현은 `contracts/component.interface.json`의 저장소 식별자와 제품 기준선 연결 규칙을 유지하면서 교체할 수 있습니다.
27 + 정상 입력은 `examples/input.json`, 차단·안전 경로는 `examples/failure-input.json`으로 재현합니다. 실제 기관 구현은 `contracts/component.interface.json`의 저장소 식별자와 제품 기준선 연결 규칙을 유지하면서 교체할 수 있습니다.
modified asw_component/component.py +11−3 View file
@@ -2,6 +2,14 @@ from __future__ import annotations
2 2
3 3
4 4 def execute(payload: dict) -> dict:
5 if not isinstance(payload, dict) or not payload:
6 raise ValueError("a non-empty object is required")
7 return {"component": "via/fruit-multi-robot-control", "accepted": True, "status": "READY", "target": "service", "input_fields": sorted(payload.keys())}
5 + robots = payload.get("robots", [])
6 + eligible = [robot for robot in robots if robot.get("state") == "AVAILABLE"
7 + and float(robot.get("batteryPercent", 0)) >= 30
8 + and "fruit_work" in robot.get("capabilities", [])]
9 + eligible.sort(key=lambda robot: (-float(robot["batteryPercent"]), robot["id"]))
10 + selected = eligible[0] if eligible else None
11 + assignment = None if not selected else {"robotId": selected["id"], "taskCount": int(payload.get("taskCount", 0)), "mode": "FRUIT_WORK"}
12 + return {"component": "via/fruit-multi-robot-control", "accepted": selected is not None,
13 + "status": "ASSIGNED" if selected else "QUEUED", "target": "service",
14 + "workOrder": str(payload.get("workOrder", "unknown")), "assignment": assignment,
15 + "queueReason": None if selected else "NO_ELIGIBLE_ROBOT", "evaluatedRobots": len(robots)}
modified contracts/component.interface.json +24−1 View file
@@ -5,7 +5,12 @@
5 5 "required": [
6 6 "component",
7 7 "accepted",
8 "status"
8 + "status",
9 + "target",
10 + "workOrder",
11 + "assignment",
12 + "queueReason",
13 + "evaluatedRobots"
9 14 ],
10 15 "properties": {
11 16 "component": {
@@ -19,6 +24,24 @@
19 24 },
20 25 "target": {
21 26 "const": "service"
27 + },
28 + "workOrder": {
29 + "type": "string"
30 + },
31 + "assignment": {
32 + "type": [
33 + "object",
34 + "null"
35 + ]
36 + },
37 + "queueReason": {
38 + "type": [
39 + "string",
40 + "null"
41 + ]
42 + },
43 + "evaluatedRobots": {
44 + "type": "integer"
22 45 }
23 46 }
24 47 }
added examples/failure-input.json +14−0 View file
@@ -0,0 +1,14 @@
1 + {
2 + "workOrder": "WO-FRUIT-082",
3 + "taskCount": 5,
4 + "robots": [
5 + {
6 + "id": "fruit-bot-02",
7 + "state": "CHARGING",
8 + "batteryPercent": 91,
9 + "capabilities": [
10 + "fruit_work"
11 + ]
12 + }
13 + ]
14 + }
modified examples/input.json +11−2 View file
@@ -1,13 +1,22 @@
1 1 {
2 2 "workOrder": "WO-FRUIT-081",
3 + "taskCount": 12,
3 4 "robots": [
4 5 {
5 6 "id": "fruit-bot-01",
6 "state": "AVAILABLE"
7 + "state": "AVAILABLE",
8 + "batteryPercent": 72,
9 + "capabilities": [
10 + "fruit_work"
11 + ]
7 12 },
8 13 {
9 14 "id": "fruit-bot-02",
10 "state": "CHARGING"
15 + "state": "CHARGING",
16 + "batteryPercent": 91,
17 + "capabilities": [
18 + "fruit_work"
19 + ]
11 20 }
12 21 ]
13 22 }
modified pyproject.toml +1−1 View file
@@ -1,5 +1,5 @@
1 1 [project]
2 2 name = "fruit-multi-robot-control"
3 version = "0.1.0"
3 + version = "0.2.0"
4 4 description = "작업 주문, 로봇 가용성, OTA 상태를 연결하는 SaaS 관제 참조 MVP입니다."
5 5 requires-python = ">=3.11"
modified release/manifest.json +1−1 View file
@@ -4,7 +4,7 @@
4 4 "displayName": "과채류 다중로봇 관제",
5 5 "owner": "주식회사 비아",
6 6 "implementationAuthor": "주식회사 비아 (연동 검증용 참조 구현)",
7 "version": "0.1.0-mvp",
7 + "version": "0.2.0-mvp",
8 8 "domain": "Service Platform",
9 9 "status": "REFERENCE_MVP",
10 10 "compatibility": {
modified tests/test_component.py +12−0 View file
@@ -12,6 +12,18 @@ class ComponentTest(unittest.TestCase):
12 12 result = execute(payload)
13 13 self.assertTrue(result["accepted"])
14 14 self.assertEqual(result["component"], "via/fruit-multi-robot-control")
15 + self.assertEqual(result["target"], "service")
16 + self.assertEqual(result["status"], "ASSIGNED")
17 + self.assertEqual(result["assignment"]["robotId"], "fruit-bot-01")
18 + self.assertEqual(result["assignment"]["taskCount"], 12)
19 +
20 + def test_safety_or_failure_path(self):
21 + payload = json.loads((ROOT / "examples/failure-input.json").read_text(encoding="utf-8"))
22 + result = execute(payload)
23 + self.assertEqual(result["component"], "via/fruit-multi-robot-control")
24 + self.assertFalse(result["accepted"])
25 + self.assertEqual(result["status"], "QUEUED")
26 + self.assertEqual(result["queueReason"], "NO_ELIGIBLE_ROBOT")
15 27
16 28 if __name__ == "__main__":
17 29 unittest.main()