namespace HSchool.People.Tests; public class ApplicantPoolTests { [Fact] public void SameSeedAndWeek_YieldTheSamePool() { var roster = Fixtures.Generate(Fixtures.VanillaMap()); var a = Create(roster); var b = Create(roster); Assert.Equal(Snapshot(a), Snapshot(b)); } [Fact] public void Advance_KeepsSomePeopleWithTheSameSkillsAndAsk() { var roster = Fixtures.Generate(Fixtures.VanillaMap()); var first = Create(roster); var next = first.Advance(Fixtures.Catalog(), roster, Fixtures.SchoolSeed, "Russia", Fixtures.AsOf.AddDays(7)); Assert.NotEqual(first.Week, next.Week); var stayed = first.Applicants .Join( next.Applicants, applicant => applicant.Person.Id, applicant => applicant.Person.Id, (before, after) => (before, after), StringComparer.Ordinal) .ToArray(); Assert.NotEmpty(stayed); foreach (var (before, after) in stayed) { Assert.Equal(before.HourlyWageAsk, after.HourlyWageAsk); Assert.Equal(Skills(before.Person), Skills(after.Person)); Assert.Equal(before.Person.Name.Full, after.Person.Name.Full); } } [Fact] public void FiftyWeeks_StayAtPoolSize() { var catalog = Fixtures.Catalog(); var roster = Fixtures.Generate(Fixtures.VanillaMap()); var size = catalog.StaffingRules!.PoolSize; var asOf = Fixtures.AsOf; var pool = ApplicantPool.Create(catalog, roster, Fixtures.SchoolSeed, "Russia", asOf); for (var week = 0; week < 50; week++) { asOf = asOf.AddDays(7); pool = pool.Advance(catalog, roster, Fixtures.SchoolSeed, "Russia", asOf); Assert.Equal(size, pool.Applicants.Count); } } [Fact] public void StrongerSkills_AskForMore() { var catalog = Fixtures.Catalog(); var person = Fixtures.Generate(Fixtures.Classrooms(1)).People.First(candidate => candidate.IsParent); var weak = person with { Skills = person.Skills.ToDictionary(pair => pair.Key, _ => 15) }; var strong = person with { Skills = person.Skills.ToDictionary(pair => pair.Key, _ => 90) }; Assert.True(ApplicantPool.HourlyAsk(catalog, strong) > ApplicantPool.HourlyAsk(catalog, weak)); } /// /// design/staffing.md prices an hour from skills *and* traits — the self-assured ask for more. /// Skills are covered above; this pins the trait half, which core sets on Leader (+10), /// HotTempered (+6) and Quiet (-8). /// [Fact] public void ConfidentTraits_AskForMoreAndQuietOnesForLess() { var catalog = Fixtures.Catalog(); var person = Fixtures.Generate(Fixtures.Classrooms(1)).People.First(candidate => candidate.IsParent); var plain = person with { Traits = [] }; var leader = person with { Traits = ["Leader"] }; var quiet = person with { Traits = ["Quiet"] }; var plainAsk = ApplicantPool.HourlyAsk(catalog, plain); Assert.Equal(plainAsk + 10f, ApplicantPool.HourlyAsk(catalog, leader), 0.01f); Assert.Equal(plainAsk - 8f, ApplicantPool.HourlyAsk(catalog, quiet), 0.01f); } [Fact] public void GeneratedApplicants_AreNotOnTheRoster_ParentsKeepTheirId() { var roster = Fixtures.Generate(Fixtures.VanillaMap()); ApplicantPool? pool = null; for (var seed = 1; seed <= 20 && pool is null; seed++) { var candidate = Fixtures.Generate(Fixtures.VanillaMap(), seed); var created = ApplicantPool.Create(Fixtures.Catalog(), candidate, seed, "Russia", Fixtures.AsOf); if (created.Applicants.Any(applicant => candidate.People.Any(person => person.Id == applicant.Person.Id))) { roster = candidate; pool = created; } } Assert.NotNull(pool); var rosterIds = roster.People.Select(person => person.Id).ToHashSet(StringComparer.Ordinal); foreach (var applicant in pool.Applicants) { if (rosterIds.Contains(applicant.Person.Id)) { Assert.Contains(roster.People, person => person.Id == applicant.Person.Id && person.IsParent && !person.IsStaff); } else { Assert.StartsWith("a", applicant.Person.Id, StringComparison.Ordinal); Assert.False(applicant.Person.IsStaff); } } } [Fact] public void PeopleJson_RoundTripsThePool() { var roster = Fixtures.Generate(Fixtures.Classrooms(1)); var pool = Create(roster); var json = RosterJson.Serialize(RosterDocument.From(Fixtures.SchoolSeed, roster, pool)); var loaded = RosterJson.Parse(json); Assert.Equal(Snapshot(pool), Snapshot(loaded.Applicants!)); Assert.DoesNotContain(loaded.People, person => pool.Applicants.Any(applicant => applicant.Person.Id.StartsWith('a') && applicant.Person.Id == person.Id)); } private static ApplicantPool Create(Roster roster) => ApplicantPool.Create(Fixtures.Catalog(), roster, Fixtures.SchoolSeed, "Russia", Fixtures.AsOf); private static string Snapshot(ApplicantPool pool) => string.Join('\n', pool.Applicants.Select(applicant => $"{pool.Week}|{pool.NextIndex}|{applicant.Person.Id}|{applicant.HourlyWageAsk:0.00}|{applicant.Person.Name.Full}|{Skills(applicant.Person)}")); private static string Skills(Person person) => string.Join(',', person.Skills.OrderBy(pair => pair.Key, StringComparer.Ordinal).Select(pair => $"{pair.Key}={pair.Value}")); }