Enhance staffing management with uncovered teacher tracking
ci / server (push) Failing after 3m43s
ci / client (push) Successful in 14s

- Updated `protocol.md` to clarify the concept of uncovered subjects and the number of additional teachers required.
- Introduced `teachersShort` property in the `StaffingSubject` interface to indicate how many more teachers are needed for a subject.
- Enhanced localization strings to reflect the new uncovered teacher information in both English and Russian.
- Updated the management panel UI to display the number of teachers short for each uncovered subject.
- Revised the `Uncovered` method in the staffing logic to return detailed information about uncovered subjects, including the shortfall of teachers.
- Added tests to validate the new uncovered teacher tracking functionality and ensure accurate reporting in the staffing API.
- Marked related tasks as complete in the documentation for the golden fixtures phase.
This commit is contained in:
Leonid Pershin
2026-08-19 23:53:37 +03:00
parent c27d43f66a
commit e5a0ae5b9d
27 changed files with 16055 additions and 38 deletions
@@ -232,6 +232,28 @@ public class StaffingTests
Assert.Contains(Staffing.Uncovered(catalog, assigned.Roster), subject => subject.DefName == "PrimarySchool");
}
[Fact]
public void PrimarySchool_OnVanillaMap_NeedsThreeTeachers_AndASecondDoesNotCloseIt()
{
var (catalog, map, roster, pool) = Fresh();
var rules = catalog.StaffingRules!;
Assert.Equal(80f, Staffing.SubjectHours(catalog, roster, "PrimarySchool"));
Assert.Equal(3, Staffing.TeachersToCover(80f, rules.MaxWeeklyHours));
var empty = Staffing.Uncovered(catalog, roster).Single(subject => subject.DefName == "PrimarySchool");
Assert.Equal(3, empty.TeachersShort);
var outcome = new StaffingOutcome(StaffingError.None, roster, pool, 0, 0, 0, 0);
outcome = HireAndAssign(catalog, map, outcome, "PrimarySchool");
Assert.Equal(2, Staffing.Uncovered(catalog, outcome.Roster).Single(subject => subject.DefName == "PrimarySchool").TeachersShort);
outcome = HireAndAssign(catalog, map, outcome, "PrimarySchool");
Assert.Equal(1, Staffing.Uncovered(catalog, outcome.Roster).Single(subject => subject.DefName == "PrimarySchool").TeachersShort);
outcome = HireAndAssign(catalog, map, outcome, "PrimarySchool");
Assert.DoesNotContain(Staffing.Uncovered(catalog, outcome.Roster), subject => subject.DefName == "PrimarySchool");
}
[Fact]
public void PeopleJson_RoundTripsHiredStaffAndSubjects()
{
@@ -274,6 +296,20 @@ public class StaffingTests
return (catalog, map, roster, pool);
}
private static StaffingOutcome HireAndAssign(
DefCatalog catalog,
MapLayout map,
StaffingOutcome current,
string subject)
{
var applicant = current.Pool.Applicants[0];
var hired = Staffing.Hire(catalog, map, current.Roster, current.Pool, applicant.Person.Id, Staffing.TeacherPosition, 500_000f);
Assert.Equal(StaffingError.None, hired.Error);
var assigned = Staffing.AssignSubject(catalog, hired.Roster, hired.Pool, applicant.Person.Id, subject, 500_000f);
Assert.Equal(StaffingError.None, assigned.Error);
return assigned;
}
private static Applicant WithAsk(Applicant applicant, float hourlyAsk) =>
applicant with { HourlyWageAsk = hourlyAsk };
}