avant changement UI
This commit is contained in:
parent
793393b294
commit
a9bc1e47db
122
chat.json
122
chat.json
@ -1,41 +1,111 @@
|
|||||||
{
|
{
|
||||||
"employees": [
|
"employees": [
|
||||||
{
|
{
|
||||||
"name": "Employee1",
|
"name": "Aurélie Antoine",
|
||||||
"skills": ["Skill1", "Skill2"],
|
"skills": ["INFIRMIER", "PRELEVEMENT"],
|
||||||
"unavailableDates": ["2025-01-01", "2025-01-02"],
|
"unavailableDates": [],
|
||||||
"undesiredDates": ["2025-01-03"],
|
"undesiredDates": [],
|
||||||
"desiredDates": ["2025-01-04"]
|
"desiredDates": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Employee2",
|
"name": "Cathy Coucou",
|
||||||
"skills": ["Skill2", "Skill3"],
|
"skills": ["MEDECIN", "SUPERVISION"],
|
||||||
"unavailableDates": ["2025-01-02"],
|
"unavailableDates": [],
|
||||||
"undesiredDates": ["2025-01-05"],
|
"undesiredDates": [],
|
||||||
"desiredDates": ["2025-01-06"]
|
"desiredDates": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Sophie Bernard-Dupont",
|
||||||
|
"skills": ["INFIRMIER", "ACCUEIL"],
|
||||||
|
"unavailableDates": [],
|
||||||
|
"undesiredDates": [],
|
||||||
|
"desiredDates": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jean Leroy",
|
||||||
|
"skills": ["TRANSPORT", "PRELEVEMENT"],
|
||||||
|
"unavailableDates": [],
|
||||||
|
"undesiredDates": [],
|
||||||
|
"desiredDates": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Anne Moreau",
|
||||||
|
"skills": ["MEDECIN", "INFIRMIER"],
|
||||||
|
"unavailableDates": [],
|
||||||
|
"undesiredDates": [],
|
||||||
|
"desiredDates": ["2025-12-21", "2025-12-29"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Luc Petit",
|
||||||
|
"skills": ["ACCUEIL", "TRANSPORT"],
|
||||||
|
"unavailableDates": [],
|
||||||
|
"undesiredDates": [],
|
||||||
|
"desiredDates": ["2024-12-25", "2024-12-26"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Marie Dubois",
|
||||||
|
"skills": ["INFIRMIER", "ACCUEIL"],
|
||||||
|
"unavailableDates": [],
|
||||||
|
"undesiredDates": [],
|
||||||
|
"desiredDates": ["2024-12-22", "2024-12-23"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Pierre Martin",
|
||||||
|
"skills": ["MEDECIN", "SUPERVISION"],
|
||||||
|
"unavailableDates": [],
|
||||||
|
"undesiredDates": ["2024-12-21"],
|
||||||
|
"desiredDates": ["2024-12-29", "2024-12-30"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"shifts": [
|
"collectes": [
|
||||||
{
|
{
|
||||||
"id": "Shift1",
|
"id": "collecte_toulouse_20241220",
|
||||||
"start": "2025-01-01T08:00:00",
|
"start": "2024-12-20T08:00:00",
|
||||||
"end": "2025-01-01T16:00:00",
|
"end": "2024-12-20T16:00:00",
|
||||||
"location": "Location1",
|
"location": "Centre de collecte - Toulouse",
|
||||||
"requiredSkill": "Skill1",
|
"requiredSkills": {
|
||||||
"employee": {
|
"INFIRMIER": 2,
|
||||||
"name": "Employee1"
|
"MEDECIN": 1,
|
||||||
|
"ACCUEIL": 1,
|
||||||
|
"TRANSPORT": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Shift2",
|
"id": "collecte_blagnac_20241221",
|
||||||
"start": "2025-01-02T08:00:00",
|
"start": "2024-12-21T08:00:00",
|
||||||
"end": "2025-01-02T16:00:00",
|
"end": "2024-12-21T16:00:00",
|
||||||
"location": "Location2",
|
"location": "Centre de collecte - Blagnac",
|
||||||
"requiredSkill": "Skill2",
|
"requiredSkills": {
|
||||||
"employee": {
|
"INFIRMIER": 2,
|
||||||
"name": "Employee2"
|
"MEDECIN": 1,
|
||||||
|
"ACCUEIL": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collecte_purpan_20241222",
|
||||||
|
"start": "2024-12-22T08:00:00",
|
||||||
|
"end": "2024-12-22T16:00:00",
|
||||||
|
"location": "Hôpital Purpan - Toulouse",
|
||||||
|
"requiredSkills": {
|
||||||
|
"INFIRMIER": 3,
|
||||||
|
"MEDECIN": 1,
|
||||||
|
"ACCUEIL": 1,
|
||||||
|
"TRANSPORT": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collecte_rangueil_20241223",
|
||||||
|
"start": "2024-12-23T08:00:00",
|
||||||
|
"end": "2024-12-23T16:00:00",
|
||||||
|
"location": "Hôpital Rangueil - Toulouse",
|
||||||
|
"requiredSkills": {
|
||||||
|
"INFIRMIER": 2,
|
||||||
|
"MEDECIN": 1,
|
||||||
|
"PRELEVEMENT": 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"score": null,
|
||||||
|
"solverStatus": null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
114
chat2.json
Normal file
114
chat2.json
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
{
|
||||||
|
"employees": [
|
||||||
|
{
|
||||||
|
"name": "Aurélie Antoine",
|
||||||
|
"skills": ["INFIRMIER", "PRELEVEMENT"],
|
||||||
|
"unavailableDates": [],
|
||||||
|
"undesiredDates": [],
|
||||||
|
"desiredDates": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cathy Coucou",
|
||||||
|
"skills": ["MEDECIN", "SUPERVISION"],
|
||||||
|
"unavailableDates": [],
|
||||||
|
"undesiredDates": [],
|
||||||
|
"desiredDates": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Sophie Bernard-Dupont",
|
||||||
|
"skills": ["INFIRMIER", "ACCUEIL"],
|
||||||
|
"unavailableDates": [],
|
||||||
|
"undesiredDates": [],
|
||||||
|
"desiredDates": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"collectes": [
|
||||||
|
{
|
||||||
|
"id": "collecte_toulouse_20241220",
|
||||||
|
"start": "2024-12-20T08:00:00",
|
||||||
|
"end": "2024-12-20T16:00:00",
|
||||||
|
"location": "Centre de collecte - Toulouse",
|
||||||
|
"requiredSkills": {
|
||||||
|
"INFIRMIER": 2,
|
||||||
|
"MEDECIN": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collecte_blagnac_20241221",
|
||||||
|
"start": "2024-12-21T08:00:00",
|
||||||
|
"end": "2024-12-21T16:00:00",
|
||||||
|
"location": "Centre de collecte - Blagnac",
|
||||||
|
"requiredSkills": {
|
||||||
|
"INFIRMIER": 1,
|
||||||
|
"MEDECIN": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"shifts": [
|
||||||
|
{
|
||||||
|
"id": "shift_toulouse_1",
|
||||||
|
"start": "2024-12-20T08:00:00",
|
||||||
|
"end": "2024-12-20T16:00:00",
|
||||||
|
"location": "Centre de collecte - Toulouse",
|
||||||
|
"requiredSkill": "INFIRMIER",
|
||||||
|
"collecte": {
|
||||||
|
"id": "collecte_toulouse_20241220",
|
||||||
|
"start": "2024-12-20T08:00:00",
|
||||||
|
"end": "2024-12-20T16:00:00",
|
||||||
|
"location": "Centre de collecte - Toulouse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "shift_toulouse_2",
|
||||||
|
"start": "2024-12-20T08:00:00",
|
||||||
|
"end": "2024-12-20T16:00:00",
|
||||||
|
"location": "Centre de collecte - Toulouse",
|
||||||
|
"requiredSkill": "INFIRMIER",
|
||||||
|
"collecte": {
|
||||||
|
"id": "collecte_toulouse_20241220",
|
||||||
|
"start": "2024-12-20T08:00:00",
|
||||||
|
"end": "2024-12-20T16:00:00",
|
||||||
|
"location": "Centre de collecte - Toulouse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "shift_toulouse_3",
|
||||||
|
"start": "2024-12-20T08:00:00",
|
||||||
|
"end": "2024-12-20T16:00:00",
|
||||||
|
"location": "Centre de collecte - Toulouse",
|
||||||
|
"requiredSkill": "MEDECIN",
|
||||||
|
"collecte": {
|
||||||
|
"id": "collecte_toulouse_20241220",
|
||||||
|
"start": "2024-12-20T08:00:00",
|
||||||
|
"end": "2024-12-20T16:00:00",
|
||||||
|
"location": "Centre de collecte - Toulouse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "shift_blagnac_1",
|
||||||
|
"start": "2024-12-21T08:00:00",
|
||||||
|
"end": "2024-12-21T16:00:00",
|
||||||
|
"location": "Centre de collecte - Blagnac",
|
||||||
|
"requiredSkill": "INFIRMIER",
|
||||||
|
"collecte": {
|
||||||
|
"id": "collecte_blagnac_20241221",
|
||||||
|
"start": "2024-12-21T08:00:00",
|
||||||
|
"end": "2024-12-21T16:00:00",
|
||||||
|
"location": "Centre de collecte - Blagnac"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "shift_blagnac_2",
|
||||||
|
"start": "2024-12-21T08:00:00",
|
||||||
|
"end": "2024-12-21T16:00:00",
|
||||||
|
"location": "Centre de collecte - Blagnac",
|
||||||
|
"requiredSkill": "MEDECIN",
|
||||||
|
"collecte": {
|
||||||
|
"id": "collecte_blagnac_20241221",
|
||||||
|
"start": "2024-12-21T08:00:00",
|
||||||
|
"end": "2024-12-21T16:00:00",
|
||||||
|
"location": "Centre de collecte - Blagnac"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
268
chat3.json
Normal file
268
chat3.json
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
{
|
||||||
|
"employees": [
|
||||||
|
{
|
||||||
|
"name": "Aurélie Antoine",
|
||||||
|
"skills": ["INFIRMIER", "PRELEVEMENT"],
|
||||||
|
"unavailableDates": ["2024-12-25", "2024-12-26", "2024-12-31"],
|
||||||
|
"undesiredDates": ["2024-12-24", "2024-12-30"],
|
||||||
|
"desiredDates": ["2024-12-20", "2024-12-21"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cathy Coucou",
|
||||||
|
"skills": ["MEDECIN", "SUPERVISION"],
|
||||||
|
"unavailableDates": ["2024-12-30", "2024-12-31"],
|
||||||
|
"undesiredDates": ["2024-12-29", "2024-12-28"],
|
||||||
|
"desiredDates": ["2024-12-22", "2024-12-23"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Sophie Bernard-Dupont",
|
||||||
|
"skills": ["INFIRMIER", "ACCUEIL"],
|
||||||
|
"unavailableDates": ["2024-12-27", "2024-12-28"],
|
||||||
|
"undesiredDates": ["2024-12-25", "2024-12-26"],
|
||||||
|
"desiredDates": ["2024-12-24", "2024-12-29"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jean Leroy",
|
||||||
|
"skills": ["TRANSPORT", "PRELEVEMENT"],
|
||||||
|
"unavailableDates": ["2024-12-24", "2024-12-25"],
|
||||||
|
"undesiredDates": ["2024-12-23", "2024-12-22"],
|
||||||
|
"desiredDates": ["2024-12-27", "2024-12-30"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Anne Moreau",
|
||||||
|
"skills": ["MEDECIN", "INFIRMIER"],
|
||||||
|
"unavailableDates": ["2024-12-26", "2024-12-27"],
|
||||||
|
"undesiredDates": ["2024-12-31", "2024-12-20"],
|
||||||
|
"desiredDates": ["2024-12-21", "2024-12-29"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Luc Petit",
|
||||||
|
"skills": ["ACCUEIL", "TRANSPORT"],
|
||||||
|
"unavailableDates": ["2024-12-20", "2024-12-21"],
|
||||||
|
"undesiredDates": ["2024-12-22", "2024-12-23"],
|
||||||
|
"desiredDates": ["2024-12-25", "2024-12-26"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Marie Dubois",
|
||||||
|
"skills": ["INFIRMIER", "ACCUEIL"],
|
||||||
|
"unavailableDates": ["2024-12-27", "2024-12-28"],
|
||||||
|
"undesiredDates": ["2024-12-29", "2024-12-30"],
|
||||||
|
"desiredDates": ["2024-12-22", "2024-12-23"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Pierre Martin",
|
||||||
|
"skills": ["MEDECIN", "SUPERVISION"],
|
||||||
|
"unavailableDates": ["2024-12-20", "2024-12-21"],
|
||||||
|
"undesiredDates": ["2024-12-22", "2024-12-23"],
|
||||||
|
"desiredDates": ["2024-12-29", "2024-12-30"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Claire Dupuis",
|
||||||
|
"skills": ["INFIRMIER", "PRELEVEMENT"],
|
||||||
|
"unavailableDates": ["2024-12-25", "2024-12-26"],
|
||||||
|
"undesiredDates": ["2024-12-27", "2024-12-28"],
|
||||||
|
"desiredDates": ["2024-12-20", "2024-12-21"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Thomas Lambert",
|
||||||
|
"skills": ["TRANSPORT", "ACCUEIL"],
|
||||||
|
"unavailableDates": ["2024-12-30", "2024-12-31"],
|
||||||
|
"undesiredDates": ["2024-12-24", "2024-12-25"],
|
||||||
|
"desiredDates": ["2024-12-22", "2024-12-23"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Julie Lefèvre",
|
||||||
|
"skills": ["INFIRMIER", "MEDECIN"],
|
||||||
|
"unavailableDates": ["2024-12-22", "2024-12-23"],
|
||||||
|
"undesiredDates": ["2024-12-24", "2024-12-25"],
|
||||||
|
"desiredDates": ["2024-12-27", "2024-12-28"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Nicolas Bernard",
|
||||||
|
"skills": ["PRELEVEMENT", "SUPERVISION"],
|
||||||
|
"unavailableDates": ["2024-12-29", "2024-12-30"],
|
||||||
|
"undesiredDates": ["2024-12-20", "2024-12-21"],
|
||||||
|
"desiredDates": ["2024-12-25", "2024-12-26"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Isabelle Moreau",
|
||||||
|
"skills": ["ACCUEIL", "INFIRMIER"],
|
||||||
|
"unavailableDates": ["2024-12-24", "2024-12-25"],
|
||||||
|
"undesiredDates": ["2024-12-26", "2024-12-27"],
|
||||||
|
"desiredDates": ["2024-12-22", "2024-12-23"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "François Dubois",
|
||||||
|
"skills": ["MEDECIN", "TRANSPORT"],
|
||||||
|
"unavailableDates": ["2024-12-27", "2024-12-28"],
|
||||||
|
"undesiredDates": ["2024-12-29", "2024-12-30"],
|
||||||
|
"desiredDates": ["2024-12-20", "2024-12-21"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Élodie Martin",
|
||||||
|
"skills": ["INFIRMIER", "ACCUEIL"],
|
||||||
|
"unavailableDates": ["2024-12-31"],
|
||||||
|
"undesiredDates": ["2024-12-25", "2024-12-26"],
|
||||||
|
"desiredDates": ["2024-12-27", "2024-12-28"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Guillaume Lefèvre",
|
||||||
|
"skills": ["PRELEVEMENT", "TRANSPORT"],
|
||||||
|
"unavailableDates": ["2024-12-20", "2024-12-21"],
|
||||||
|
"undesiredDates": ["2024-12-22", "2024-12-23"],
|
||||||
|
"desiredDates": ["2024-12-29", "2024-12-30"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Caroline Lambert",
|
||||||
|
"skills": ["MEDECIN", "SUPERVISION"],
|
||||||
|
"unavailableDates": ["2024-12-25", "2024-12-26"],
|
||||||
|
"undesiredDates": ["2024-12-27", "2024-12-28"],
|
||||||
|
"desiredDates": ["2024-12-20", "2024-12-21"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Olivier Bernard",
|
||||||
|
"skills": ["INFIRMIER", "PRELEVEMENT"],
|
||||||
|
"unavailableDates": ["2024-12-22", "2024-12-23"],
|
||||||
|
"undesiredDates": ["2024-12-24", "2024-12-25"],
|
||||||
|
"desiredDates": ["2024-12-29", "2024-12-30"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Sandrine Moreau",
|
||||||
|
"skills": ["ACCUEIL", "TRANSPORT"],
|
||||||
|
"unavailableDates": ["2024-12-27", "2024-12-28"],
|
||||||
|
"undesiredDates": ["2024-12-29", "2024-12-30"],
|
||||||
|
"desiredDates": ["2024-12-20", "2024-12-21"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "David Lefèvre",
|
||||||
|
"skills": ["MEDECIN", "INFIRMIER"],
|
||||||
|
"unavailableDates": ["2024-12-30", "2024-12-31"],
|
||||||
|
"undesiredDates": ["2024-12-24", "2024-12-25"],
|
||||||
|
"desiredDates": ["2024-12-22", "2024-12-23"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Céline Martin",
|
||||||
|
"skills": ["SUPERVISION", "ACCUEIL"],
|
||||||
|
"unavailableDates": ["2024-12-24", "2024-12-25"],
|
||||||
|
"undesiredDates": ["2024-12-26", "2024-12-27"],
|
||||||
|
"desiredDates": ["2024-12-29", "2024-12-30"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"collectes": [
|
||||||
|
{
|
||||||
|
"id": "collecte_toulouse_20241220",
|
||||||
|
"start": "2024-12-20T08:00:00",
|
||||||
|
"end": "2024-12-20T16:00:00",
|
||||||
|
"location": "Centre de collecte - Toulouse",
|
||||||
|
"requiredSkills": {
|
||||||
|
"INFIRMIER": 3,
|
||||||
|
"MEDECIN": 1,
|
||||||
|
"ACCUEIL": 1,
|
||||||
|
"TRANSPORT": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collecte_blagnac_20241220",
|
||||||
|
"start": "2024-12-20T08:00:00",
|
||||||
|
"end": "2024-12-20T16:00:00",
|
||||||
|
"location": "Centre de collecte - Blagnac",
|
||||||
|
"requiredSkills": {
|
||||||
|
"INFIRMIER": 2,
|
||||||
|
"MEDECIN": 1,
|
||||||
|
"ACCUEIL": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collecte_purpan_20241221",
|
||||||
|
"start": "2024-12-21T08:00:00",
|
||||||
|
"end": "2024-12-21T16:00:00",
|
||||||
|
"location": "Hôpital Purpan - Toulouse",
|
||||||
|
"requiredSkills": {
|
||||||
|
"INFIRMIER": 3,
|
||||||
|
"MEDECIN": 1,
|
||||||
|
"ACCUEIL": 1,
|
||||||
|
"TRANSPORT": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collecte_rangueil_20241221",
|
||||||
|
"start": "2024-12-21T08:00:00",
|
||||||
|
"end": "2024-12-21T16:00:00",
|
||||||
|
"location": "Hôpital Rangueil - Toulouse",
|
||||||
|
"requiredSkills": {
|
||||||
|
"INFIRMIER": 2,
|
||||||
|
"MEDECIN": 1,
|
||||||
|
"PRELEVEMENT": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collecte_toulouse_20241222",
|
||||||
|
"start": "2024-12-22T08:00:00",
|
||||||
|
"end": "2024-12-22T16:00:00",
|
||||||
|
"location": "Centre de collecte - Toulouse",
|
||||||
|
"requiredSkills": {
|
||||||
|
"INFIRMIER": 3,
|
||||||
|
"MEDECIN": 1,
|
||||||
|
"ACCUEIL": 1,
|
||||||
|
"TRANSPORT": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collecte_blagnac_20241222",
|
||||||
|
"start": "2024-12-22T08:00:00",
|
||||||
|
"end": "2024-12-22T16:00:00",
|
||||||
|
"location": "Centre de collecte - Blagnac",
|
||||||
|
"requiredSkills": {
|
||||||
|
"INFIRMIER": 2,
|
||||||
|
"MEDECIN": 1,
|
||||||
|
"ACCUEIL": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collecte_purpan_20241223",
|
||||||
|
"start": "2024-12-23T08:00:00",
|
||||||
|
"end": "2024-12-23T16:00:00",
|
||||||
|
"location": "Hôpital Purpan - Toulouse",
|
||||||
|
"requiredSkills": {
|
||||||
|
"INFIRMIER": 3,
|
||||||
|
"MEDECIN": 1,
|
||||||
|
"ACCUEIL": 1,
|
||||||
|
"TRANSPORT": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collecte_rangueil_20241223",
|
||||||
|
"start": "2024-12-23T08:00:00",
|
||||||
|
"end": "2024-12-23T16:00:00",
|
||||||
|
"location": "Hôpital Rangueil - Toulouse",
|
||||||
|
"requiredSkills": {
|
||||||
|
"INFIRMIER": 2,
|
||||||
|
"MEDECIN": 1,
|
||||||
|
"PRELEVEMENT": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collecte_toulouse_20241224",
|
||||||
|
"start": "2024-12-24T08:00:00",
|
||||||
|
"end": "2024-12-24T16:00:00",
|
||||||
|
"location": "Centre de collecte - Toulouse",
|
||||||
|
"requiredSkills": {
|
||||||
|
"INFIRMIER": 3,
|
||||||
|
"MEDECIN": 1,
|
||||||
|
"ACCUEIL": 1,
|
||||||
|
"TRANSPORT": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "collecte_blagnac_20241224",
|
||||||
|
"start": "2024-12-24T08:00:00",
|
||||||
|
"end": "2024-12-24T16:00:00",
|
||||||
|
"location": "Centre de collecte - Blagnac",
|
||||||
|
"requiredSkills": {
|
||||||
|
"INFIRMIER": 2,
|
||||||
|
"MEDECIN": 1,
|
||||||
|
"ACCUEIL": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
75
claude.puml
Normal file
75
claude.puml
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
@startuml BloodCollectionSimple
|
||||||
|
|
||||||
|
!define PLANNING_ENTITY class
|
||||||
|
!define PLANNING_SOLUTION class
|
||||||
|
!define PROBLEM_FACT class
|
||||||
|
|
||||||
|
' Main solution class (même qu'avant)
|
||||||
|
PLANNING_SOLUTION BloodCollectionSchedule {
|
||||||
|
- List<Employee> employees
|
||||||
|
- List<BloodCollection> bloodCollections
|
||||||
|
- HardSoftBigDecimalScore score
|
||||||
|
- SolverStatus solverStatus
|
||||||
|
+ getAllShifts() : List<Shift>
|
||||||
|
}
|
||||||
|
|
||||||
|
' Employee (inchangé)
|
||||||
|
PROBLEM_FACT Employee {
|
||||||
|
@PlanningId
|
||||||
|
- String name
|
||||||
|
- Set<String> skills
|
||||||
|
- Set<LocalDate> unavailableDates
|
||||||
|
- Set<LocalDate> undesiredDates
|
||||||
|
- Set<LocalDate> desiredDates
|
||||||
|
}
|
||||||
|
|
||||||
|
' Collecte de sang
|
||||||
|
PROBLEM_FACT BloodCollection {
|
||||||
|
@PlanningId
|
||||||
|
- String id
|
||||||
|
- String name
|
||||||
|
- LocalDate date
|
||||||
|
- String location
|
||||||
|
- List<Shift> shifts
|
||||||
|
+ getShifts() : List<Shift>
|
||||||
|
+ isTeamComplete() : boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
' Shift (légèrement modifié)
|
||||||
|
PLANNING_ENTITY Shift {
|
||||||
|
@PlanningId
|
||||||
|
- String id
|
||||||
|
- LocalDateTime start
|
||||||
|
- LocalDateTime end
|
||||||
|
- String requiredSkill
|
||||||
|
- BloodCollection parentCollection
|
||||||
|
@PlanningVariable
|
||||||
|
- Employee employee
|
||||||
|
+ getParentCollection() : BloodCollection
|
||||||
|
}
|
||||||
|
|
||||||
|
' Relations
|
||||||
|
BloodCollectionSchedule ||--o{ Employee : contains
|
||||||
|
BloodCollectionSchedule ||--o{ BloodCollection : contains
|
||||||
|
|
||||||
|
BloodCollection ||--o{ Shift : contains
|
||||||
|
Shift }o--|| BloodCollection : belongs_to
|
||||||
|
Shift }o--o| Employee : assigned_to
|
||||||
|
|
||||||
|
note right of BloodCollection
|
||||||
|
Une collecte contient exactement:
|
||||||
|
- 2 shifts INFIRMIER
|
||||||
|
- 1 shift MEDECIN
|
||||||
|
- 1 shift CHAUFFEUR
|
||||||
|
- 1 shift ACCUEIL
|
||||||
|
|
||||||
|
Chacun avec ses propres horaires
|
||||||
|
end note
|
||||||
|
|
||||||
|
note left of Shift
|
||||||
|
Même concept qu'avant,
|
||||||
|
mais maintenant lié à
|
||||||
|
une collecte parente
|
||||||
|
end note
|
||||||
|
|
||||||
|
@enduml
|
||||||
64
gemini.puml
Normal file
64
gemini.puml
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
@startuml
|
||||||
|
!define DARK_BLUE #264653
|
||||||
|
!define ORANGE #F4A261
|
||||||
|
!define YELLOW #E9C46A
|
||||||
|
!define LIGHT_BLUE #2A9D8F
|
||||||
|
|
||||||
|
skinparam class {
|
||||||
|
BackgroundColor DARK_BLUE
|
||||||
|
ArrowColor DARK_BLUE
|
||||||
|
BorderColor DARK_BLUE
|
||||||
|
FontColor WHITE
|
||||||
|
}
|
||||||
|
|
||||||
|
skinparam arrow {
|
||||||
|
Color DARK_BLUE
|
||||||
|
}
|
||||||
|
|
||||||
|
' Définition des classes
|
||||||
|
class Employee {
|
||||||
|
+ name : String
|
||||||
|
+ skills : Set<Skill>
|
||||||
|
}
|
||||||
|
|
||||||
|
class BloodDrive {
|
||||||
|
+ id : UUID
|
||||||
|
+ location : String
|
||||||
|
+ date : LocalDate
|
||||||
|
--
|
||||||
|
+ <<transient>> shifts : List<Shift>
|
||||||
|
}
|
||||||
|
|
||||||
|
class Shift {
|
||||||
|
+ id : UUID
|
||||||
|
+ start : LocalDateTime
|
||||||
|
+ end : LocalDateTime
|
||||||
|
+ requiredSkill : Skill
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Skill {
|
||||||
|
+ NURSE
|
||||||
|
+ DOCTOR
|
||||||
|
+ DRIVER
|
||||||
|
+ RECEPTIONIST
|
||||||
|
}
|
||||||
|
|
||||||
|
' Définition des relations
|
||||||
|
BloodDrive "1" --> "1..*" Shift : <<compose>>
|
||||||
|
Shift "1" --> "1" Employee : <<assigned to>>
|
||||||
|
Shift "1" --> "1" Skill : <<requires>>
|
||||||
|
Employee "1" -- "0..*" Shift : <<is assigned to>>
|
||||||
|
Employee "1" -- "0..*" Skill : <<has>>
|
||||||
|
|
||||||
|
' Notes explicatives
|
||||||
|
note top of BloodDrive
|
||||||
|
Nouvelle entité pour regrouper
|
||||||
|
tous les shifts d'une même collecte.
|
||||||
|
end note
|
||||||
|
|
||||||
|
note top of Shift
|
||||||
|
Chaque rôle (infirmier, médecin)
|
||||||
|
est un shift distinct avec ses propres horaires.
|
||||||
|
end note
|
||||||
|
|
||||||
|
@enduml
|
||||||
@ -0,0 +1,69 @@
|
|||||||
|
package org.acme.employeescheduling.domain;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
public class Collecte {
|
||||||
|
private String id;
|
||||||
|
private LocalDateTime start;
|
||||||
|
private LocalDateTime end;
|
||||||
|
private String location;
|
||||||
|
private Map<String, Integer> requiredSkills;
|
||||||
|
private List<Shift> shifts;
|
||||||
|
|
||||||
|
public Collecte() {
|
||||||
|
this.requiredSkills = new HashMap<>();
|
||||||
|
this.shifts = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collecte(String id, LocalDateTime start, LocalDateTime end, String location, Map<String, Integer> requiredSkills) {
|
||||||
|
this.id = id;
|
||||||
|
this.start = start;
|
||||||
|
this.end = end;
|
||||||
|
this.location = location;
|
||||||
|
this.requiredSkills = requiredSkills != null ? requiredSkills : new HashMap<>();
|
||||||
|
this.shifts = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void generateShifts() {
|
||||||
|
if (this.requiredSkills == null) {
|
||||||
|
this.requiredSkills = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.shifts = this.requiredSkills.entrySet().stream()
|
||||||
|
.flatMap(entry -> {
|
||||||
|
String skill = entry.getKey();
|
||||||
|
int quantity = entry.getValue();
|
||||||
|
return IntStream.range(0, quantity)
|
||||||
|
.mapToObj(i -> new Shift(
|
||||||
|
this.id + "_" + skill + "_" + i,
|
||||||
|
this.start,
|
||||||
|
this.end,
|
||||||
|
this.location,
|
||||||
|
skill,
|
||||||
|
null, // employee
|
||||||
|
this // collecte
|
||||||
|
));
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters et setters
|
||||||
|
public String getId() { return id; }
|
||||||
|
public void setId(String id) { this.id = id; }
|
||||||
|
public LocalDateTime getStart() { return start; }
|
||||||
|
public void setStart(LocalDateTime start) { this.start = start; }
|
||||||
|
public LocalDateTime getEnd() { return end; }
|
||||||
|
public void setEnd(LocalDateTime end) { this.end = end; }
|
||||||
|
public String getLocation() { return location; }
|
||||||
|
public void setLocation(String location) { this.location = location; }
|
||||||
|
public Map<String, Integer> getRequiredSkills() { return requiredSkills; }
|
||||||
|
public void setRequiredSkills(Map<String, Integer> requiredSkills) { this.requiredSkills = requiredSkills; }
|
||||||
|
public List<Shift> getShifts() { return shifts; }
|
||||||
|
public void setShifts(List<Shift> shifts) { this.shifts = shifts; }
|
||||||
|
}
|
||||||
@ -1,7 +1,8 @@
|
|||||||
package org.acme.employeescheduling.domain;
|
package org.acme.employeescheduling.domain;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty;
|
import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty;
|
||||||
import ai.timefold.solver.core.api.domain.solution.PlanningScore;
|
import ai.timefold.solver.core.api.domain.solution.PlanningScore;
|
||||||
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
|
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
|
||||||
@ -12,61 +13,50 @@ import ai.timefold.solver.core.api.solver.SolverStatus;
|
|||||||
|
|
||||||
@PlanningSolution
|
@PlanningSolution
|
||||||
public class EmployeeSchedule {
|
public class EmployeeSchedule {
|
||||||
|
@ProblemFactCollectionProperty
|
||||||
|
@ValueRangeProvider(id = "employeeRange")
|
||||||
|
private List<Employee> employees;
|
||||||
|
|
||||||
@ProblemFactCollectionProperty
|
@ProblemFactCollectionProperty
|
||||||
@ValueRangeProvider
|
private List<Collecte> collectes;
|
||||||
private List<Employee> employees;
|
|
||||||
|
|
||||||
@PlanningEntityCollectionProperty
|
@PlanningEntityCollectionProperty
|
||||||
private List<Shift> shifts;
|
private List<Shift> shifts;
|
||||||
|
|
||||||
@PlanningScore
|
@PlanningScore
|
||||||
private HardSoftBigDecimalScore score;
|
private HardSoftBigDecimalScore score;
|
||||||
|
|
||||||
private SolverStatus solverStatus;
|
private SolverStatus solverStatus;
|
||||||
|
|
||||||
// No-arg constructor required for Timefold
|
// Constructeur vide requis par Timefold
|
||||||
public EmployeeSchedule() {}
|
public EmployeeSchedule() {
|
||||||
|
this.employees = new ArrayList<>();
|
||||||
public EmployeeSchedule(List<Employee> employees, List<Shift> shifts) {
|
this.collectes = new ArrayList<>();
|
||||||
this.employees = employees;
|
this.shifts = new ArrayList<>();
|
||||||
this.shifts = shifts;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public EmployeeSchedule(HardSoftBigDecimalScore score, SolverStatus solverStatus) {
|
// Constructeur principal
|
||||||
this.score = score;
|
public EmployeeSchedule(List<Employee> employees, List<Collecte> collectes) {
|
||||||
this.solverStatus = solverStatus;
|
this.employees = employees != null ? employees : new ArrayList<>();
|
||||||
|
this.collectes = collectes != null ? collectes : new ArrayList<>();
|
||||||
|
|
||||||
|
// Générer les shifts à partir des collectes
|
||||||
|
this.shifts = collectes != null ?
|
||||||
|
collectes.stream()
|
||||||
|
.peek(Collecte::generateShifts)
|
||||||
|
.flatMap(collecte -> collecte.getShifts().stream())
|
||||||
|
.collect(Collectors.toList()) :
|
||||||
|
new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Employee> getEmployees() {
|
// Getters et setters
|
||||||
return employees;
|
public List<Employee> getEmployees() { return employees; }
|
||||||
}
|
public void setEmployees(List<Employee> employees) { this.employees = employees; }
|
||||||
|
public List<Collecte> getCollectes() { return collectes; }
|
||||||
public void setEmployees(List<Employee> employees) {
|
public void setCollectes(List<Collecte> collectes) { this.collectes = collectes; }
|
||||||
this.employees = employees;
|
public List<Shift> getShifts() { return shifts; }
|
||||||
}
|
public void setShifts(List<Shift> shifts) { this.shifts = shifts; }
|
||||||
|
public HardSoftBigDecimalScore getScore() { return score; }
|
||||||
public List<Shift> getShifts() {
|
public void setScore(HardSoftBigDecimalScore score) { this.score = score; }
|
||||||
return shifts;
|
public SolverStatus getSolverStatus() { return solverStatus; }
|
||||||
}
|
public void setSolverStatus(SolverStatus solverStatus) { this.solverStatus = solverStatus; }
|
||||||
|
|
||||||
public void setShifts(List<Shift> shifts) {
|
|
||||||
this.shifts = shifts;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HardSoftBigDecimalScore getScore() {
|
|
||||||
return score;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setScore(HardSoftBigDecimalScore score) {
|
|
||||||
this.score = score;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SolverStatus getSolverStatus() {
|
|
||||||
return solverStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSolverStatus(SolverStatus solverStatus) {
|
|
||||||
this.solverStatus = solverStatus;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,28 +20,30 @@ public class Shift {
|
|||||||
|
|
||||||
private String location;
|
private String location;
|
||||||
private String requiredSkill;
|
private String requiredSkill;
|
||||||
|
private Collecte collecte;
|
||||||
|
|
||||||
@PlanningVariable
|
@PlanningVariable(valueRangeProviderRefs = "employeeRange")
|
||||||
private Employee employee;
|
private Employee employee;
|
||||||
|
|
||||||
public Shift() {
|
public Shift() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Shift(LocalDateTime start, LocalDateTime end, String location, String requiredSkill) {
|
public Shift(LocalDateTime start, LocalDateTime end, String location, String requiredSkill) {
|
||||||
this(start, end, location, requiredSkill, null);
|
this(null, start, end, location, requiredSkill, null, null);;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Shift(LocalDateTime start, LocalDateTime end, String location, String requiredSkill, Employee employee) {
|
public Shift(LocalDateTime start, LocalDateTime end, String location, String requiredSkill, Employee employee) {
|
||||||
this(null, start, end, location, requiredSkill, employee);
|
this(null, start, end, location, requiredSkill, employee, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Shift(String id, LocalDateTime start, LocalDateTime end, String location, String requiredSkill, Employee employee) {
|
public Shift(String id, LocalDateTime start, LocalDateTime end, String location, String requiredSkill, Employee employee, Collecte collecte) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.start = start;
|
this.start = start;
|
||||||
this.end = end;
|
this.end = end;
|
||||||
this.location = location;
|
this.location = location;
|
||||||
this.requiredSkill = requiredSkill;
|
this.requiredSkill = requiredSkill;
|
||||||
this.employee = employee;
|
this.employee = employee;
|
||||||
|
this.collecte = collecte;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
@ -88,6 +90,9 @@ public class Shift {
|
|||||||
return employee;
|
return employee;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Collecte getCollecte() { return collecte; }
|
||||||
|
public void setCollecte(Collecte collecte) { this.collecte = collecte; }
|
||||||
|
|
||||||
public void setEmployee(Employee employee) {
|
public void setEmployee(Employee employee) {
|
||||||
this.employee = employee;
|
this.employee = employee;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import java.util.Collection;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.ws.rs.Consumes;
|
import jakarta.ws.rs.Consumes;
|
||||||
import jakarta.ws.rs.DELETE;
|
import jakarta.ws.rs.DELETE;
|
||||||
@ -17,14 +16,12 @@ import jakarta.ws.rs.Produces;
|
|||||||
import jakarta.ws.rs.QueryParam;
|
import jakarta.ws.rs.QueryParam;
|
||||||
import jakarta.ws.rs.core.MediaType;
|
import jakarta.ws.rs.core.MediaType;
|
||||||
import jakarta.ws.rs.core.Response;
|
import jakarta.ws.rs.core.Response;
|
||||||
|
|
||||||
import ai.timefold.solver.core.api.score.analysis.ScoreAnalysis;
|
import ai.timefold.solver.core.api.score.analysis.ScoreAnalysis;
|
||||||
import ai.timefold.solver.core.api.score.buildin.hardsoftbigdecimal.HardSoftBigDecimalScore;
|
import ai.timefold.solver.core.api.score.buildin.hardsoftbigdecimal.HardSoftBigDecimalScore;
|
||||||
import ai.timefold.solver.core.api.solver.ScoreAnalysisFetchPolicy;
|
import ai.timefold.solver.core.api.solver.ScoreAnalysisFetchPolicy;
|
||||||
import ai.timefold.solver.core.api.solver.SolutionManager;
|
import ai.timefold.solver.core.api.solver.SolutionManager;
|
||||||
import ai.timefold.solver.core.api.solver.SolverManager;
|
import ai.timefold.solver.core.api.solver.SolverManager;
|
||||||
import ai.timefold.solver.core.api.solver.SolverStatus;
|
import ai.timefold.solver.core.api.solver.SolverStatus;
|
||||||
|
|
||||||
import org.acme.employeescheduling.domain.EmployeeSchedule;
|
import org.acme.employeescheduling.domain.EmployeeSchedule;
|
||||||
import org.acme.employeescheduling.rest.exception.EmployeeScheduleSolverException;
|
import org.acme.employeescheduling.rest.exception.EmployeeScheduleSolverException;
|
||||||
import org.acme.employeescheduling.rest.exception.ErrorInfo;
|
import org.acme.employeescheduling.rest.exception.ErrorInfo;
|
||||||
@ -42,13 +39,9 @@ import org.slf4j.LoggerFactory;
|
|||||||
@Tag(name = "Employee Schedules", description = "Employee Schedules service for assigning employees to shifts.")
|
@Tag(name = "Employee Schedules", description = "Employee Schedules service for assigning employees to shifts.")
|
||||||
@Path("schedules")
|
@Path("schedules")
|
||||||
public class EmployeeScheduleResource {
|
public class EmployeeScheduleResource {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(EmployeeScheduleResource.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(EmployeeScheduleResource.class);
|
||||||
|
|
||||||
SolverManager<EmployeeSchedule, String> solverManager;
|
SolverManager<EmployeeSchedule, String> solverManager;
|
||||||
SolutionManager<EmployeeSchedule, HardSoftBigDecimalScore> solutionManager;
|
SolutionManager<EmployeeSchedule, HardSoftBigDecimalScore> solutionManager;
|
||||||
|
|
||||||
// TODO: Without any "time to live", the map may eventually grow out of memory.
|
|
||||||
private final ConcurrentMap<String, Job> jobIdToJob = new ConcurrentHashMap<>();
|
private final ConcurrentMap<String, Job> jobIdToJob = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@ -77,20 +70,36 @@ public class EmployeeScheduleResource {
|
|||||||
@POST
|
@POST
|
||||||
@Consumes({ MediaType.APPLICATION_JSON })
|
@Consumes({ MediaType.APPLICATION_JSON })
|
||||||
@Produces(MediaType.TEXT_PLAIN)
|
@Produces(MediaType.TEXT_PLAIN)
|
||||||
public String solve(EmployeeSchedule problem) {
|
public String solve(EmployeeSchedule problem) {
|
||||||
String jobId = UUID.randomUUID().toString();
|
String jobId = UUID.randomUUID().toString();
|
||||||
|
LOGGER.info("Submitting problem with jobId: {}", jobId);
|
||||||
|
LOGGER.info("Problem details - Employees: {}, Collectes: {}",
|
||||||
|
problem.getEmployees().size(),
|
||||||
|
problem.getCollectes().size());
|
||||||
|
|
||||||
jobIdToJob.put(jobId, Job.ofSchedule(problem));
|
jobIdToJob.put(jobId, Job.ofSchedule(problem));
|
||||||
|
|
||||||
|
LOGGER.info("Starting solver for jobId: {}", jobId);
|
||||||
solverManager.solveBuilder()
|
solverManager.solveBuilder()
|
||||||
.withProblemId(jobId)
|
.withProblemId(jobId)
|
||||||
.withProblemFinder(jobId_ -> jobIdToJob.get(jobId).schedule)
|
.withProblemFinder(jobId_ -> {
|
||||||
.withBestSolutionConsumer(solution -> jobIdToJob.put(jobId, Job.ofSchedule(solution)))
|
Job job = jobIdToJob.get(jobId);
|
||||||
|
LOGGER.debug("Problem finder called for jobId: {}, problem: {}", jobId, job != null ? job.schedule : null);
|
||||||
|
return job != null ? job.schedule : null;
|
||||||
|
})
|
||||||
|
.withBestSolutionConsumer(solution -> {
|
||||||
|
LOGGER.info("New best solution for jobId: {}", jobId);
|
||||||
|
jobIdToJob.put(jobId, Job.ofSchedule(solution));
|
||||||
|
})
|
||||||
.withExceptionHandler((jobId_, exception) -> {
|
.withExceptionHandler((jobId_, exception) -> {
|
||||||
|
LOGGER.error("Exception during solving jobId {}: {}", jobId, exception.getMessage(), exception);
|
||||||
jobIdToJob.put(jobId, Job.ofException(exception));
|
jobIdToJob.put(jobId, Job.ofException(exception));
|
||||||
LOGGER.error("Failed solving jobId ({}).", jobId, exception);
|
|
||||||
})
|
})
|
||||||
.run();
|
.run();
|
||||||
|
|
||||||
|
LOGGER.info("Solver started for jobId: {}", jobId);
|
||||||
return jobId;
|
return jobId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "Submit a schedule to analyze its score.")
|
@Operation(summary = "Submit a schedule to analyze its score.")
|
||||||
@APIResponses(value = {
|
@APIResponses(value = {
|
||||||
@ -131,17 +140,6 @@ public class EmployeeScheduleResource {
|
|||||||
return schedule;
|
return schedule;
|
||||||
}
|
}
|
||||||
|
|
||||||
private EmployeeSchedule getEmployeeScheduleAndCheckForExceptions(String jobId) {
|
|
||||||
Job job = jobIdToJob.get(jobId);
|
|
||||||
if (job == null) {
|
|
||||||
throw new EmployeeScheduleSolverException(jobId, Response.Status.NOT_FOUND, "No schedule found.");
|
|
||||||
}
|
|
||||||
if (job.exception != null) {
|
|
||||||
throw new EmployeeScheduleSolverException(jobId, job.exception);
|
|
||||||
}
|
|
||||||
return job.schedule;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Terminate solving for a given job ID. Returns the best solution of the schedule so far, as it might still be running or not even started.")
|
summary = "Terminate solving for a given job ID. Returns the best solution of the schedule so far, as it might still be running or not even started.")
|
||||||
@APIResponses(value = {
|
@APIResponses(value = {
|
||||||
@ -160,7 +158,6 @@ public class EmployeeScheduleResource {
|
|||||||
@Path("{jobId}")
|
@Path("{jobId}")
|
||||||
public EmployeeSchedule terminateSolving(
|
public EmployeeSchedule terminateSolving(
|
||||||
@Parameter(description = "The job ID returned by the POST method.") @PathParam("jobId") String jobId) {
|
@Parameter(description = "The job ID returned by the POST method.") @PathParam("jobId") String jobId) {
|
||||||
// TODO: Replace with .terminateEarlyAndWait(... [, timeout]); see https://github.com/TimefoldAI/timefold-solver/issues/77
|
|
||||||
solverManager.terminateEarly(jobId);
|
solverManager.terminateEarly(jobId);
|
||||||
return getEmployeeSchedule(jobId);
|
return getEmployeeSchedule(jobId);
|
||||||
}
|
}
|
||||||
@ -170,7 +167,7 @@ public class EmployeeScheduleResource {
|
|||||||
@APIResponses(value = {
|
@APIResponses(value = {
|
||||||
@APIResponse(responseCode = "200", description = "The schedule status and the best score so far.",
|
@APIResponse(responseCode = "200", description = "The schedule status and the best score so far.",
|
||||||
content = @Content(mediaType = MediaType.APPLICATION_JSON,
|
content = @Content(mediaType = MediaType.APPLICATION_JSON,
|
||||||
schema = @Schema(implementation = EmployeeSchedule.class))),
|
schema = @Schema(implementation = ScheduleStatus.class))),
|
||||||
@APIResponse(responseCode = "404", description = "No schedule found.",
|
@APIResponse(responseCode = "404", description = "No schedule found.",
|
||||||
content = @Content(mediaType = MediaType.APPLICATION_JSON,
|
content = @Content(mediaType = MediaType.APPLICATION_JSON,
|
||||||
schema = @Schema(implementation = ErrorInfo.class))),
|
schema = @Schema(implementation = ErrorInfo.class))),
|
||||||
@ -181,21 +178,50 @@ public class EmployeeScheduleResource {
|
|||||||
@GET
|
@GET
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
@Path("{jobId}/status")
|
@Path("{jobId}/status")
|
||||||
public EmployeeSchedule getStatus(
|
public Response getStatus(
|
||||||
@Parameter(description = "The job ID returned by the POST method.") @PathParam("jobId") String jobId) {
|
@Parameter(description = "The job ID returned by the POST method.") @PathParam("jobId") String jobId) {
|
||||||
EmployeeSchedule schedule = getEmployeeScheduleAndCheckForExceptions(jobId);
|
EmployeeSchedule schedule = getEmployeeScheduleAndCheckForExceptions(jobId);
|
||||||
SolverStatus solverStatus = solverManager.getSolverStatus(jobId);
|
SolverStatus solverStatus = solverManager.getSolverStatus(jobId);
|
||||||
return new EmployeeSchedule(schedule.getScore(), solverStatus);
|
|
||||||
|
return Response.ok(new ScheduleStatus(schedule.getScore(), solverStatus)).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private EmployeeSchedule getEmployeeScheduleAndCheckForExceptions(String jobId) {
|
||||||
|
Job job = jobIdToJob.get(jobId);
|
||||||
|
if (job == null) {
|
||||||
|
throw new EmployeeScheduleSolverException(jobId, Response.Status.NOT_FOUND, "No schedule found.");
|
||||||
|
}
|
||||||
|
if (job.exception != null) {
|
||||||
|
throw new EmployeeScheduleSolverException(jobId, job.exception);
|
||||||
|
}
|
||||||
|
return job.schedule;
|
||||||
}
|
}
|
||||||
|
|
||||||
private record Job(EmployeeSchedule schedule, Throwable exception) {
|
private record Job(EmployeeSchedule schedule, Throwable exception) {
|
||||||
|
|
||||||
static Job ofSchedule(EmployeeSchedule schedule) {
|
static Job ofSchedule(EmployeeSchedule schedule) {
|
||||||
return new Job(schedule, null);
|
return new Job(schedule, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Job ofException(Throwable error) {
|
static Job ofException(Throwable error) {
|
||||||
return new Job(null, error);
|
return new Job(null, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class ScheduleStatus {
|
||||||
|
private HardSoftBigDecimalScore score;
|
||||||
|
private SolverStatus solverStatus;
|
||||||
|
|
||||||
|
public ScheduleStatus(HardSoftBigDecimalScore score, SolverStatus solverStatus) {
|
||||||
|
this.score = score;
|
||||||
|
this.solverStatus = solverStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
public HardSoftBigDecimalScore getScore() {
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SolverStatus getSolverStatus() {
|
||||||
|
return solverStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,30 @@
|
|||||||
|
package org.acme.employeescheduling.solver;
|
||||||
|
|
||||||
|
import ai.timefold.solver.core.api.score.buildin.hardsoftbigdecimal.HardSoftBigDecimalScore;
|
||||||
|
import ai.timefold.solver.core.api.score.stream.Constraint;
|
||||||
|
import ai.timefold.solver.core.api.score.stream.ConstraintFactory;
|
||||||
|
import ai.timefold.solver.core.api.score.stream.ConstraintProvider;
|
||||||
|
import ai.timefold.solver.core.api.score.stream.ConstraintCollectors;
|
||||||
|
// import org.acme.employeescheduling.domain.Collecte;
|
||||||
|
import org.acme.employeescheduling.domain.Shift;
|
||||||
|
|
||||||
|
public class CollecteConstraintProvider implements ConstraintProvider {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
|
||||||
|
return new Constraint[] {
|
||||||
|
requiredSkills(constraintFactory)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Constraint requiredSkills(ConstraintFactory constraintFactory) {
|
||||||
|
return constraintFactory.forEach(Shift.class)
|
||||||
|
.groupBy(Shift::getCollecte, Shift::getRequiredSkill, ConstraintCollectors.count())
|
||||||
|
.penalize(HardSoftBigDecimalScore.ONE_HARD,
|
||||||
|
(collecte, skill, count) -> {
|
||||||
|
int requiredQuantity = collecte.getRequiredSkills().getOrDefault(skill, 0);
|
||||||
|
return Math.max(0, requiredQuantity - count);
|
||||||
|
})
|
||||||
|
.asConstraint("Insufficient employees with required skill");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -48,18 +48,19 @@ public class EmployeeSchedulingConstraintProvider implements ConstraintProvider
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Constraint requiredSkill(ConstraintFactory constraintFactory) {
|
private Constraint requiredSkill(ConstraintFactory constraintFactory) {
|
||||||
return constraintFactory.forEach(Shift.class)
|
return constraintFactory.forEach(Shift.class)
|
||||||
.filter(shift -> !shift.getEmployee().getSkills().contains(shift.getRequiredSkill()))
|
.filter(shift -> shift.getEmployee() != null &&
|
||||||
|
!shift.getEmployee().getSkills().contains(shift.getRequiredSkill()))
|
||||||
.penalize(HardSoftBigDecimalScore.ONE_HARD)
|
.penalize(HardSoftBigDecimalScore.ONE_HARD)
|
||||||
.asConstraint("Missing required skill");
|
.asConstraint("Missing required skill");
|
||||||
}
|
}
|
||||||
|
|
||||||
Constraint noOverlappingShifts(ConstraintFactory constraintFactory) {
|
private Constraint noOverlappingShifts(ConstraintFactory constraintFactory) {
|
||||||
return constraintFactory.forEachUniquePair(Shift.class, equal(Shift::getEmployee),
|
return constraintFactory.forEachUniquePair(Shift.class,
|
||||||
overlapping(Shift::getStart, Shift::getEnd))
|
equal(Shift::getEmployee),
|
||||||
.penalize(HardSoftBigDecimalScore.ONE_HARD,
|
overlapping(shift -> shift.getStart(), shift -> shift.getEnd()))
|
||||||
EmployeeSchedulingConstraintProvider::getMinuteOverlap)
|
.penalize(HardSoftBigDecimalScore.ONE_HARD)
|
||||||
.asConstraint("Overlapping shift");
|
.asConstraint("Overlapping shift");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -41,3 +41,6 @@ quarkus.swagger-ui.always-include=true
|
|||||||
########################
|
########################
|
||||||
|
|
||||||
%test.quarkus.timefold.solver.termination.spent-limit=10s
|
%test.quarkus.timefold.solver.termination.spent-limit=10s
|
||||||
|
|
||||||
|
quarkus.log.category."ai.timefold.solver".level=DEBUG
|
||||||
|
quarkus.log.category."org.acme.employeescheduling".level=DEBUG
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -41,3 +41,6 @@ quarkus.swagger-ui.always-include=true
|
|||||||
########################
|
########################
|
||||||
|
|
||||||
%test.quarkus.timefold.solver.termination.spent-limit=10s
|
%test.quarkus.timefold.solver.termination.spent-limit=10s
|
||||||
|
|
||||||
|
quarkus.log.category."ai.timefold.solver".level=DEBUG
|
||||||
|
quarkus.log.category."org.acme.employeescheduling".level=DEBUG
|
||||||
|
|||||||
BIN
target/classes/org/acme/employeescheduling/domain/Collecte.class
Normal file
BIN
target/classes/org/acme/employeescheduling/domain/Collecte.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,14 +0,0 @@
|
|||||||
org/acme/employeescheduling/domain/EmployeeSchedule.class
|
|
||||||
org/acme/employeescheduling/rest/DemoDataGenerator$CountDistribution.class
|
|
||||||
org/acme/employeescheduling/rest/DemoDataGenerator$DemoDataParameters.class
|
|
||||||
org/acme/employeescheduling/rest/exception/EmployeeScheduleSolverException.class
|
|
||||||
org/acme/employeescheduling/rest/EmployeeScheduleDemoResource.class
|
|
||||||
org/acme/employeescheduling/solver/EmployeeSchedulingConstraintProvider.class
|
|
||||||
org/acme/employeescheduling/rest/exception/EmployeeScheduleSolverExceptionMapper.class
|
|
||||||
org/acme/employeescheduling/rest/EmployeeScheduleResource$Job.class
|
|
||||||
org/acme/employeescheduling/rest/EmployeeScheduleResource.class
|
|
||||||
org/acme/employeescheduling/rest/DemoDataGenerator.class
|
|
||||||
org/acme/employeescheduling/domain/Shift.class
|
|
||||||
org/acme/employeescheduling/domain/Employee.class
|
|
||||||
org/acme/employeescheduling/rest/exception/ErrorInfo.class
|
|
||||||
org/acme/employeescheduling/rest/DemoDataGenerator$DemoData.class
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
/home/virt/timefold-quickstarts/java/employee-scheduling/src/main/java/org/acme/employeescheduling/domain/Employee.java
|
|
||||||
/home/virt/timefold-quickstarts/java/employee-scheduling/src/main/java/org/acme/employeescheduling/domain/EmployeeSchedule.java
|
|
||||||
/home/virt/timefold-quickstarts/java/employee-scheduling/src/main/java/org/acme/employeescheduling/domain/Shift.java
|
|
||||||
/home/virt/timefold-quickstarts/java/employee-scheduling/src/main/java/org/acme/employeescheduling/rest/DemoDataGenerator.java
|
|
||||||
/home/virt/timefold-quickstarts/java/employee-scheduling/src/main/java/org/acme/employeescheduling/rest/EmployeeScheduleDemoResource.java
|
|
||||||
/home/virt/timefold-quickstarts/java/employee-scheduling/src/main/java/org/acme/employeescheduling/rest/EmployeeScheduleResource.java
|
|
||||||
/home/virt/timefold-quickstarts/java/employee-scheduling/src/main/java/org/acme/employeescheduling/rest/exception/EmployeeScheduleSolverException.java
|
|
||||||
/home/virt/timefold-quickstarts/java/employee-scheduling/src/main/java/org/acme/employeescheduling/rest/exception/EmployeeScheduleSolverExceptionMapper.java
|
|
||||||
/home/virt/timefold-quickstarts/java/employee-scheduling/src/main/java/org/acme/employeescheduling/rest/exception/ErrorInfo.java
|
|
||||||
/home/virt/timefold-quickstarts/java/employee-scheduling/src/main/java/org/acme/employeescheduling/solver/EmployeeSchedulingConstraintProvider.java
|
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
org/acme/employeescheduling/domain/EmployeeSchedule.class
|
||||||
|
org/acme/employeescheduling/rest/DemoDataGenerator$CountDistribution.class
|
||||||
|
org/acme/employeescheduling/rest/DemoDataGenerator$DemoDataParameters.class
|
||||||
|
org/acme/employeescheduling/rest/exception/EmployeeScheduleSolverException.class
|
||||||
|
org/acme/employeescheduling/domain/Collecte.class
|
||||||
|
org/acme/employeescheduling/rest/EmployeeScheduleDemoResource.class
|
||||||
|
org/acme/employeescheduling/solver/EmployeeSchedulingConstraintProvider.class
|
||||||
|
org/acme/employeescheduling/rest/exception/EmployeeScheduleSolverExceptionMapper.class
|
||||||
|
org/acme/employeescheduling/rest/EmployeeScheduleResource$Job.class
|
||||||
|
org/acme/employeescheduling/rest/EmployeeScheduleResource.class
|
||||||
|
org/acme/employeescheduling/rest/DemoDataGenerator.class
|
||||||
|
org/acme/employeescheduling/domain/Shift.class
|
||||||
|
org/acme/employeescheduling/domain/Employee.class
|
||||||
|
org/acme/employeescheduling/rest/EmployeeScheduleResource$ScheduleStatus.class
|
||||||
|
org/acme/employeescheduling/rest/exception/ErrorInfo.class
|
||||||
|
org/acme/employeescheduling/rest/DemoDataGenerator$DemoData.class
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
/home/virt/timefold-quickstarts/java/collect-sang/src/main/java/org/acme/employeescheduling/domain/Collecte.java
|
||||||
/home/virt/timefold-quickstarts/java/collect-sang/src/main/java/org/acme/employeescheduling/domain/Employee.java
|
/home/virt/timefold-quickstarts/java/collect-sang/src/main/java/org/acme/employeescheduling/domain/Employee.java
|
||||||
/home/virt/timefold-quickstarts/java/collect-sang/src/main/java/org/acme/employeescheduling/domain/EmployeeSchedule.java
|
/home/virt/timefold-quickstarts/java/collect-sang/src/main/java/org/acme/employeescheduling/domain/EmployeeSchedule.java
|
||||||
/home/virt/timefold-quickstarts/java/collect-sang/src/main/java/org/acme/employeescheduling/domain/Shift.java
|
/home/virt/timefold-quickstarts/java/collect-sang/src/main/java/org/acme/employeescheduling/domain/Shift.java
|
||||||
|
|||||||
@ -1,4 +0,0 @@
|
|||||||
org/acme/employeescheduling/solver/EmployeeSchedulingConstraintProviderTest.class
|
|
||||||
org/acme/employeescheduling/rest/EmployeeScheduleResourceTest.class
|
|
||||||
org/acme/employeescheduling/rest/EmployeeSchedulingEnvironmentTest.class
|
|
||||||
org/acme/employeescheduling/rest/EmployeeSchedulingResourceIT.class
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
/home/virt/timefold-quickstarts/java/employee-scheduling/src/test/java/org/acme/employeescheduling/rest/EmployeeScheduleResourceTest.java
|
|
||||||
/home/virt/timefold-quickstarts/java/employee-scheduling/src/test/java/org/acme/employeescheduling/rest/EmployeeSchedulingEnvironmentTest.java
|
|
||||||
/home/virt/timefold-quickstarts/java/employee-scheduling/src/test/java/org/acme/employeescheduling/rest/EmployeeSchedulingResourceIT.java
|
|
||||||
/home/virt/timefold-quickstarts/java/employee-scheduling/src/test/java/org/acme/employeescheduling/solver/EmployeeSchedulingConstraintProviderTest.java
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user