Enhance school creation and staffing management with native language support
ci / server (push) Failing after 3m44s
ci / client (push) Successful in 14s

- Updated protocol documentation to include `nativeLanguages` in `nameSets` and added `nativeLanguage` to school creation options.
- Enhanced the UI for school creation to allow selection of native languages, improving user experience.
- Revised API interfaces to accommodate new native language features, ensuring proper data handling.
- Improved localization strings to support new native language functionalities in both English and Russian.
- Updated tests to validate the new native language features and ensure robust functionality in staffing scenarios.
This commit is contained in:
Leonid Pershin
2026-08-19 22:22:31 +03:00
parent 21d79cb49b
commit 5a00ad7746
48 changed files with 2064 additions and 312 deletions
+56 -5
View File
@@ -39,7 +39,8 @@ public static class YearlyIntake
Roster roster,
int schoolSeed,
string nameSetId,
DateTime asOf)
DateTime asOf,
string? nativeLanguage = null)
{
ArgumentNullException.ThrowIfNull(catalog);
ArgumentNullException.ThrowIfNull(roster);
@@ -50,6 +51,8 @@ public static class YearlyIntake
throw new ArgumentException($"Unknown name set '{nameSetId}'.", nameof(nameSetId));
}
var native = NativeLanguages.Pick(names, schoolSeed, nativeLanguage, rollIfOmitted: false);
if (roster.Classes.Count == 0)
{
return roster;
@@ -107,6 +110,10 @@ public static class YearlyIntake
var classes = new SchoolClass[roster.Classes.Count];
var newSeats = new List<PupilSeat>();
var incumbents = remainingPeople
.Where(pair => pair.Value.IsStudent)
.Select(pair => pair.Key)
.ToHashSet(StringComparer.Ordinal);
for (var i = 0; i < roster.Classes.Count; i++)
{
var schoolClass = roster.Classes[i];
@@ -134,13 +141,56 @@ public static class YearlyIntake
families,
remainingPeople,
newSeats,
nextFamilyIndex: roster.Families.Select(family => IndexOf(family.Id)).DefaultIfEmpty(-1).Max() + 1);
nextFamilyIndex: roster.Families.Select(family => IndexOf(family.Id)).DefaultIfEmpty(-1).Max() + 1,
native);
GrantPromotedSkills(catalog, names, schoolSeed, when, remainingPeople, classes, incumbents, native);
var people = remainingPeople.Values.ToList();
var filled = AttachPupils(classes, people);
return new Roster(people, families, filled);
}
private static void GrantPromotedSkills(
DefCatalog catalog,
NameSetDef names,
int schoolSeed,
DateTime when,
Dictionary<string, Person> people,
SchoolClass[] classes,
HashSet<string> incumbents,
string? nativeLanguage)
{
var yearByClass = classes.ToDictionary(
schoolClass => schoolClass.Id,
schoolClass => schoolClass.Year,
StringComparer.Ordinal);
foreach (var id in incumbents)
{
if (!people.TryGetValue(id, out var person)
|| person.ClassId is not { } classId
|| !yearByClass.TryGetValue(classId, out var year))
{
continue;
}
var rng = new Random(Seed.Mix(schoolSeed, person.Id, when.Year, Seed.SkillGrantSalt));
people[id] = person with
{
Skills = PersonSampler.EnsurePupilYear(
catalog,
names,
rng,
person.AgeOn(when),
person.Choices,
person.Traits,
person.Skills,
year,
nativeLanguage),
};
}
}
private static void FillFirstYear(
DefCatalog catalog,
NameSetDef names,
@@ -150,7 +200,8 @@ public static class YearlyIntake
List<Family> families,
Dictionary<string, Person> people,
List<PupilSeat> seats,
int nextFamilyIndex)
int nextFamilyIndex,
string? nativeLanguage)
{
var cursor = 0;
foreach (var family in families.ToArray())
@@ -185,7 +236,7 @@ public static class YearlyIntake
var childIndex = family.NextChildIndex;
var rng = new Random(Seed.Mix(schoolSeed, index, Seed.IntakeSalt + yearStart.Year * 10 + childIndex));
var child = FamilyFactory.AddChild(catalog, names, rng, family, members, seats[cursor], yearStart, asOf, childIndex);
var child = FamilyFactory.AddChild(catalog, names, rng, family, members, seats[cursor], yearStart, asOf, childIndex, nativeLanguage);
people[child.Id] = child;
var familyAt = families.FindIndex(candidate => candidate.Id.Equals(family.Id, StringComparison.Ordinal));
families[familyAt] = family with
@@ -209,7 +260,7 @@ public static class YearlyIntake
var leftover = seats.Skip(cursor).ToArray();
foreach (var plan in FamilyPlanner.Singletons(leftover, Math.Max(nextFamilyIndex, 0)))
{
var (created, members) = FamilyFactory.Create(catalog, names, schoolSeed, plan, yearStart, asOf);
var (created, members) = FamilyFactory.Create(catalog, names, schoolSeed, plan, yearStart, asOf, nativeLanguage);
families.Add(created);
foreach (var member in members)
{