Files
h-school/docs/design/staffing.md
T
Leonid Pershin 25ce26064d
ci / server (push) Failing after 3m39s
ci / client (push) Successful in 14s
Update staffing management to enhance payroll and hiring functionalities
- Increased the `MonthlyPayrollCap` from 10,000 to 40,000, allowing for greater flexibility in hiring and subject assignments.
- Revised payroll calculation logic to ensure that staff members are compensated based on their actual weekly hours, with a minimum payment reflecting one full rate.
- Updated documentation to clarify the new payroll structure and its implications for hiring and subject assignments.
- Enhanced tests to validate the new payroll cap and ensure proper functionality in staffing scenarios, including the handling of uncovered subjects.
- Improved localization strings to reflect changes in staffing and payroll terminology.
2026-08-19 12:51:38 +03:00

20 KiB
Raw Blame History

Наём, предметы и бюджет

Договорённость на следующий срез, не текущий код. Люди: people.md. Типы и карта: defs.md. Экран игрока: near-term.md. Проекты: projects.md.

Первый срез, где игрок что-то решает. До сих пор школа собиралась сама: генератор закрывал все должности, игрок смотрел. Теперь школа открывается пустой — без единого учителя, — и её надо укомплектовать: нанять людей из пула соискателей, раздать им предметы и уложиться в выделенные деньги.

Уроков по-прежнему нет. Предмет — это то, что учитель числится вести, а не то, что происходит в кабинете. Расписание, звонки и оценки — следующий разговор.

Что меняется в уже принятом

Три решения прошлых срезов приходится поправить. Это не мелочь, поэтому названы отдельно.

Было Стало Почему
Генератор закрывает все должности Школа стартует без сотрудников Иначе игроку нечего решать
Кабинет — таблица именованных слотов Кабинет — число мест Никто не будет выбирать, какой стул у доски
PETeacher — отдельная должность Должность одна, физкультура — предмет Иначе на каждый предмет заводится должность

Мебель и вместимость

Вместимость по-прежнему физическая: в классе не может сидеть больше учеников, чем стоит парт. Это не меняется — от неё зависит весь ростер.

Меняется то, как эта мебель задаётся. У кабинета исчезает таблица именованных слотов — «доска», «стол учителя», «стул учителя», «парты» — и остаётся одно число: сколько парт в кабинете. Выбирать между стулом и креслом у доски игрок не будет никогда, а таблица из четырёх строк на каждый из одиннадцати кабинетов делает редактор нечитаемым.

Именованные слоты остаются там, где обстановка действительно разная: столовая, медкабинет, кабинет директора, библиотека. Там выбор осмыслен — сколько столов, какая кушетка.

Правило простое: слоты — там, где есть из чего выбирать; число — там, где важно только сколько.

Предметы

Новый вид def — SubjectDef. Не навык и не работа: навык — это то, что растёт у ребёнка, работа — то, чем занят человек в комнате, а предмет — то, что стоит в дипломе и в нагрузке.

SubjectDef описывает:

  • какие навыки развивает и насколько — список пар «навык + доля»;
  • в каких параллелях преподаётся;
  • сколько часов в неделю в каждой из них — учебный план.

Часы здесь не задел на расписание, а рабочая величина уже этого среза: из них считается нагрузка учителя, а из нагрузки — зарплата. Расписание (schedule.md) только расставит эти часы по дням; сколько их — решает учебный план.

Так «начальные классы» получаются естественно: один предмет, который развивает всё понемногу и преподаётся с первой по четвёртую параллель. Дальше идут предметники: математика с пятой по одиннадцатую, развивает один навык, но сильно.

{
  "defName": "PrimarySchool",
  "grades": { "min": 1, "max": 4 },
  "hoursPerWeek": 20,
  "skills": [
    { "skill": "Mathematics", "share": 0.3 },
    { "skill": "RussianLanguage", "share": 0.3 },
    { "skill": "Literature", "share": 0.2 },
    { "skill": "Biology", "share": 0.2 },
  ],
}

Имена предметов и навыков намеренно совпадают там, где это одно и то же слово: SubjectDef Mathematics и SkillDef Mathematics — разные def'ы разных видов, но подпись у них одна, потому что локали ключуются по имени. Это то, что нужно: игрок не должен видеть «Математика (предмет)».

Доли не обязаны давать в сумме единицу — это не проценты, а вес влияния. Ни на что в этом срезе они не влияют; они здесь потому, что предмет без ответа на вопрос «что он развивает» не проверишь, а дописывать поле позже — значит переписывать весь ванильный контент.

Учитель

Должность одна — Teacher. Предмет не в должности, а в назначении: у нанятого учителя один предмет или несколько.

Отсюда следует важное: потребность в учителях больше не берётся из комнат. Раньше кабинет открывал вакансию «учитель», спортзал — «физрук». Теперь кабинет не открывает ничего: он лишь место, где идёт урок. Школе нужны не «люди в комнатах», а покрытие предметов по параллелям — на каждый предмет, преподаваемый в существующих параллелях, нужен хотя бы один учитель.

Нетренерские должности — директор, секретарь, медсестра, библиотекарь, повар — по-прежнему рождаются из комнат, как описано в defs.md. Их игрок тоже нанимает, но предметов им не назначают.

Соискатели

Пул соискателей — общий список на школу, не «по трое на вакансию».

Соискатель — полноценный человек со всеми слоями: тело, навыки, черты, семья. Иначе выбирать не из чего: весь смысл в том, чтобы сравнить «математика 78, усидчивый» с «математика 41, вспыльчивый».

Соискателем может оказаться родитель ученика — и это не частный случай, а естественное следствие уже принятого: человек может быть в двух ролях сразу. Нанятый родитель становится работником, оставаясь родителем, и никакой второй сущности для него не заводится.

Пул обновляется раз в неделю и не целиком. Часть людей уходит — нашли работу в другом месте, — часть остаётся искать дальше, приходят новые. Полная замена раз в неделю превращала бы выбор в лотерею: понравившийся кандидат исчезал бы, пока игрок думает.

Не нанятый и не ушедший остаётся тем же человеком: с тем же именем, теми же навыками и тем же запросом по зарплате. Обновление детерминировано от сида школы и номера недели.

Деньги

Баланса нет. Сверху выделяют фиксированную сумму на зарплаты в месяц — не на счёт, а как предел. Ничего не копится, сэкономленное не переносится.

Значит и правило одно: месячный фонд оплаты не может превысить выделенную сумму. Выделили 10 000, наняли одного за 3 000 и второго за 7 000 — всё, больше никого. Не «ушли в минус», не «долг», а просто нельзя.

Сколько просит человек

Соискатель называет цену часа. Она складывается из двух вещей:

  • навыки — сильный предметник стоит дороже слабого;
  • черты — самоуверенный просит больше, зажатый меньше, при тех же навыках.

Черта, влияющая на запрос, — это поле у TraitDef, а не список фамилий в коде. Мод, добавивший «скромный», сразу получает более дешёвого кандидата.

Сколько он в итоге стоит

Месячная зарплата — цена часа × недельная нагрузка. Нагрузка не выдумывается: она следует из учебного плана. Назначили человеку математику — он забирает все часы математики во всех параллелях, где она преподаётся, и это может оказаться много.

Ниже одной ставки (BaseWeeklyHours) не платят: нанятый стоит денег, даже если ему ничего не назначили. Иначе набрать полный штат «про запас» было бы бесплатно.

Отсюда два ограничения вместо одного:

  • Деньги. Фонд оплаты не может превысить выделенную сумму. Проверяется при найме и назначении.
  • Человек. Никто не вытянет больше MaxWeeklyHours часов. Если часов предмета больше, нужен второй учитель — часы делятся между ними, и первому становится легче и дешевле.

Предел часов не запрещает назначение, а помечает предмет непокрытым. Запрет завёл бы в тупик: первый учитель предмета всегда получает все его часы, поэтому первым не смог бы стать никто. Считать часы «до предела» тоже нельзя — тогда седьмой предмет тому, кто уже за пределом, достаётся бесплатно, а это ровно та дыра, ради которой почасовая оплата и вводилась. Поэтому часы считаются целиком, деньги это показывают, а список непокрытого говорит, что один человек столько не выучит.

Поэтому «нанять посильнее» и «нагрузить одного вместо двоих» — два разных способа потратить один фонд, и в этом весь выбор среза. Дешёвый учитель, взявший три предмета, упрётся в часы; дорогой с одним предметом съест фонд.

Цифры — шкала цены часа, влияние черт, предел часов — живут в настройках и в core, не в коде.

Старт в выходной

Школа создаётся пустой, и первое, что делает игрок, — нанимает людей. Делать это под тикающие часы учебного дня незачем, поэтому дата по умолчанию переезжает с вторника 3 апреля 2012 на субботу 31 марта 2012: у игрока есть выходные, чтобы собрать штат до того, как в понедельник придут дети.

Учебный год при этом тот же, ростер не меняется.

Экран

Не третья вкладка в панели и не модальное окно. Под часами появляется два верхних раздела:

Раздел Что в нём
Обзор Всё, что есть сейчас: карта / люди слева, просмотр выбранного справа
Управление Деньги, соискатели, штат и назначение предметов

Верхние разделы, а не вкладки внутри панели, потому что управление — это другой режим работы, а не ещё один список. Часы и дата остаются на месте в обоих: наём идёт по игровому времени, пул обновляется по неделям, и прятать календарь на время найма нельзя.

Внутри «Управления» та же схема, что и в «Обзоре»: список слева, карточка справа. Карточка соискателя — та же карточка человека, что и в «Людях»; он и есть человек, просто пока не ваш.

Сверху — одна строка про деньги: выделено, расписано, свободно. Ниже — непокрытые предметы, соискатели и нанятый штат.

Советы, которые стоит принять сразу

  • Не заводить должность на каждый предмет. Иначе PositionDef превращается в справочник специальностей, а назначение предмета становится увольнением и повторным наймом.
  • Не делать баланс, пока сверху приходит фиксированная сумма. Баланс — это уже экономика: накопления, долги, проценты. Здесь нужен предел, а предел — одно число.
  • Не обновлять пул целиком. Кандидат, исчезнувший, пока игрок его разглядывал, читается как баг.
  • Не выкидывать не нанятых людей в ростер школы. Соискатель — не сотрудник и не житель школы; в ростер он попадает только при найме.
  • Проверять предел в момент действия, а не в конце месяца. «Нельзя нанять» игрок понимает, «в конце месяца что-то случилось» — нет.

Заведомо не сейчас

  • Само расписание: когда именно идут эти часы. Следующий срез, schedule.md. Здесь предмет — нагрузка в часах, а не событие в кабинете.
  • Уроки, звонки, оценки, посещаемость.
  • Классное руководство: класс закреплён за кабинетом, но не за учителем.
  • Увольнение и уход по собственному желанию.
  • Переговоры о зарплате, повышения, стаж.
  • Любые траты, кроме зарплат: ремонт, закупки, коммуналка.
  • Накопление денег, долги, штрафы.
  • Требования к диплому: любой нанятый может вести любой предмет, вопрос только в его навыке.

Зафиксировано этим разговором

Тема Решение
Старт школы Без сотрудников вообще; игрок нанимает всех
Стартовая дата Суббота 31 марта 2012 — выходные на сборку штата
Вместимость класса По-прежнему физическая: не больше учеников, чем парт
Кабинет Одно число мест вместо таблицы именованных слотов
Слоты Остаются там, где обстановка разная: столовая, медкабинет, кабинет директора
Должность учителя Одна, Teacher; PETeacher упраздняется
Предмет Новый SubjectDef: какие навыки развивает и в каких параллелях
Начальные классы Предмет, развивающий всё понемногу, параллели 1–4
Назначение У учителя один предмет или несколько
Потребность в учителях Покрытие предметов по параллелям, а не вакансии в комнатах
Прочие должности По-прежнему из комнат; игрок нанимает и их
Соискатель Полноценный человек; может быть родителем ученика
Пул Общий, обновляется раз в неделю частично, детерминированно
Учебный план Часы в неделю по параллелям — поле SubjectDef, а не задел
Запрос по деньгам Цена часа: от навыков и от черт (самоуверенный просит больше)
Зарплата Цена часа × недельная нагрузка из учебного плана, но не меньше одной ставки
Деление часов Учителя одного предмета делят его часы поровну
Предел нагрузки MaxWeeklyHours не запрещает назначение, а делает предмет непокрытым
Непокрытый предмет Нет учителя или назначенные не вытягивают его часы
Бюджет Фиксированная сумма в месяц из настроек; баланса нет
Предел Фонд оплаты не может превысить выделенное; проверка в момент действия
Экран Два верхних раздела под часами: «Обзор» и «Управление»
Внутри управления Список слева, карточка человека справа — та же, что в «Людях»