Godswill-IoT commited on
Commit
3330e82
·
verified ·
1 Parent(s): 9290064

Upload 14 files

Browse files
Dockerfile ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ # Set up a new user named "user" with user ID 1000
4
+ RUN useradd -m -u 1000 user
5
+
6
+ # Switch to the "user" user
7
+ USER user
8
+
9
+ # Set home to the user's home directory
10
+ ENV HOME=/home/user \
11
+ PATH=/home/user/.local/bin:$PATH
12
+
13
+ # Set the working directory to the user's home directory
14
+ WORKDIR $HOME/app
15
+
16
+ # Try and run pip command after setting the user with `USER user` to avoid permission issues
17
+ RUN pip install --no-cache-dir --upgrade pip
18
+
19
+ # Copy requirements and install dependencies
20
+ COPY --chown=user requirements.txt .
21
+ RUN pip install --no-cache-dir -r requirements.txt
22
+
23
+ # Copy the application code
24
+ COPY --chown=user app/ ./app/
25
+
26
+ # Expose port 7860 (HF Spaces default)
27
+ EXPOSE 7860
28
+
29
+ # Run the application on port 7860
30
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -1,12 +1,27 @@
1
- ---
2
- title: Mdc Engine
3
- emoji: 🦀
4
- colorFrom: blue
5
- colorTo: green
6
- sdk: docker
7
- pinned: false
8
- license: lgpl-3.0
9
- short_description: This is the Multi-Desciplinery Content Engine
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Multi-Disciplinary Thinking Engine
3
+ emoji: 🔀
4
+ colorFrom: pink
5
+ colorTo: purple
6
+ sdk: docker
7
+ app_port: 7860
8
+ ---
9
+
10
+ # Multi-Disciplinary Thinking Engine
11
+
12
+ Generates cross-domain learning experiences that connect concepts across disciplines.
13
+
14
+ ## Features
15
+
16
+ - **Cross-Domain Mapping**: Links concepts across subjects
17
+ - **Integrated Assessments**: Multi-disciplinary challenges
18
+ - **Transfer Learning**: Reinforces concept portability
19
+ - **Holistic Understanding**: Builds interconnected knowledge
20
+
21
+ ## API
22
+
23
+ **Endpoint**: `POST /run`
24
+
25
+ ## Deployment
26
+
27
+ This service is configured for Hugging Face Spaces deployment with Docker SDK.
app/api/routes.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Depends, HTTPException
2
+ from app.contracts.dtos import (
3
+ MultiDisciplinaryRequest, MDTPlan, ToggleModeRequest, ToggleModeResponse,
4
+ SelectDisciplinesResponse, TransferCheckRequest, TransferCheckResponse,
5
+ UpdateMasteryRequest, UpdateMasteryResponse
6
+ )
7
+ from app.container import container
8
+ from typing import List
9
+
10
+ router = APIRouter()
11
+
12
+ @router.post("/toggle-mode", response_model=ToggleModeResponse)
13
+ async def toggle_mode(request: ToggleModeRequest):
14
+ controller = container.get("mode_controller")
15
+ if not controller:
16
+ raise HTTPException(status_code=500, detail="Service not available")
17
+ return controller.toggle_mode(request.learner_id, request.enabled)
18
+
19
+ @router.post("/select-disciplines", response_model=SelectDisciplinesResponse)
20
+ async def select_disciplines(concept_id: str, learner_profile: dict): # Simplified input for now
21
+ router_service = container.get("router_service")
22
+ if not router_service:
23
+ raise HTTPException(status_code=500, detail="Service not available")
24
+ disciplines = router_service.select_disciplines(concept_id, learner_profile)
25
+ return SelectDisciplinesResponse(concept_id=concept_id, ranked_disciplines=disciplines)
26
+
27
+ @router.post("/plan", response_model=MDTPlan)
28
+ async def build_mdt_plan(request: MultiDisciplinaryRequest):
29
+ stitcher_service = container.get("stitcher_service")
30
+ if not stitcher_service:
31
+ raise HTTPException(status_code=500, detail="Service not available")
32
+ return stitcher_service.build_mdt_plan(request)
33
+
34
+ @router.post("/generate")
35
+ async def generate_mdt_course(mdt_plan: MDTPlan):
36
+ adapter = container.get("learning_os_adapter")
37
+ if not adapter:
38
+ raise HTTPException(status_code=500, detail="Service not available")
39
+ return adapter.generate_mdt_course(mdt_plan)
40
+
41
+ @router.post("/transfer-check", response_model=TransferCheckResponse)
42
+ async def run_transfer_checks(request: TransferCheckRequest):
43
+ assessment_service = container.get("assessment_service")
44
+ if not assessment_service:
45
+ raise HTTPException(status_code=500, detail="Service not available")
46
+ return assessment_service.run_transfer_checks(request.learner_id, request.target_concept_id, request.disciplines)
47
+
48
+ @router.post("/update-mastery", response_model=UpdateMasteryResponse)
49
+ async def update_mastery(request: UpdateMasteryRequest):
50
+ assessment_service = container.get("assessment_service")
51
+ if not assessment_service:
52
+ raise HTTPException(status_code=500, detail="Service not available")
53
+ return assessment_service.update_mastery(request.learner_id, request.evidence)
app/container.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, Any
2
+
3
+ class Container(dict):
4
+ def __init__(self):
5
+ super().__init__()
6
+ self._services = {}
7
+
8
+ def register(self, name: str, service: Any):
9
+ self._services[name] = service
10
+ self[name] = service
11
+
12
+ def get(self, name: str) -> Any:
13
+ return self._services.get(name)
14
+
15
+ # Global container instance
16
+ container = Container()
app/contracts/dtos.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field
2
+ from typing import List, Dict, Optional, Any
3
+
4
+ class Discipline(BaseModel):
5
+ name: str
6
+ role: str # e.g., "core_model", "causal_story", "real_world_context"
7
+
8
+ class LessonStep(BaseModel):
9
+ step: int
10
+ lens: str
11
+ objective: str
12
+
13
+ class MultiDisciplinaryRequest(BaseModel):
14
+ learner_id: str
15
+ target_concept_id: str
16
+ goal: str
17
+ level: str
18
+ time_budget_min: int
19
+ selected_disciplines: Optional[List[str]] = None
20
+ mode: str = "multidisciplinary"
21
+
22
+ class MDTPlan(BaseModel):
23
+ target_concept_id: str
24
+ disciplines: List[Discipline]
25
+ lesson_outline: List[LessonStep]
26
+ generated_by: str = "LearningOS"
27
+ assets: List[str]
28
+
29
+ class ToggleModeRequest(BaseModel):
30
+ learner_id: str
31
+ enabled: bool
32
+
33
+ class ToggleModeResponse(BaseModel):
34
+ learner_id: str
35
+ mode_active: bool
36
+
37
+ class SelectDisciplinesResponse(BaseModel):
38
+ concept_id: str
39
+ ranked_disciplines: List[str]
40
+
41
+ class TransferCheckRequest(BaseModel):
42
+ learner_id: str
43
+ target_concept_id: str
44
+ disciplines: List[str]
45
+
46
+ class TransferCheckResponse(BaseModel):
47
+ results: Dict[str, Any]
48
+
49
+ class UpdateMasteryRequest(BaseModel):
50
+ learner_id: str
51
+ evidence: Dict[str, Any]
52
+
53
+ class UpdateMasteryResponse(BaseModel):
54
+ mastery_deltas: Dict[str, float]
app/main.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from app.api.routes import router
3
+ from app.container import container
4
+
5
+ app = FastAPI(title="Multi-Disciplinary Training Engine", version="1.0.0")
6
+ app.include_router(router)
7
+
8
+ @app.get("/")
9
+ def read_root():
10
+ return {"status": "ok", "service": "multidisciplinary-engine"}
11
+
12
+ from app.services.router_service import RouterService
13
+ from app.services.stitcher_service import StitcherService
14
+ from app.services.learning_os_adapter import LearningOSAdapter
15
+ from app.services.mode_controller import ModeToggleController
16
+ from app.services.assessment_service import AssessmentService
17
+
18
+ @app.on_event("startup")
19
+ async def startup():
20
+ container.register("router_service", RouterService())
21
+ container.register("stitcher_service", StitcherService())
22
+ container.register("learning_os_adapter", LearningOSAdapter())
23
+ container.register("mode_controller", ModeToggleController())
24
+ container.register("assessment_service", AssessmentService())
app/services/assessment_service.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Dict, Any
2
+ from app.contracts.dtos import TransferCheckResponse, UpdateMasteryResponse
3
+
4
+ class AssessmentService:
5
+ def run_transfer_checks(self, learner_id: str, target_concept_id: str, disciplines: List[str]) -> TransferCheckResponse:
6
+ # TODO: Implement logic to generate or retrieve transfer check questions
7
+ results = {
8
+ "concept": target_concept_id,
9
+ "checks": [
10
+ {"discipline": d, "question": f"Explain {target_concept_id} using {d} concepts", "status": "pending"}
11
+ for d in disciplines
12
+ ]
13
+ }
14
+ return TransferCheckResponse(results=results)
15
+
16
+ def update_mastery(self, learner_id: str, evidence: Dict[str, Any]) -> UpdateMasteryResponse:
17
+ # TODO: Implement logic to calculate mastery deltas based on evidence
18
+ mastery_deltas = {
19
+ "economics": 0.5,
20
+ "history": 0.2
21
+ }
22
+ return UpdateMasteryResponse(mastery_deltas=mastery_deltas)
app/services/learning_os_adapter.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import httpx
2
+ from app.contracts.dtos import MDTPlan
3
+ from typing import Dict, Any
4
+
5
+ class LearningOSAdapter:
6
+ def __init__(self, base_url: str = "http://learning-os-api"):
7
+ self.base_url = base_url
8
+
9
+ def generate_mdt_course(self, mdt_plan: MDTPlan) -> Dict[str, Any]:
10
+ # TODO: Implement actual API call to Learning OS
11
+ # async with httpx.AsyncClient() as client:
12
+ # response = await client.post(f"{self.base_url}/generate", json=mdt_plan.dict())
13
+ # return response.json()
14
+
15
+ # Mock response
16
+ return {
17
+ "status": "success",
18
+ "course_id": "course_123",
19
+ "assets": mdt_plan.assets,
20
+ "message": "Course generated successfully via Learning OS"
21
+ }
app/services/mode_controller.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app.contracts.dtos import ToggleModeResponse
2
+
3
+ class ModeToggleController:
4
+ def __init__(self):
5
+ # simple in-memory storage for MVP
6
+ self._learner_modes = {}
7
+
8
+ def toggle_mode(self, learner_id: str, enabled: bool) -> ToggleModeResponse:
9
+ self._learner_modes[learner_id] = enabled
10
+ # In a real system, this would trigger UI events or update a persistent user profile
11
+ return ToggleModeResponse(
12
+ learner_id=learner_id,
13
+ mode_active=enabled
14
+ )
app/services/router_service.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Dict
2
+
3
+ class RouterService:
4
+ def select_disciplines(self, concept_id: str, learner_profile: dict) -> List[str]:
5
+ # TODO: Implement actual logic to rank disciplines based on concept and learner profile
6
+ # For now, return hardcoded disciplines as per MVP rules
7
+ # "Limit to 2–3 supporting disciplines"
8
+ # "Always include: Core lens, Story lens, Application lens"
9
+
10
+ # Mock logic
11
+ if "inflation" in concept_id.lower():
12
+ return ["economics", "history", "geography"]
13
+
14
+ # Default fallback
15
+ return ["science", "history", "mathematics"]
app/services/stitcher_service.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List
2
+ from app.contracts.dtos import MultiDisciplinaryRequest, MDTPlan, Discipline, LessonStep
3
+
4
+ class StitcherService:
5
+ def build_mdt_plan(self, request: MultiDisciplinaryRequest) -> MDTPlan:
6
+ # TODO: Implement AI logic to weave disciplines together
7
+
8
+ # Mock implementation based on MVP rules
9
+ disciplines = [
10
+ Discipline(name=d, role="core_model" if i==0 else "causal_story" if i==1 else "real_world_context")
11
+ for i, d in enumerate(request.selected_disciplines or ["economics", "history", "geography"])
12
+ ]
13
+
14
+ lesson_outline = [
15
+ LessonStep(step=1, lens=disciplines[0].name, objective=f"Define {request.target_concept_id} + mechanisms"),
16
+ LessonStep(step=2, lens=disciplines[1].name, objective="Case study explanation"),
17
+ LessonStep(step=3, lens=disciplines[2].name, objective="Real world context and application"),
18
+ LessonStep(step=4, lens="integration", objective="Apply to learner’s context")
19
+ ]
20
+
21
+ return MDTPlan(
22
+ target_concept_id=request.target_concept_id,
23
+ disciplines=disciplines,
24
+ lesson_outline=lesson_outline,
25
+ generated_by="LearningOS",
26
+ assets=["lesson://generated-id-1", "quiz://generated-id-1"] # Mock assets
27
+ )
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ fastapi==0.115.0
2
+ uvicorn[standard]==0.30.6
3
+ pydantic==2.8.2
4
+ pydantic-settings==2.4.0
5
+ httpx==0.27.2
swagger_tests.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "name": "Build MDT Plan",
4
+ "endpoint": "/plan",
5
+ "payload": {
6
+ "learner_id": "learner_123",
7
+ "target_concept_id": "Evolution",
8
+ "goal": "Understand natural selection through biological and historical lenses.",
9
+ "level": "intermediate",
10
+ "time_budget_min": 60,
11
+ "mode": "multidisciplinary"
12
+ }
13
+ }
14
+ ]
tests/test_api.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi.testclient import TestClient
2
+ from app.main import app
3
+
4
+ client = TestClient(app)
5
+
6
+ def test_read_root():
7
+ response = client.get("/")
8
+ assert response.status_code == 200
9
+ assert response.json() == {"status": "ok", "service": "multidisciplinary-engine"}
10
+
11
+ def test_toggle_mode():
12
+ response = client.post("/toggle-mode", json={"learner_id": "L1", "enabled": True})
13
+ assert response.status_code == 200
14
+ assert response.json()["mode_active"] is True
15
+ assert response.json()["learner_id"] == "L1"
16
+
17
+ def test_select_disciplines():
18
+ response = client.post("/select-disciplines?concept_id=inflation", json={"learner_id": "L1"}) # passing learner_profile as body
19
+ assert response.status_code == 200
20
+ data = response.json()
21
+ assert "economics" in data["ranked_disciplines"]
22
+ assert "history" in data["ranked_disciplines"]
23
+
24
+ def test_build_plan():
25
+ # Helper to enable mode first if needed (though service currently stateless/mocked)
26
+ req_data = {
27
+ "learner_id": "L1",
28
+ "target_concept_id": "inflation",
29
+ "goal": "understand",
30
+ "level": "beginner",
31
+ "time_budget_min": 30,
32
+ "selected_disciplines": ["economics", "history", "geography"]
33
+ }
34
+ response = client.post("/plan", json=req_data)
35
+ assert response.status_code == 200
36
+ data = response.json()
37
+ assert data["target_concept_id"] == "inflation"
38
+ assert len(data["lesson_outline"]) > 0
39
+ assert data["generated_by"] == "LearningOS"
40
+
41
+ def test_generate_course():
42
+ # Mock plan
43
+ plan = {
44
+ "target_concept_id": "inflation",
45
+ "disciplines": [{"name": "eco", "role": "core"}],
46
+ "lesson_outline": [{"step": 1, "lens": "eco", "objective": "test"}],
47
+ "generated_by": "LearningOS",
48
+ "assets": []
49
+ }
50
+ response = client.post("/generate", json=plan)
51
+ assert response.status_code == 200
52
+ assert response.json()["status"] == "success"