Add timetable planning and related tests to school simulation
ci / server (push) Failing after 3m33s
ci / client (push) Successful in 13s

- Introduced a new project for timetable planning, dependent on HSchool.Content.
- Updated documentation to reflect the addition of timetable planning and its dependencies.
- Added tests for timetable planning to ensure deterministic behavior with the same staff and map.
- Revised architecture documentation to include the new HSchool.Schedule component and its interactions.
This commit is contained in:
Leonid Pershin
2026-08-19 00:55:02 +03:00
parent 2ebc783585
commit 2011d12b1d
12 changed files with 731 additions and 16 deletions
+112
View File
@@ -0,0 +1,112 @@
using HSchool.Content;
namespace HSchool.Schedule.Tests;
internal static class PackDocuments
{
public static IReadOnlyList<ContentDocument> FromDirectory(string packId, string packRoot)
{
var documents = new List<ContentDocument>();
foreach (var path in Directory.EnumerateFiles(packRoot, "*.*", SearchOption.AllDirectories))
{
if (!path.EndsWith(".jsonc", StringComparison.OrdinalIgnoreCase)
&& !path.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
{
continue;
}
var relative = Path.GetRelativePath(packRoot, path).Replace('\\', '/');
documents.Add(new ContentDocument(packId, relative, File.ReadAllText(path)));
}
return documents;
}
}
internal static class Fixtures
{
public static DefCatalog Catalog()
{
var root = Path.Combine(AppContext.BaseDirectory, "vanilla");
return new CatalogLoader().Load(
[CatalogLoader.CorePackId],
PackDocuments.FromDirectory(CatalogLoader.CorePackId, root));
}
public static MapLayout ClassroomsAndGym(int classes, int desks = 16)
{
var rooms = new List<RoomNode>(classes + 1);
for (var i = 0; i < classes; i++)
{
rooms.Add(Classroom(i, desks));
}
rooms.Add(new RoomNode
{
Id = "gym-hall",
Def = "GymHall",
Building = "gym",
Floor = "gym-floor",
Slots = [new SlotFill { Key = "benches", Thing = "Bench", Count = 4 }],
});
return new MapLayout { Rooms = rooms };
}
public static MapLayout ClassroomAndLab(int desks = 16, int computers = 12)
{
return new MapLayout
{
Rooms =
[
Classroom(0, desks),
new RoomNode
{
Id = "computer-lab",
Def = "ComputerLab",
Building = "main",
Floor = "floor-1",
Slots =
[
new SlotFill { Key = "teacherDesk", Thing = "Desk", Count = 1 },
new SlotFill { Key = "teacherChair", Thing = "Chair", Count = 1 },
new SlotFill { Key = "computers", Thing = "Computer", Count = computers },
],
},
],
};
}
public static PlannerClass Class(int index, int year, int pupils, string letter = "A") =>
new($"y{year}{letter}", year, letter, $"classroom-{index:00}", pupils);
public static PlannerTeacher Teacher(string id, params string[] subjects) =>
new(id, subjects);
public static RoomNode Classroom(int index, int desks) =>
new()
{
Id = $"classroom-{index:00}",
Def = "Classroom",
Building = "main",
Floor = "floor-1",
Label = $"{101 + index}",
Seats = desks,
};
public static string RepoRoot()
{
var dir = new DirectoryInfo(AppContext.BaseDirectory);
while (dir is not null && !File.Exists(Path.Combine(dir.FullName, "h-school.sln")))
{
dir = dir.Parent;
}
if (dir is null)
{
throw new InvalidOperationException("Could not find h-school.sln from the test output directory.");
}
return dir.FullName;
}
}