Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,12 @@
['name' => 'timetable#upsert', 'url' => '/api/timetable/sessions/upsert', 'verb' => 'POST'],
// Admins publish a source's draft lessons in a window (timetable-draft-review).
['name' => 'timetable#publish', 'url' => '/api/timetable/sessions/publish', 'verb' => 'POST'],
// Admins upload the activities and rooms sheets for the timetable generator (timetabling-generator 2.2).
['name' => 'timetableInput#upload', 'url' => '/api/timetable/input/upload', 'verb' => 'POST'],
// Admins start a generator run for a timetable scenario (timetabling-generator 5.2).
['name' => 'timetableScenario#generate', 'url' => '/api/timetable/scenarios/{id}/generate', 'verb' => 'POST'],
// Admins take the current timetable into an imported scenario (timetabling-generator 6.1).
['name' => 'timetableScenario#importCurrent', 'url' => '/api/timetable/scenarios/{id}/import', 'verb' => 'POST'],
// Admins publish a scenario as draft lessons for its window (timetabling-generator 8.1).
['name' => 'timetableScenario#publishDrafts', 'url' => '/api/timetable/scenarios/{id}/publish-drafts', 'verb' => 'POST'],
]);
1 change: 1 addition & 0 deletions docs/features/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ Planninq is a project management app for Nextcloud, providing kanban boards, tas
| Procest Integration | Link tasks and projects to Procest cases via caseReference and zaakUuid fields; case badges and links in the UI | VNG ZGW InterneTaak, Schema.org Action | [procest-integration.md](procest-integration.md) |
| Finance import | Actual costs and commitments from the finance system land on the project with the same project number; unmatched lines wait in Beheer | OpenRegister financeLine, integriq mapping | [finance-import.md](finance-import.md) |
| School timetable | Lessons delivered by a rostering system (Zermelo, Untis, Xedule, TimeEdit) through integriq, upserted by source and lesson id, read by learniq per group or teacher | Schema.org Event, iCalendar VEVENT (RFC 5545) | [school-timetable.md](school-timetable.md) |
| Timetable generator | Generate a school timetable that keeps every hard wish, take the current timetable into a scenario, compare two or three scenarios, publish one as draft lessons | Schema.org Event, iCalendar VEVENT (RFC 5545) | [timetable-generator.md](timetable-generator.md) |
73 changes: 73 additions & 0 deletions docs/features/timetable-generator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Timetable generator

Planninq makes a school timetable for you. You set the wishes, press Generate, and compare the result with the timetable you have now. When you like it, you publish it as draft lessons. Teachers review the drafts before anything goes live.

Only admins run the generator. Everyone with the `planninq-timetable` read right sees the wishes and the scenarios.

## Set the week grid

An admin sets two values through `POST /apps/planninq/api/settings`. There is no screen for them yet.

- `timetable_period_grid`: the school days and the periods of a day, each with a start and an end time. The default is Monday to Friday with eight periods of 50 minutes from 08:30.
- `timetable_generator_budget_minutes`: how many minutes one run may search. The default is 10.

Planninq refuses a grid without days, and periods that overlap.

## Give the generator its activities

The generator needs to know what to place: which group gets which subject, from which teacher, how many lessons a week, and in which kind of room.

- **With learniq installed**, learniq answers with the activities of its hour plan.
- **Without learniq**, an admin sends two CSV sheets to `POST /apps/planninq/api/timetable/input/upload`: `rooms` (reference, capacity, type) and `activities` (group, subject, teacher, lessons per week, lesson length, room type).

## Set the wishes

Go to Timetable, Wishes. A wish applies to a teacher, a group, a room or an activity. Pick its kind:

| Kind | What it asks |
|---|---|
| Not on these periods | No lessons on the periods you tick. |
| Preferably not on these periods | Avoid the periods you tick if possible. |
| At most a number of lessons a day | Never more lessons a day than the limit. |
| No free periods between lessons | Keep the day without gaps. |
| Always the same room | Every lesson in one room. |

A **hard** wish is always kept. A **soft** wish has a weight from 1 to 3. The generator breaks a heavy soft wish last.

## Generate a scenario

1. Go to Timetable, Scenarios and add a scenario. Choose source "generated", the first week (`weekOf`) and the window it covers.
2. Open it and press **Generate**. The run goes to the background and shows how much of the time budget it used.
3. When it is done, the page shows how many lessons have a place.

A generated scenario never breaks a hard wish. A lesson that has no place without breaking one stays off the grid, and the page names the wish that blocked it. Loosen that wish or make it soft, then press **Generate again**.

## Compare with the timetable you have now

1. Add a scenario with source "imported" and the week you want to take.
2. Open it and press **Take the current timetable**. Planninq reads the scheduled lessons of that week and scores them the same way. Hard wishes the current timetable breaks are listed under "Broken hard wishes".
3. Back on Timetable, Scenarios, choose two or three scenarios under "Scenarios to compare".

Each line shows the value of every scenario, with the best one in bold. Below it, the lessons that sit on another period or in another room.

## Publish as draft lessons

Open a generated scenario that is done and press **Publish as draft lessons**. Planninq writes its lessons as drafts for every week of the window, with source `planninq-generator`.

- Drafts of an earlier scenario for the same window are replaced.
- If lessons of that window from the generator are already published, nothing is written and the page tells you why.

Teachers then see their own drafts, and an admin publishes them through the usual draft review. See [school-timetable.md](school-timetable.md).

## The endpoints

| Call | What it does |
|---|---|
| `POST /apps/planninq/api/timetable/input/upload` | Stores the rooms and activities sheets. |
| `POST /apps/planninq/api/timetable/scenarios/{id}/generate` | Starts a run. Answers 202. |
| `POST /apps/planninq/api/timetable/scenarios/{id}/import` | Takes the scheduled lessons of the scenario's week. |
| `POST /apps/planninq/api/timetable/scenarios/{id}/publish-drafts` | Writes the placements as draft lessons. |

All four are for admins only. Anyone else gets 403.

Next: set your week grid and add your first hard wish.
65 changes: 64 additions & 1 deletion l10n/be.js
Original file line number Diff line number Diff line change
Expand Up @@ -1008,7 +1008,70 @@ OC.L10N.register(
"Reason": "Прычына",
"Why the run failed": "Чаму запуск не ўдаўся",
"Published at": "Апублікаваны",
"When the scenario was written out as draft lessons": "Калі сцэнарый запісаны як чарнавыя ўрокі"
"When the scenario was written out as draft lessons": "Калі сцэнарый запісаны як чарнавыя ўрокі",
"Edit wish": "Рэдагаваць пажаданне",
"New wish": "Новае пажаданне",
"Hard: the generator always keeps it": "Абавязковае: генератар заўсёды яго выконвае",
"Soft: the generator keeps it where it can": "Гнуткае: генератар выконвае яго, калі можа",
"Weight (1 to 3)": "Вага (ад 1 да 3)",
"Most lessons a day": "Не больш за столькі ўрокаў у дзень",
"Period": "Урок",
"{day} period {number}": "{day} урок {number}",
"Teacher (user name)": "Настаўнік (імя карыстальніка)",
"Activity (group:subject)": "Заняткі (група:прадмет)",
"Say who or what the wish is about.": "Пазначце, каго ці чаго датычыць пажаданне.",
"Choose at least one period.": "Выберыце хаця б адзін урок.",
"The limit is a whole number of 1 or more.": "Ліміт: цэлы лік ад 1.",
"Timetable": "Расклад",
"Wishes": "Пажаданні",
"Timetable wishes": "Пажаданні да раскладу",
"Generator run": "Запуск генератара",
"Generator progress": "Ход генератара",
"The run failed: {reason}": "Запуск не ўдаўся: {reason}",
"{placed} of {lessons} lessons placed": "Размешчана {placed} з {lessons} урокаў",
"Generate again": "Згенераваць зноў",
"Generate": "Згенераваць",
"Every lesson has a place.": "Кожны ўрок мае месца.",
"Lesson": "Урок",
"No free period and room": "Няма вольнага ўрока і кабінета",
"Broken soft wishes": "Парушаныя гнуткія пажаданні",
"Every soft wish is kept.": "Кожнае гнуткае пажаданне выканана.",
"Wish": "Пажаданне",
"Lessons": "Урокі",
"Generating: {percent}% of the time budget used": "Генерацыя: выкарыстана {percent}% часу",
"Waiting to start": "Чакае запуску",
"Published as draft lessons": "Апублікавана як чарнавікі ўрокаў",
"Could not read the scenario.": "Не ўдалося прачытаць сцэнарый.",
"Could not start the run.": "Не ўдалося пачаць запуск.",
"Scenarios": "Сцэнарыі",
"Timetable scenarios": "Сцэнарыі раскладу",
"Scenario": "Сцэнарый",
"Take the current timetable": "Узяць бягучы расклад",
"Could not take the current timetable.": "Не ўдалося ўзяць бягучы расклад.",
"Broken hard wishes": "Парушаныя абавязковыя пажаданні",
"Not on a period of the week grid": "Не на ўроку тыднёвай сеткі",
"Compare scenarios": "Параўнаць сцэнарыі",
"Scenarios to compare": "Сцэнарыі для параўнання",
"Choose two or three scenarios to compare them.": "Выберыце два ці тры сцэнарыі, каб параўнаць іх.",
"Measure": "Паказчык",
"Best": "Найлепшы",
"Lessons that differ": "Урокі, якія адрозніваюцца",
"No lesson differs.": "Ніводзін урок не адрозніваецца.",
"Not placed": "Не размешчаны",
"{period} in {room}": "{period}, {room}",
"Lessons placed": "Размешчаныя ўрокі",
"Lessons without a place": "Урокі без месца",
"Clashes": "Канфлікты",
"Hard wishes broken": "Парушаныя абавязковыя пажаданні",
"Soft wishes broken": "Парушаныя гнуткія пажаданні",
"Soft wishes broken, weighted": "Парушаныя гнуткія пажаданні, узважаныя",
"Free periods between lessons, all teachers": "Вокны, усе настаўнікі",
"Most free periods of one teacher": "Найбольш вокнаў у аднаго настаўніка",
"Most lessons of one group on a day": "Найбольш урокаў адной групы за дзень",
"Room use": "Выкарыстанне кабінетаў",
"Publish as draft lessons": "Апублікаваць як чарнавікі ўрокаў",
"Draft lessons written: {count}": "Запісана чарнавікоў урокаў: {count}",
"Could not publish the scenario.": "Не ўдалося апублікаваць сцэнарый."
},
"nplurals=2; plural=(n != 1);"
)
65 changes: 64 additions & 1 deletion l10n/be.json
Original file line number Diff line number Diff line change
Expand Up @@ -1007,7 +1007,70 @@
"Reason": "Прычына",
"Why the run failed": "Чаму запуск не ўдаўся",
"Published at": "Апублікаваны",
"When the scenario was written out as draft lessons": "Калі сцэнарый запісаны як чарнавыя ўрокі"
"When the scenario was written out as draft lessons": "Калі сцэнарый запісаны як чарнавыя ўрокі",
"Edit wish": "Рэдагаваць пажаданне",
"New wish": "Новае пажаданне",
"Hard: the generator always keeps it": "Абавязковае: генератар заўсёды яго выконвае",
"Soft: the generator keeps it where it can": "Гнуткае: генератар выконвае яго, калі можа",
"Weight (1 to 3)": "Вага (ад 1 да 3)",
"Most lessons a day": "Не больш за столькі ўрокаў у дзень",
"Period": "Урок",
"{day} period {number}": "{day} урок {number}",
"Teacher (user name)": "Настаўнік (імя карыстальніка)",
"Activity (group:subject)": "Заняткі (група:прадмет)",
"Say who or what the wish is about.": "Пазначце, каго ці чаго датычыць пажаданне.",
"Choose at least one period.": "Выберыце хаця б адзін урок.",
"The limit is a whole number of 1 or more.": "Ліміт: цэлы лік ад 1.",
"Timetable": "Расклад",
"Wishes": "Пажаданні",
"Timetable wishes": "Пажаданні да раскладу",
"Generator run": "Запуск генератара",
"Generator progress": "Ход генератара",
"The run failed: {reason}": "Запуск не ўдаўся: {reason}",
"{placed} of {lessons} lessons placed": "Размешчана {placed} з {lessons} урокаў",
"Generate again": "Згенераваць зноў",
"Generate": "Згенераваць",
"Every lesson has a place.": "Кожны ўрок мае месца.",
"Lesson": "Урок",
"No free period and room": "Няма вольнага ўрока і кабінета",
"Broken soft wishes": "Парушаныя гнуткія пажаданні",
"Every soft wish is kept.": "Кожнае гнуткае пажаданне выканана.",
"Wish": "Пажаданне",
"Lessons": "Урокі",
"Generating: {percent}% of the time budget used": "Генерацыя: выкарыстана {percent}% часу",
"Waiting to start": "Чакае запуску",
"Published as draft lessons": "Апублікавана як чарнавікі ўрокаў",
"Could not read the scenario.": "Не ўдалося прачытаць сцэнарый.",
"Could not start the run.": "Не ўдалося пачаць запуск.",
"Scenarios": "Сцэнарыі",
"Timetable scenarios": "Сцэнарыі раскладу",
"Scenario": "Сцэнарый",
"Take the current timetable": "Узяць бягучы расклад",
"Could not take the current timetable.": "Не ўдалося ўзяць бягучы расклад.",
"Broken hard wishes": "Парушаныя абавязковыя пажаданні",
"Not on a period of the week grid": "Не на ўроку тыднёвай сеткі",
"Compare scenarios": "Параўнаць сцэнарыі",
"Scenarios to compare": "Сцэнарыі для параўнання",
"Choose two or three scenarios to compare them.": "Выберыце два ці тры сцэнарыі, каб параўнаць іх.",
"Measure": "Паказчык",
"Best": "Найлепшы",
"Lessons that differ": "Урокі, якія адрозніваюцца",
"No lesson differs.": "Ніводзін урок не адрозніваецца.",
"Not placed": "Не размешчаны",
"{period} in {room}": "{period}, {room}",
"Lessons placed": "Размешчаныя ўрокі",
"Lessons without a place": "Урокі без месца",
"Clashes": "Канфлікты",
"Hard wishes broken": "Парушаныя абавязковыя пажаданні",
"Soft wishes broken": "Парушаныя гнуткія пажаданні",
"Soft wishes broken, weighted": "Парушаныя гнуткія пажаданні, узважаныя",
"Free periods between lessons, all teachers": "Вокны, усе настаўнікі",
"Most free periods of one teacher": "Найбольш вокнаў у аднаго настаўніка",
"Most lessons of one group on a day": "Найбольш урокаў адной групы за дзень",
"Room use": "Выкарыстанне кабінетаў",
"Publish as draft lessons": "Апублікаваць як чарнавікі ўрокаў",
"Draft lessons written: {count}": "Запісана чарнавікоў урокаў: {count}",
"Could not publish the scenario.": "Не ўдалося апублікаваць сцэнарый."
},
"plurals": {}
}
Loading
Loading