Merge remote-tracking branch 'origin/feat/recieve-programs-ui' into demo

This commit is contained in:
Twirre Meulenbelt
2025-11-12 13:39:53 +01:00
4 changed files with 94 additions and 2 deletions

View File

@@ -30,7 +30,7 @@ HEADER=$(head -n 1 "$COMMIT_MSG_FILE")
# Check for Merge commits (covers 'git merge' and PR merges from GitHub/GitLab)
# Examples: "Merge branch 'main' into ...", "Merge pull request #123 from ..."
MERGE_PATTERN="^Merge (branch|pull request|tag) .*"
MERGE_PATTERN="^Merge (remote-tracking )?(branch|pull request|tag) .*"
if [[ "$HEADER" =~ $MERGE_PATTERN ]]; then
echo -e "${GREEN}Merge commit detected by message content. Skipping validation.${NC}"
exit 0

View File

@@ -0,0 +1,52 @@
import json
import logging
from fastapi import APIRouter, HTTPException, Request
from control_backend.schemas.message import Message
from control_backend.schemas.program import Phase
logger = logging.getLogger(__name__)
router = APIRouter()
@router.post("/program", status_code=202)
async def receive_message(program: Message, request: Request):
"""
Receives a BehaviorProgram as a stringified JSON list inside `message`.
Converts it into real Phase objects.
"""
logger.info("Received raw program: ")
logger.debug("%s", program)
raw_str = program.message # This is the JSON string
# Convert Json into dict.
try:
program_list = json.loads(raw_str)
except json.JSONDecodeError as e:
logger.error("Failed to decode program JSON: %s", e)
raise HTTPException(status_code=400, detail="Undecodeable Json string") from None
# Validate Phases
try:
phases: list[Phase] = [Phase(**phase) for phase in program_list]
except Exception as e:
logger.error("❌ Failed to convert to Phase objects: %s", e)
raise HTTPException(status_code=400, detail="Non-Phase String") from None
logger.info(f"Succesfully recieved {len(phases)} Phase(s).")
for p in phases:
logger.info(
f"Phase {p.id}: "
f"{len(p.phaseData.norms)} norms, "
f"{len(p.phaseData.goals)} goals, "
f"{len(p.phaseData.triggers) if hasattr(p.phaseData, 'triggers') else 0} triggers"
)
# send away
topic = b"program"
body = json.dumps([p.model_dump() for p in phases]).encode("utf-8")
pub_socket = request.app.state.endpoints_pub_socket
await pub_socket.send_multipart([topic, body])
return {"status": "Program parsed", "phase_count": len(phases)}

View File

@@ -1,6 +1,6 @@
from fastapi.routing import APIRouter
from control_backend.api.v1.endpoints import command, logs, message, sse
from control_backend.api.v1.endpoints import command, logs, message, program, sse
api_router = APIRouter()
@@ -11,3 +11,5 @@ api_router.include_router(sse.router, tags=["SSE"])
api_router.include_router(command.router, tags=["Commands"])
api_router.include_router(logs.router, tags=["Logs"])
api_router.include_router(program.router, tags=["Program"])

View File

@@ -0,0 +1,38 @@
from pydantic import BaseModel
class Norm(BaseModel):
id: str
name: str
value: str
class Goal(BaseModel):
id: str
name: str
description: str
achieved: bool
class Trigger(BaseModel):
id: str
label: str
type: str
value: list[str]
class PhaseData(BaseModel):
norms: list[Norm]
goals: list[Goal]
triggers: list[Trigger]
class Phase(BaseModel):
id: str
name: str
nextPhaseId: str
phaseData: PhaseData
class Program(BaseModel):
phases: list[Phase]