Enhance staffing management with uncovered teacher tracking
- 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:
@@ -1,3 +1,4 @@
|
||||
using HSchool.Ai;
|
||||
using HSchool.Content;
|
||||
using HSchool.People;
|
||||
using HSchool.Schedule;
|
||||
@@ -51,6 +52,44 @@ public class LunchTests
|
||||
Assert.All(hunger.Values, value => Assert.True(value > 0.5f, $"somebody came back to school at {value:F2}"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Measured once: people arrive full, hit about 0.4 by their own lunch sitting, leave hungry,
|
||||
/// and come back full the next morning. The numbers live in data; this test is the lock.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Hunger_FollowsTheSchoolDayCurve()
|
||||
{
|
||||
using var school = StaffedSchool();
|
||||
var pupils = school.Roster!.People.Where(person => person.IsStudent).Select(person => person.Id)
|
||||
.ToHashSet(StringComparer.Ordinal);
|
||||
var (junior, senior) = Parallels(school);
|
||||
|
||||
AdvanceTo(school, new DateTime(2012, 4, 3, 8, 40, 0, DateTimeKind.Utc));
|
||||
var morning = Hunger(school, pupils, onCampus: true);
|
||||
Assert.NotEmpty(morning);
|
||||
Assert.All(morning.Values, value => Assert.InRange(value, 0.85f, 1f));
|
||||
|
||||
AdvanceTo(school, new DateTime(2012, 4, 3, 11, 0, 0, DateTimeKind.Utc));
|
||||
var beforeJuniorLunch = Hunger(school, junior, onCampus: true);
|
||||
Assert.NotEmpty(beforeJuniorLunch);
|
||||
Assert.InRange(Median(beforeJuniorLunch.Values), 0.35f, 0.50f);
|
||||
|
||||
AdvanceTo(school, new DateTime(2012, 4, 3, 12, 5, 0, DateTimeKind.Utc));
|
||||
var beforeSeniorLunch = Hunger(school, senior, onCampus: true);
|
||||
Assert.NotEmpty(beforeSeniorLunch);
|
||||
Assert.InRange(Median(beforeSeniorLunch.Values), 0.20f, 0.50f);
|
||||
|
||||
AdvanceTo(school, new DateTime(2012, 4, 3, 14, 45, 0, DateTimeKind.Utc));
|
||||
var leaving = Hunger(school, pupils, onCampus: true);
|
||||
Assert.NotEmpty(leaving);
|
||||
Assert.True(Median(leaving.Values) < 0.55f, $"median hunger at the last lessons was {Median(leaving.Values):F2}");
|
||||
|
||||
AdvanceTo(school, new DateTime(2012, 4, 4, 8, 0, 0, DateTimeKind.Utc));
|
||||
var nextMorning = Hunger(school, pupils, onCampus: false);
|
||||
Assert.NotEmpty(nextMorning);
|
||||
Assert.All(nextMorning.Values, value => Assert.InRange(value, 0.9f, 1f));
|
||||
}
|
||||
|
||||
private static School StaffedSchool()
|
||||
{
|
||||
var (catalog, map) = Vanilla();
|
||||
@@ -108,20 +147,33 @@ public class LunchTests
|
||||
.Select(row => row.PersonId)
|
||||
.ToArray();
|
||||
|
||||
private static Dictionary<string, float> Hunger(School school, HashSet<string> pupils)
|
||||
private static Dictionary<string, float> Hunger(School school, HashSet<string> pupils, bool onCampus = false)
|
||||
{
|
||||
var values = new Dictionary<string, float>(StringComparer.Ordinal);
|
||||
var query = new Arch.Core.QueryDescription().WithAll<PersonIdentity, PersonNeeds>();
|
||||
school.World.Query(in query, (ref PersonIdentity identity, ref PersonNeeds needs) =>
|
||||
var query = new Arch.Core.QueryDescription().WithAll<PersonIdentity, PersonNeeds, Presence>();
|
||||
school.World.Query(in query, (ref PersonIdentity identity, ref PersonNeeds needs, ref Presence presence) =>
|
||||
{
|
||||
if (pupils.Contains(identity.Id) && needs.Values.TryGetValue("Hunger", out var value))
|
||||
if (!pupils.Contains(identity.Id) || !needs.Values.TryGetValue("Hunger", out var value))
|
||||
{
|
||||
values[identity.Id] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
if (onCampus && !presence.IsOnCampus)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
values[identity.Id] = value;
|
||||
});
|
||||
return values;
|
||||
}
|
||||
|
||||
private static float Median(IReadOnlyCollection<float> values)
|
||||
{
|
||||
var sorted = values.OrderBy(value => value).ToArray();
|
||||
return sorted[sorted.Length / 2];
|
||||
}
|
||||
|
||||
private static void AdvanceTo(School school, DateTime until)
|
||||
{
|
||||
while (school.Clock.Time < until)
|
||||
|
||||
Reference in New Issue
Block a user