File size: 4,639 Bytes
177c40c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
"""
Domain model for your optimization problem.

This file defines:
1. Problem facts (@dataclass) - immutable input data
2. Planning entities (@planning_entity) - what the solver assigns
3. Planning solution (@planning_solution) - container for the problem

TODO: Replace this example with your own domain model.
"""

from dataclasses import dataclass, field
from typing import Annotated, Optional, List
from datetime import datetime

from solverforge_legacy.solver import SolverStatus
from solverforge_legacy.solver.domain import (
    planning_entity,
    planning_solution,
    PlanningId,
    PlanningVariable,
    PlanningEntityCollectionProperty,
    ProblemFactCollectionProperty,
    ValueRangeProvider,
    PlanningScore,
)
from solverforge_legacy.solver.score import HardSoftScore

from .json_serialization import JsonDomainBase
from pydantic import Field


# =============================================================================
# PROBLEM FACTS (immutable input data)
# =============================================================================

@dataclass
class Resource:
    """
    A resource that can be assigned to tasks.

    TODO: Replace with your own problem fact (e.g., Employee, Room, Vehicle).
    """
    name: Annotated[str, PlanningId]
    capacity: int = 100
    skills: set[str] = field(default_factory=set)


# =============================================================================
# PLANNING ENTITIES (what the solver optimizes)
# =============================================================================

@planning_entity
@dataclass
class Task:
    """
    A task to be assigned to a resource.

    The `resource` field is the planning variable - the solver will
    try different assignments to find the best solution.

    TODO: Replace with your own planning entity (e.g., Shift, Lesson, Delivery).
    """
    id: Annotated[str, PlanningId]
    name: str
    duration: int  # in minutes
    required_skill: str = ""

    # This is the planning variable - solver assigns this
    resource: Annotated[Resource | None, PlanningVariable] = None

    def has_required_skill(self) -> bool:
        """Check if assigned resource has the required skill.

        NOTE: We use len(str(...)) instead of boolean check because
        required_skill may be a Java String during constraint evaluation.
        """
        if self.resource is None:
            return False
        if len(str(self.required_skill)) == 0:
            return True
        return str(self.required_skill) in self.resource.skills


# =============================================================================
# PLANNING SOLUTION (container)
# =============================================================================

@planning_solution
@dataclass
class Schedule:
    """
    The planning solution containing all problem facts and planning entities.

    TODO: Rename to match your domain (e.g., Timetable, RoutePlan, Roster).
    """
    resources: Annotated[
        list[Resource],
        ProblemFactCollectionProperty,
        ValueRangeProvider
    ]
    tasks: Annotated[list[Task], PlanningEntityCollectionProperty]
    score: Annotated[HardSoftScore | None, PlanningScore] = None
    solver_status: SolverStatus = SolverStatus.NOT_SOLVING


# =============================================================================
# PYDANTIC MODELS (for REST API serialization)
# =============================================================================

class ResourceModel(JsonDomainBase):
    """Pydantic model for Resource serialization."""
    name: str
    capacity: int = 100
    skills: List[str] = Field(default_factory=list)


class TaskModel(JsonDomainBase):
    """Pydantic model for Task serialization."""
    id: str
    name: str
    duration: int
    required_skill: str = Field(default="", alias="requiredSkill")
    resource: Optional[str] = None  # Resource name or None


class ConstraintWeightsModel(JsonDomainBase):
    """Pydantic model for constraint weight configuration."""
    required_skill: int = Field(default=100, ge=0, le=100)
    resource_capacity: int = Field(default=100, ge=0, le=100)
    minimize_duration: int = Field(default=50, ge=0, le=100)
    balance_load: int = Field(default=50, ge=0, le=100)


class ScheduleModel(JsonDomainBase):
    """Pydantic model for Schedule serialization."""
    resources: List[ResourceModel]
    tasks: List[TaskModel]
    score: Optional[str] = None
    solver_status: Optional[str] = Field(default=None, alias="solverStatus")
    constraint_weights: Optional[ConstraintWeightsModel] = Field(default=None, alias="constraintWeights")