code:
App.EndWeek.schoolroomReport = function() {
	let frag = document.createDocumentFragment();

	const slaves = App.Utils.sortedEmployees(App.Entity.facilities.schoolroom);
	const devBonus = (V.schoolroomDecoration !== "standard") ? 1 : 0;
	V.flSex = App.EndWeek.getFLSex(App.Entity.facilities.schoolroom); // FIXME: should be local, passed as a parameter to saRules

	function schoolteacherText() {
		let r = [];
		let FLsFetish = 0;
		let idleBonus = 0;

		if (S.Schoolteacher) {
			if (S.Schoolteacher.health.condition < -80) {
				improveCondition(S.Schoolteacher, 20);
			} else if (S.Schoolteacher.health.condition < -40) {
				improveCondition(S.Schoolteacher, 15);
			} else if (S.Schoolteacher.health.condition < 0) {
				improveCondition(S.Schoolteacher, 10);
			} else if (S.Schoolteacher.health.condition < 90) {
				improveCondition(S.Schoolteacher, 7);
			}
			if (S.Schoolteacher.devotion <= 60) {
				S.Schoolteacher.devotion++;
			}
			if (S.Schoolteacher.trust < -30) {
				S.Schoolteacher.trust++;
			}
			S.Schoolteacher.devotion += devBonus;
			if (S.Schoolteacher.rules.living !== "luxurious") {
				S.Schoolteacher.rules.living = "luxurious";
			}
			if (S.Schoolteacher.rules.rest !== "restrictive") {
				S.Schoolteacher.rules.rest = "restrictive";
			}
			if (S.Schoolteacher.fetishStrength <= 95) {
				if (S.Schoolteacher.fetish !== "dom") {
					if (fetishChangeChance(S.Schoolteacher) > jsRandom(0, 100)) {
						FLsFetish = 1;
						S.Schoolteacher.fetishKnown = 1;
						S.Schoolteacher.fetish = "dom";
					}
				} else if (S.Schoolteacher.fetishKnown === 0) {
					FLsFetish = 1;
					S.Schoolteacher.fetishKnown = 1;
				} else {
					FLsFetish = 2;
					S.Schoolteacher.fetishStrength += 4;
				}
			}

			const {He, he, His, his, him, himself, wife} = getPronouns(S.Schoolteacher);
			r.push(`${SlaveFullName(S.Schoolteacher)} is serving as your Schoolteacher.`);
			if (S.Schoolteacher.relationship === -3 && S.Schoolteacher.devotion > 50) {
				r.push(`As your loving ${wife}, ${he} tries ${his} best to teach ${his} pupils how to please you.`);
			}
			if (FLsFetish === 1) {
				r.push(`${He}'s allowed and even expected to use ${his} students for ${his} own sexual gratification, and in their own way ${his} students are complicit, offering sexual favors for an easier time in the classroom. Before long, ${he}'s running a hungry eye over the tits and asses of new trainees; ${he} is now more dominant.`);
			} else if (FLsFetish === 2) {
				r.push(`Every new student in class is a new target for ${his} personal educational attention. <span class="lightsalmon">${He} becomes more dominant.</span>`);
			}
			if (setup.schoolteacherCareers.includes(S.Schoolteacher.career)) {
				r.push(`${He} has experience with students and learning from ${his} life before ${he} was a slave, making ${him} more effective.`);
				idleBonus++;
			} else if (S.Schoolteacher.skill.teacher >= V.masteredXP) {
				r.push(`${He} has experience with students and learning from working for you, making ${him} more effective.`);
				idleBonus++;
			} else {
				S.Schoolteacher.skill.teacher += jsRandom(1, Math.ceil((S.Schoolteacher.intelligence + S.Schoolteacher.intelligenceImplant) / 15) + 8);
			}
			if (S.Schoolteacher.visualAge > 35) {
				r.push(`${His} age earns ${him} the respect of ${his} students.`);
				idleBonus++;
			}
			if (S.Schoolteacher.intelligence > 15) {
				r.push(`${He}'s intelligent enough to be a good teacher.`);
				idleBonus++;
			}
			if (S.Schoolteacher.intelligenceImplant >= 15) {
				r.push(`Since ${he}'s educated ${him}self, ${he} understands `);
				if (V.schoolroomRemodelBimbo !== 0) {
					r.push(`how best to apply the new lesson plan.`);
				} else {
					r.push(`${his} students.`);
				}
				idleBonus++;
			}
			if (S.Schoolteacher.face > 40) {
				r.push(`${His} students want to be just like their beautiful teacher.`);
				idleBonus++;
			}
			if (S.Schoolteacher.accent >= 2) { // really accent === 2, because accent > 2 can't teach anyway
				r.push(`${He}'s been tasked with teaching ${V.language}, but ${he}'s barely understandable ${himself}.`);
				r.push(`This <span class="warning">slows ${his} students' progress</span> with the language.`);
				if (S.Schoolteacher.devotion > 50) {
					r.push(`${He} wants to do better, and devotes any extra time ${he} can find to improving ${his} own competency in ${V.language}.`);
					idleBonus--;
					if (S.Schoolteacher.intelligence + S.Schoolteacher.intelligenceImplant > random(-110, 110)) { // similar chances to saTakeClasses.speechLessons
						r.push(`This week, ${he} makes a breakthrough, <span class="improvement">reducing ${his} accent</span> to a clear, pleasant, exoticism.`);
						S.Schoolteacher.accent--;
					}
				}
			}

			for (const slave of slaves) {
				const {he2} = getPronouns(slave).appendSuffix('2');
				if (S.Schoolteacher.rivalryTarget === slave.ID) {
					r.push(`Ever since ${his} ${rivalryTerm(S.Schoolteacher)} was enrolled, ${he} began stalking the classroom carrying a large paddle, much to ${slave.slaveName}'s terror.`);
					slave.devotion -= 4;
					slave.trust -= 4;
					if (jsRandom(1, 100) > 35) {
						S.Schoolteacher.rivalry++;
						slave.rivalry++;
					}
				} else if (S.Schoolteacher.relationshipTarget === slave.ID) {
					r.push(`${He} dedicates most of ${his} attention to ${his} ${relationshipTerm(S.Schoolteacher)}, ${slave.slaveName}, making sure ${he2} understands that day's lesson.`);
					slave.devotion += 2;
					slave.trust += 2;
				} else if (areRelated(S.Schoolteacher, slave)) {
					r.push(`${He} makes sure to spend extra time teaching ${his} ${relativeTerm(S.Schoolteacher, slave)}, ${slave.slaveName}.`);
					slave.trust++;
				}
			}

			if (slaves.length < V.schoolroom && !slaveResting(S.Schoolteacher)) {
				const _idlePay = jsRandom(1, 10) + ((V.schoolroom - slaves.length) * (jsRandom(150, 170) + (idleBonus * 10)));
				cashX(_idlePay, "school", S.Schoolteacher);
				r.push(`
Since ${he} doesn't have enough students to occupy all of ${his} time, ${V.schoolroomName} takes in citizens' slaves on a contract basis and ${he} teaches them too, earning ${cashFormat(_idlePay)}.
`);
			}

			return r.join(" ");
		}
	}

	const schoolteacherEffects = App.UI.DOM.appendNewElement("p", frag, '', "indent");
	$(schoolteacherEffects).append(schoolteacherText());

	if (slaves.length > 0) {
		const intro = App.UI.DOM.appendNewElement("p", frag, '', "indent");
		if (slaves.length > 1) {
			$(intro).append(`<strong>There are ${slaves.length} slaves studying in ${V.schoolroomName}.</strong>`);
		} else {
			$(intro).append(`<strong>There is one slave studying in ${V.schoolroomName}.</strong>`);
		}
	}

	if (S.Schoolteacher) {
		const slave = S.Schoolteacher;
		V.i = V.slaveIndices[slave.ID];
		App.Utils.setLocalPronouns(slave); // need this for the includes
		/* apply following SA passages to facility leader */
		if (V.showEWD !== 0) {
			const schoolteacherEntry = App.UI.DOM.appendNewElement("div", frag, '', "slave-report");
			if (V.seeImages && V.seeReportImages) {
				App.UI.DOM.appendNewElement("div", schoolteacherEntry, App.Art.SlaveArtElement(slave, 0, 0), ["imageRef", "tinyImg"]);
			}
			schoolteacherEntry.append(App.EndWeek.favoriteIcon(slave), " ");
			$(schoolteacherEntry).append(`<span class='slave-name'>${SlaveFullName(slave)}</span> is serving as your Schoolteacher.`);
			$(schoolteacherEntry).append(App.SlaveAssignment.standardSlaveReport(slave, false));
		} else {
			App.SlaveAssignment.standardSlaveReport(slave, true);
		}
	}

	let restedSlaves = 0;
	for (const slave of slaves) {
		V.i = V.slaveIndices[slave.ID];
		slave.devotion += devBonus;
		if (slave.health.condition < -80) {
			improveCondition(slave, 20);
		} else if (slave.health.condition < -40) {
			improveCondition(slave, 10);
		} else if (slave.health.condition < 0) {
			improveCondition(slave, 7);
		} else if (slave.health.condition < 90) {
			improveCondition(slave, 3);
		}
		if (slave.rules.living !== "normal") {
			slave.rules.living = "normal";
		}
		/* Education done? Has to be here before we run the standard slave report or there will be double entries for slave */
		if (slave.fetish === "mindbroken") {
			if (V.assignmentRecords[slave.ID]) {
				assignJobSafely(slave, V.assignmentRecords[slave.ID]);
			} else {
				removeJob(slave, "learn in the schoolroom");
			}
			restedSlaves++;
			continue;
		} else if (	((slave.intelligenceImplant >= 30 && V.schoolroomRemodelBimbo !== 1) || (slave.intelligenceImplant <= -15 && V.schoolroomRemodelBimbo === 1)) &&
					((slave.voice === 0) || (slave.accent <= 1) || ((V.schoolroomUpgradeLanguage === 0) && (slave.accent <= 2))) &&
					((slave.skill.oral > 30) || ((V.schoolroomUpgradeSkills === 0) && (slave.skill.oral > 10))) &&
					((slave.skill.whoring > 30) || ((V.schoolroomUpgradeSkills === 0) && (slave.skill.whoring > 10))) &&
					((slave.skill.entertainment > 30) || ((V.schoolroomUpgradeSkills === 0) && (slave.skill.entertainment > 10))) &&
					((slave.skill.anal > 30) || ((V.schoolroomUpgradeSkills === 0) && (slave.skill.anal > 10))) &&
					((slave.skill.vaginal > 30) || ((V.schoolroomUpgradeSkills === 0) && (slave.skill.vaginal > 10)) || (slave.vagina < 0)) &&
					(tutorForSlave(slave) === null || V.tutorGraduate.includes(slave.ID))) {
			const reassignment = App.UI.DOM.appendNewElement("p", frag, '');
			const {he, his} = getPronouns(slave);
			let r = [];
			r.push(`<span class="slave-name">${slave.slaveName}</span> can learn little from further classes,`);
			r.push(`<span class="noteworthy">`);
			if (V.assignmentRecords[slave.ID]) {
				const oldJob = V.assignmentRecords[slave.ID];
				assignJobSafely(slave, oldJob);
				if (slave.choosesOwnAssignment === 1) {
					r.push(`and ${he} is resting before choosing another task.`);
				} else if (slave.assignment === "rest") {
					if (oldJob !== "rest") {
						r.push(`and since ${he} was unable to return to ${his} old task to ${oldJob}, ${his} assignment has defaulted to rest.`);
					} else {
						r.push(`so ${he} has returned to rest.`);
					}
				} else {
					r.push(`so ${he} goes back to ${slave.assignment}.`);
				}
			} else {
				r.push(`so ${his} assignment has defaulted to rest.`);
				removeJob(slave, "learn in the schoolroom");
			}
			r.push(`</span>`);
			restedSlaves++;
			$(reassignment).append(r.join(" "));
			continue;
		}

		App.Utils.setLocalPronouns(slave); // need this for the includes
		if (V.showEWD !== 0) {
			const {He} = getPronouns(slave);
			const slaveEntry = App.UI.DOM.appendNewElement("div", frag, '', "slave-report");
			if (V.seeImages && V.seeReportImages) {
				App.UI.DOM.appendNewElement("div", slaveEntry, App.Art.SlaveArtElement(slave, 0, 0), ["imageRef", "tinyImg"]);
			}
			slaveEntry.append(App.EndWeek.favoriteIcon(slave), " ");
			$(slaveEntry).append(`<span class='slave-name'>${SlaveFullName(slave)}</span> `);
			if (slave.choosesOwnAssignment === 2) {
				$(slaveEntry).append(App.SlaveAssignment.choosesOwnJob(slave));
			} else {
				let leaderTraining = '';
				for (const role of Object.keys(V.slaveTutor)) {
					if (V.slaveTutor[role].contains(slave.ID)) {
						leaderTraining = ` on being a ${role}`; break;
					}
				}
				$(slaveEntry).append(`is studying in ${V.schoolroomName}${leaderTraining}.`);
			}
			const studentContent = App.UI.DOM.appendNewElement("div", slaveEntry, '', "indent");
			$(studentContent).append(`${He} ${App.SlaveAssignment.takeClasses(slave)}`);
			$(slaveEntry).append(App.SlaveAssignment.standardSlaveReport(slave, false));
		} else {
			// discard return values silently
			App.SlaveAssignment.choosesOwnJob(slave);
			App.SlaveAssignment.takeClasses(slave);
			App.SlaveAssignment.standardSlaveReport(slave, true);
		}
	}

	if (restedSlaves > 0) {
		const rested = App.UI.DOM.appendNewElement("p", frag, '', "indent");
		rested.append((restedSlaves === 1) ? `One slave has ` : `${restedSlaves} slaves have `);
		App.UI.DOM.appendNewElement("span", rested, "learned", "green");
		rested.append(` all they can, and will be released from the schoolroom before the end of the week.`);
	}

	if (V.schoolroomDecoration !== "standard") {
		const decorationEffects = App.UI.DOM.appendNewElement("p", frag, '', "indent");
		$(decorationEffects).append(`${capFirstChar(V.schoolroomName)}'s ${V.schoolroomDecoration} atmosphere <span class="hotpink">has a minor impact on the students.</span>`);
		if (App.Entity.facilities.schoolroom.revivalistLanguageDecorationBonus()) {
			$(decorationEffects).append(` They also <span class="positive">learn ${V.language} more quickly</span> in this environment.`);
		}
	}

	return frag;
};


code:
App.EndWeek.servantsQuartersReport = function() {
	let frag = document.createDocumentFragment();

	const slaves = App.Utils.sortedEmployees(App.Entity.facilities.servantsQuarters);
	const devBonus = (V.servantsQuartersDecoration !== "standard") ? 1 : 0;
	let stewardessBonus = 0;

	function stewardessText() {
		let r = [];
		let FLsFetish = 0;
		let stewardessImpregnated = 0;

		if (S.Stewardess) {
			S.Stewardess.devotion += devBonus;
			if (S.Stewardess.health.condition < -80) {
				improveCondition(S.Stewardess, 20);
			} else if (S.Stewardess.health.condition < -40) {
				improveCondition(S.Stewardess, 15);
			} else if (S.Stewardess.health.condition < 0) {
				improveCondition(S.Stewardess, 10);
			} else if (S.Stewardess.health.condition < 90) {
				improveCondition(S.Stewardess, 7);
			}
			if (S.Stewardess.devotion <= 60) {
				S.Stewardess.devotion++;
			}
			if (S.Stewardess.trust < 60) {
				S.Stewardess.trust++;
			}
			if (S.Stewardess.fetishStrength <= 95) {
				if (S.Stewardess.fetish !== "dom") {
					if (fetishChangeChance(S.Stewardess) > jsRandom(0, 100)) {
						FLsFetish = 1;
						S.Stewardess.fetishKnown = 1;
						S.Stewardess.fetish = "dom";
					}
				} else if (S.Stewardess.fetishKnown === 0) {
					FLsFetish = 1;
					S.Stewardess.fetishKnown = 1;
				} else {
					FLsFetish = 2;
					S.Stewardess.fetishStrength += 4;
				}
			}
			if (S.Stewardess.rules.living !== "luxurious") {
				S.Stewardess.rules.living = "luxurious";
			}
			if (S.Stewardess.rules.rest !== "restrictive") {
				S.Stewardess.rules.rest = "restrictive";
			}

			const {He, he, His, his, him, wife} = getPronouns(S.Stewardess);
			r.push(`${SlaveFullName(S.Stewardess)} is serving as your Stewardess.`);
			if (S.Stewardess.relationship === -3 && S.Stewardess.devotion > 50) {
				r.push(`${He} does ${his} best to be your perfect lovely house${wife}.`);
			}
			if (V.stewardessImpregnates === 1 && canPenetrate(S.Stewardess) && S.Stewardess.pubertyXY === 1 && !slaveResting(S.Stewardess)) {
				for (const slave of slaves) {
					if (canImpreg(slave, S.Stewardess)) { // FIXME: assumes vaginal pregnancy without check
						seX(S.Stewardess, "penetrative", slave, "vaginal", 10);
						r.push(knockMeUp(slave, 100, 2, S.Stewardess.ID));
						if (slave.vagina === 0) {
							slave.vagina = 1; // FIXME: silent virginity loss?
						}
						stewardessImpregnated++;
						slave.need = 0;
					}
				}
				if (stewardessImpregnated > 0) {
					r.push(`It's ${his} responsibility to keep ${his} charges pregnant, so`);
					if (stewardessImpregnated === 1) {
						r.push(`${he} cums in the only fertile slave's pussy all week.`);
					} else {
						r.push(`${he} ensures the ${stewardessImpregnated} fertile servants are full of ${his} cum at all times.`);
					}
					if (S.Stewardess.career === "a breeding bull") {
						r.push(`${He} was conditioned to fill empty wombs, so ${he} takes a <span class="hotpink">deep pleasure</span> in ${his} job.`);
						S.Stewardess.devotion++;
					}
					S.Stewardess.need = 0;
				}
			}
			if (FLsFetish === 1) {
				r.push(`${He}'s allowed and even expected to use ${his} charges for ${his} own gratification, and sometimes they'll even instigate submissive sex with ${him} just to ingratiate themselves. ${He} becomes comfortable with the role of a sexual dominant.`);
			} else if (FLsFetish === 2) {
				r.push(`Having a legion of servants jumping to obey ${his} daily commands <span class="lightsalmon">makes ${him} more dominant.</span>`);
			}
			if (S.Stewardess.health.condition < 10) {
				r.push(`Though ${he} tries to watch the servants, ${his} lack of good health inhibits ${him}.`);
			} else if (S.Stewardess.health.condition < 80) {
				stewardessBonus += 50;
				r.push(`${His} good health allows ${him} to work long hours and <span class="yellowgreen">drive</span> the servants hard.`);
			} else {
				stewardessBonus += 75;
				r.push(`${His} perfect health allows ${him} to work exhaustive hours and <span class="yellowgreen">drive</span> the servants very hard.`);
			}
			if (setup.stewardessCareers.includes(S.Stewardess.career)) {
				stewardessBonus += 25;
				r.push(`${He} has applicable experience with daily sums and organizational trifles from ${his} life before ${he} was a slave.`);
			} else if (S.Stewardess.skill.stewardess >= V.masteredXP) {
				stewardessBonus += 25;
				r.push(`${He} has applicable experience with daily sums and organizational trifles from working for you.`);
			} else {
				S.Stewardess.skill.stewardess += jsRandom(1, Math.ceil((S.Stewardess.intelligence + S.Stewardess.intelligenceImplant) / 15) + 8);
			}
			if (S.Stewardess.actualAge > 35) {
				stewardessBonus += 25;
				r.push(`${His} age and experience give ${him} added effectiveness.`);
			} else if (V.AgePenalty === 0) {
				stewardessBonus +=25;
			}
			if (S.Stewardess.intelligence + S.Stewardess.intelligenceImplant > 15) {
				stewardessBonus += S.Stewardess.intelligence + S.Stewardess.intelligenceImplant;
				r.push(`${He}'s smart enough that ${he} misses very little.`);
			}
			stewardessBonus *= restEffects(S.Stewardess);
			const bonusToggle = S.Stewardess.energy > 95 || (S.Stewardess.fetishKnown === 1 && S.Stewardess.fetish === "dom");
			for (const slave of slaves) {
				const {he2, him2, his2} = getPronouns(slave).appendSuffix('2');
				if (S.Stewardess.rivalryTarget === slave.ID) {
					r.push(`${He} bears down on ${his} ${rivalryTerm(S.Stewardess)}, ${slave.slaveName}, forcing ${him2} to work extra hard or suffer punishment.`);
					slave.devotion--;
					slave.trust--;
					if (jsRandom(1, 100) > 65) {
						S.Stewardess.rivalry++;
						slave.rivalry++;
					}
				} else if (S.Stewardess.relationshipTarget === slave.ID) {
					r.push(`${He} dotes over ${his} ${relationshipTerm(S.Stewardess)}}, _${slave.slaveName}, making sure ${he2} knows how to do a good job and keeping ${him2} well stocked with everything ${he2} needs.`);
					slave.devotion++;
				} else if (areRelated(S.Stewardess, slave)) {
					r.push(`${He} pays special attention to ${his} ${relativeTerm(S.Stewardess, slave)}, ${slave.slaveName}, making sure ${he2} doesn't slack off just because they are related.`);
					slave.trust++;
				}
				switch (slave.prestigeDesc) {
					case "$He is a famed Free Cities whore, and commands top prices.":
						r.push(`${He} does ${his} best to motivate the famous whore, ${slave.slaveName}, though ${he} is uncertain why ${he2} is here.`);
						break;
					case "$He is a famed Free Cities slut, and can please anyone.":
						r.push(`${He} ignores ${his} duties in order the fawn over the famous entertainer, ${slave.slaveName}.`);
						break;
					case "$He is remembered for winning best in show as a cockmilker.":
						if ((slave.dick !== 0) && (slave.balls !== 0) && (slave.prostate > 1)) {
							r.push(`${slave.slaveName} leaves a trail of cum wherever ${he2} goes and frequently stops to masturbate, greatly annoying ${S.Stewardess.slaveName}.`);
							if (jsRandom(1, 100) > 90 && S.Stewardess.rivalry === 0 && slave.rivalry === 0) {
								S.Stewardess.rivalryTarget = slave.ID;
								S.Stewardess.rivalry++;
								slave.rivalryTarget = S.Stewardess.ID;
								slave.rivalry++;
							}
						}
						break;
					case "$He is remembered for winning best in show as a dairy cow.":
						if ((slave.lactation > 0) && ((slave.boobs-slave.boobsImplant) > 6000)) {
							r.push(`${slave.slaveName}'s huge breasts frequently get in the way of ${his2} work and ${he2} leaks milk everywhere, greatly annoying ${S.Stewardess.slaveName}.`);
							if (jsRandom(1, 100) > 90 && S.Stewardess.rivalry === 0 && slave.rivalry === 0) {
								S.Stewardess.rivalryTarget = slave.ID;
								S.Stewardess.rivalry++;
								slave.rivalryTarget = S.Stewardess.ID;
								slave.rivalry++;
							}
						}
						break;
					case "$He is remembered for winning best in show as a breeder.":
						if (slave.bellyPreg >= 10000) {
							r.push(`${slave.slaveName}'s big pregnant belly frequently gets in the way of ${his2} work. However, ${S.Stewardess.slaveName} is willing to overlook it, as well as allow ${him2} easy jobs, due to how valuable ${his2} womb is.`);
						}
						break;
				}
				if (bonusToggle && slave.devotion < 45) {
					slave.devotion += 5;
				}
			}
			if (slaves.length > 0) {
				if (S.Stewardess.fetishKnown === 1 && S.Stewardess.fetish === "dom") {
					r.push(`${He} walks among the cowering servants as a queen among peasants, playing the role of a dominant to perfection and increasing ${his} charges' <span class="hotpink">obedience.</span>`);
				} else if (S.Stewardess.energy > 95) {
					r.push(`${He} walks among the cowering servants looking for an excuse to extract sex from shirkers as <span class="hotpink">punishment.</span>`);
				}
				cashX(stewardessBonus * slaves.length, "servantsQuarters", S.Stewardess);
			}
			if (V.arcologies[0].FSRestart !== "unset" && stewardessImpregnated > 0 && V.eugenicsFullControl !== 1) {
				r.push(`<br>The Societal Elite know you've ordered ${S.Stewardess.slaveName} to impregnate your maids. <span class="red">They are not amused by your disinterest in eugenics.</span>`);
				V.failedElite += 10;
			}
		}
		return r.join(' ');
	}

	const stewardessEffects = App.UI.DOM.appendNewElement("p", frag, '', "indent");
	$(stewardessEffects).append(stewardessText());

	if (slaves.length > 0) {
		const intro = App.UI.DOM.appendNewElement("p", frag, '', "indent");
		let r = [];
		if (slaves.length > 1) {
			r.push(`<strong>There are ${slaves.length} slaves working out of the servants' quarters.</strong> They work to`);
		} else {
			const {He} = getPronouns(slaves[0]);
			r.push(`<strong>There is one slave working out of the servants' quarters.</strong> ${He} works to`);
		}
		r.push(`<span class="yellowgreen">reduce</span> your household expenses; having a well-staffed house slightly <span class="green">increases</span> your reputation.`);
		$(intro).append(r.join(' '));
	}

	if (S.Stewardess) {
		/** @type {App.Entity.SlaveState} */
		const slave = S.Stewardess;
		V.i = V.slaveIndices[slave.ID];
		App.Utils.setLocalPronouns(slave); // need this for the includes
		/* apply following SA passages to facility leader */
		if (V.showEWD !== 0) {
			const stewardessEntry = App.UI.DOM.appendNewElement("div", frag, '', "slave-report");
			if (V.seeImages && V.seeReportImages) {
				App.UI.DOM.appendNewElement("div", stewardessEntry, App.Art.SlaveArtElement(slave, 0, 0), ["imageRef", "tinyImg"]);
			}
			stewardessEntry.append(App.EndWeek.favoriteIcon(slave), " ");
			$(stewardessEntry).append(`<span class='slave-name'>${SlaveFullName(slave)}</span> is serving as your Stewardess.`);
			$(stewardessEntry).append(App.SlaveAssignment.standardSlaveReport(slave, false));
		} else {
			App.SlaveAssignment.standardSlaveReport(slave, true);
		}
	}

	let SQMilk = 0;
	let SQMilkSale = 0;

	for (const slave of slaves) {
		V.i = V.slaveIndices[slave.ID];
		slave.devotion += devBonus;
		if (slave.devotion <= 20 && slave.trust >= -20) {
			slave.devotion -= 5;
			slave.trust -= 5;
		} else if (slave.devotion <= 10) {
			slave.devotion += 2;
		} else if (slave.devotion >= 80) {
			slave.devotion -= 2;
		}
		if (slave.devotion < -20) {
			slave.trust += 3;
		} else if (slave.trust < -50) {
			slave.trust += 2;
		} else if (slave.trust < -30) {
			slave.trust += 1;
		}
		if (slave.health.condition < -80) {
			improveCondition(slave, 20);
		} else if (slave.health.condition < -40) {
			improveCondition(slave, 10);
		} else if (slave.health.condition < 0) {
			improveCondition(slave, 7);
		} else if (slave.health.condition < 90) {
			improveCondition(slave, 3);
		}
		switch (V.servantsQuartersDecoration) {
			case "Arabian Revivalist":
			case "Aztec Revivalist":
			case "Chattel Religionist":
			case "Chinese Revivalist":
			case "Degradationist":
			case "Edo Revivalist":
			case "Egyptian Revivalist":
			case "Roman Revivalist":
			case "Subjugationist":
			case "Supremacist":
				slave.rules.living = "spare";
				break;
			case "Slave Professionalism":
				if (slave.intelligence+slave.intelligenceImplant > 15) {
					slave.rules.living = "normal";
				} else {
					slave.rules.living = "spare";
				}
				break;
			case "Petite Admiration":
			case "Statuesque Glorification":
				if (heightPass(slave)) {
					slave.rules.living = "normal";
				} else {
					slave.rules.living = "spare";
				}
				break;
			default:
				slave.rules.living = "normal";
		}

		App.Utils.setLocalPronouns(slave); // need this for the includes
		if (V.showEWD !== 0) {
			const {He} = getPronouns(slave);
			const slaveEntry = App.UI.DOM.appendNewElement("div", frag, '', "slave-report");
			if (V.seeImages && V.seeReportImages) {
				App.UI.DOM.appendNewElement("div", slaveEntry, App.Art.SlaveArtElement(slave, 0, 0), ["imageRef", "tinyImg"]);
			}
			slaveEntry.append(App.EndWeek.favoriteIcon(slave), " ");
			$(slaveEntry).append(`<span class='slave-name'>${SlaveFullName(slave)}</span> `);
			if (slave.choosesOwnAssignment === 2) {
				$(slaveEntry).append(App.SlaveAssignment.choosesOwnJob(slave));
			} else {
				$(slaveEntry).append(`is working out of ${V.servantsQuartersName}.`);
			}
			const servantContent = App.UI.DOM.appendNewElement("div", slaveEntry, '', "indent");
			$(servantContent).append(`${He} ${App.SlaveAssignment.servant(slave, stewardessBonus)}`);
			if ((V.servantMilkers === 1) && (slave.lactation > 0)) {
				const milkContent = App.UI.DOM.appendNewElement("div", slaveEntry, '', "indent");
				const milkResults = App.SlaveAssignment.getMilked(slave, 0.5);
				$(milkContent).append(`${He} ${milkResults.text}`);
				SQMilk += milkResults.milk;
				SQMilkSale += milkResults.milkSale;
			}
			$(slaveEntry).append(App.SlaveAssignment.standardSlaveReport(slave, false));
		} else {
			// discard return values silently
			App.SlaveAssignment.choosesOwnJob(slave);
			App.SlaveAssignment.servant(slave, stewardessBonus);
			if ((V.servantMilkers === 1) && (slave.lactation > 0)) {
				const milkResults = App.SlaveAssignment.getMilked(slave, 0.5);
				SQMilk += milkResults.milk;
				SQMilkSale += milkResults.milkSale;
			}
			App.SlaveAssignment.standardSlaveReport(slave, true);
		}
	}

	if (SQMilk > 0) {
		const milkEffects = App.UI.DOM.appendNewElement("p", frag, '', "indent");
		$(milkEffects).append(`Since your lactating servants spend most of their time working in the penthouse, they use the milkers there, giving ${SQMilk} liters of milk over the week, which is sold for ${cashFormat(SQMilkSale)}.`);
	}

	if (V.servantsQuartersDecoration !== "standard") {
		const decorationEffects = App.UI.DOM.appendNewElement("p", frag, '', "indent");
		$(decorationEffects).append(`${capFirstChar(V.servantsQuartersName)}'s ${V.servantsQuartersDecoration} atmosphere <span class="hotpink">has a minor impact on your servants.</span>`);
	}

	repX(slaves.length * 20, "servantsQuarters");

	return frag;
};

repo_name: Welptard/fc

license: mit


code:
/**
 * @param {ParentNode} renderContainer
 */
App.EndWeek.computeSexualServicesModel = function(renderContainer) {
	const arcology = V.arcologies[0];

	/* Sexual services demand per class */
	/* We can add milk etc. to this as well, though this is a good that can be traded beyond the arcology and also needs its own market much like the slave market. TODO */
	/* In the default scenario these numbers indicate the amount of money each individual citizen of a particular class is looking to spend on sex (and milk) every week
	Depending on the conditions they may derive more or less 'utility' out of their credits spent
	i.e. a highly paternalist arcology with little choice for its lower class but Fuckdolls may still put money into them but not get the same satisfaction out of it*/
	/* Low rent increases demand/available money for sexual services, high rent decreases it */
	if (V.brothelBoost.selected > 0 && V.rep < V.brothelBoost.selected * 500 + 2000) {
		V.brothelBoost.eligible = Math.trunc((V.rep - 2000) / 500);
		if (V.brothelBoost.eligible < 0) {
			V.brothelBoost.eligible = 0;
		}
	} else {
		V.brothelBoost.eligible = V.brothelBoost.selected;
	}
	repX(forceNeg(50 * V.brothelBoost.eligible), "brothel");

	let lowerClassSexDemand = Math.trunc(V.lowerClass * V.whoreBudget.lowerClass) * 2,
		lowerClassSexDemandRef = Math.max(lowerClassSexDemand, 1),
		visitorsSexDemand = Math.trunc(V.visitors) * 40,
		middleClassSexDemand = Math.trunc(V.middleClass * V.whoreBudget.middleClass + visitorsSexDemand * 0.5) * 2,
		middleClassSexDemandRef = Math.max(middleClassSexDemand, 1),
		upperClassSexDemand = Math.trunc(V.upperClass * V.whoreBudget.upperClass + visitorsSexDemand * 0.5) * 2,
		upperClassSexDemandRef = Math.max(upperClassSexDemand, 1),
		topClassSexDemand = Math.trunc(V.topClass * V.whoreBudget.topClass) * 2,
		topClassSexDemandRef = Math.max(topClassSexDemand, 1),
		arcadeSupply = {lowerClass: 0, middleClass: 0, upperClass: 0},
		clubSupply = {lowerClass: 0, middleClass: 0};

	App.EndWeek.saVars.whorePriceAdjustment = {
		lowerClass: 0, middleClass: 0, upperClass: 0, topClass: 0
	};
	V.NPCMarketShare = {
		lowerClass: 0, middleClass: 0, upperClass: 0, topClass: 0
	};
	V.sexDemandResult = {
		lowerClass: 0, middleClass: 0, upperClass: 0, topClass: 0
	};
	App.EndWeek.saVars.slaveJobValues = slaveJobValues(lowerClassSexDemandRef, middleClassSexDemandRef, upperClassSexDemandRef, topClassSexDemandRef);
	const sjv = App.EndWeek.saVars.slaveJobValues;

	let lowerClassArcadeSexDemand = 0;
	let middleClassArcadeSexDemand = 0;
	let upperClassArcadeSexDemand = 0;
	let arcadeDemand = 0;
	let arcadeDemandDeg = 0;
	// Arcade Specific Demand for Degradationists to give it higher priority
	if (arcology.FSDegradationist !== "unset" && arcology.FSDegradationist > 0) {
		lowerClassArcadeSexDemand = Math.trunc(lowerClassSexDemand * arcology.FSDegradationist * 0.0015);
		middleClassArcadeSexDemand = Math.trunc(middleClassSexDemand * arcology.FSDegradationist * 0.0002);
		upperClassArcadeSexDemand = Math.trunc(upperClassSexDemand * arcology.FSDegradationist * 0.000025);
		arcadeDemandDeg = lowerClassArcadeSexDemand + middleClassArcadeSexDemand + upperClassArcadeSexDemand;
		if (arcology.FSDegradationistLaw === 1) {
			arcadeDemandDeg += V.ASlaves * 3;
		}
		if (sjv.arcade < arcadeDemandDeg) {
			if (arcadeDemandDeg > 20000 || arcology.FSDegradationist > 50) {
				const degradationistPenalty = ((arcadeDemandDeg - sjv.arcade) / arcadeDemandDeg);
				arcology.FSDegradationist -= Math.trunc(5 * degradationistPenalty);
				repX(forceNeg(100 * degradationistPenalty), "futureSocieties");
				V.arcadeDemandDegResult = degradationistPenalty > 0.5 ? 1 : 2;
			} else {
				V.arcadeDemandDegResult = 3;
			}
			arcadeSupply.lowerClass += Math.trunc(1.15 * sjv.arcade * (lowerClassArcadeSexDemand / arcadeDemandDeg));
			lowerClassSexDemand -= arcadeSupply.lowerClass;
			arcadeSupply.middleClass += Math.trunc(1.15 * sjv.arcade * (middleClassArcadeSexDemand / arcadeDemandDeg));
			middleClassSexDemand -= arcadeSupply.middleClass;
			arcadeSupply.upperClass += Math.trunc(1.15 * sjv.arcade * (upperClassArcadeSexDemand / arcadeDemandDeg));
			upperClassSexDemand -= arcadeSupply.upperClass; // There's a 15% satisfaction bonus from getting release in a societally approved way
		} else {
			if (sjv.arcade >= arcadeDemandDeg * 1.5) {
				arcology.FSDegradationist += 2;
				V.arcadeDemandDegResult = 4;
				repX(500, "futureSocieties");
			} else {
				V.arcadeDemandDegResult = 5;
			}
			arcadeSupply.lowerClass += Math.trunc(1.15 * lowerClassArcadeSexDemand);
			lowerClassSexDemand -= arcadeSupply.lowerClass;
			arcadeSupply.middleClass += Math.trunc(1.15 * middleClassArcadeSexDemand);
			middleClassSexDemand -= arcadeSupply.middleClass;
			arcadeSupply.upperClass += Math.trunc(1.15 * upperClassArcadeSexDemand);
			upperClassSexDemand -= arcadeSupply.upperClass;
		}
	} else {
		arcadeDemandDeg = 0;
	}

	// Public slut sex supply. Top and upper class won't partake
	let clubDemand = lowerClassSexDemand + middleClassSexDemand;
	let lowerClassClubRatio = lowerClassSexDemand / clubDemand;
	let middleClassClubRatio = middleClassSexDemand / clubDemand;

	if (sjv.club * sjv.clubSP < clubDemand) {
		lowerClassSexDemand -= Math.trunc(sjv.club * sjv.clubSP * lowerClassClubRatio);
		middleClassSexDemand -= Math.trunc(sjv.club * sjv.clubSP * middleClassClubRatio);
	} else {
		lowerClassSexDemand = 0;
		middleClassSexDemand = 0;
	}

	clubSupply.lowerClass = Math.trunc(sjv.club * sjv.clubSP * lowerClassClubRatio);
	clubSupply.middleClass = Math.trunc(sjv.club * sjv.clubSP * middleClassClubRatio);

	// Brothel or street whore sex supply
	if (lowerClassSexDemand < sjv.brothel.lowerClass) {
		App.EndWeek.saVars.whorePriceAdjustment.lowerClass = Math.max(Math.pow(lowerClassSexDemand / (sjv.brothel.lowerClass + V.NPCSexSupply.lowerClass), 1.513), 0.3);
		lowerClassSexDemand = 0; // This accounts for people having too much choice and getting more picky how they spend their money
	} else {
		App.EndWeek.saVars.whorePriceAdjustment.lowerClass = Math.pow(lowerClassSexDemand / (sjv.brothel.lowerClass + V.NPCSexSupply.lowerClass), 0.5);
		lowerClassSexDemand -= sjv.brothel.lowerClass; // People are willing to pay more for a scarce good, but within reason
	}
	if (middleClassSexDemand < sjv.brothel.middleClass) {
		App.EndWeek.saVars.whorePriceAdjustment.middleClass = Math.max(Math.pow((middleClassSexDemand * 1.1) / (sjv.brothel.middleClass + V.NPCSexSupply.middleClass), 1.513), 0.33);
		middleClassSexDemand = 0;
	} else {
		App.EndWeek.saVars.whorePriceAdjustment.middleClass = Math.pow((middleClassSexDemand * 1.1) / (sjv.brothel.middleClass + V.NPCSexSupply.middleClass), 0.5);
		middleClassSexDemand -= sjv.brothel.middleClass;
	}
	if (upperClassSexDemand < sjv.brothel.upperClass) {
		App.EndWeek.saVars.whorePriceAdjustment.upperClass = Math.max(Math.pow((upperClassSexDemand * 1.21) / (sjv.brothel.upperClass + V.NPCSexSupply.upperClass), 1.513), 0.363);
		upperClassSexDemand = 0;
	} else {
		App.EndWeek.saVars.whorePriceAdjustment.upperClass = Math.pow((upperClassSexDemand * 1.21) / (sjv.brothel.upperClass + V.NPCSexSupply.upperClass), 0.5);
		upperClassSexDemand -= sjv.brothel.upperClass;
	}
	if (topClassSexDemand < sjv.brothel.topClass) {
		App.EndWeek.saVars.whorePriceAdjustment.topClass = Math.max(Math.pow((topClassSexDemand * 1.331) / (sjv.brothel.topClass + V.NPCSexSupply.topClass), 1.513), 0.3993);
		topClassSexDemand = 0;
	} else {
		App.EndWeek.saVars.whorePriceAdjustment.topClass = Math.pow((topClassSexDemand * 1.331) / (sjv.brothel.topClass + V.NPCSexSupply.topClass), 0.5);
		topClassSexDemand -= sjv.brothel.topClass;
	}

	// Price for sex in the arcade or at a glory hole
	if (arcology.FSPaternalist !== "unset" && arcology.FSPaternalist > 0) {
		arcadeDemand = Math.max((arcadeDemandDeg + lowerClassSexDemand) * (1 - arcology.FSPaternalist / 50), 0); // Paternalists sap demand for the arcade
	} else {
		arcadeDemand = arcadeDemandDeg + lowerClassSexDemand;
	}
	if (sjv.arcade + V.NPCSexSupply.lowerClass > 0) {
		V.arcadePrice = Math.clamp(Math.pow((arcadeDemand / (sjv.arcade + V.NPCSexSupply.lowerClass)), 0.5), 0.5, 3); // This calculates the price charged per fuck
	} else {
		V.arcadePrice = 0.5; // minimum price no matter what
	}

	// Arcade usage beyond degradationist demand
	if (sjv.arcade > arcadeDemandDeg) {
		sjv.arcade -= arcadeDemandDeg;
	} else {
		sjv.arcade = 0;
	}
	if (sjv.arcade > lowerClassSexDemand) {
		lowerClassSexDemand = 0;
	} else {
		lowerClassSexDemand -= sjv.arcade;
	}
	arcadeSupply.lowerClass += sjv.arcade;

	// NPC sex supply
	V.NPCSexSupply = NPCSexSupply(lowerClassSexDemand, lowerClassSexDemandRef, middleClassSexDemand, middleClassSexDemandRef, upperClassSexDemand, upperClassSexDemandRef, topClassSexDemand, topClassSexDemandRef);
	V.NPCMarketShare.lowerClass = Math.trunc((V.NPCSexSupply.lowerClass * 1000) / lowerClassSexDemandRef);
	V.NPCMarketShare.middleClass = Math.trunc((V.NPCSexSupply.middleClass * 1000) / middleClassSexDemandRef);
	V.NPCMarketShare.upperClass = Math.trunc((V.NPCSexSupply.upperClass * 1000) / upperClassSexDemandRef);
	V.NPCMarketShare.topClass = Math.trunc((V.NPCSexSupply.topClass * 1000) / topClassSexDemandRef);
	if (lowerClassSexDemand < V.NPCSexSupply.lowerClass) {
		lowerClassSexDemand = 0;
	} else {
		lowerClassSexDemand -= V.NPCSexSupply.lowerClass;
	}

	if (middleClassSexDemand < V.NPCSexSupply.middleClass) {
		middleClassSexDemand = 0;
	} else {
		middleClassSexDemand -= V.NPCSexSupply.middleClass;
	}

	if (upperClassSexDemand < V.NPCSexSupply.upperClass) {
		upperClassSexDemand = 0;
	} else {
		upperClassSexDemand -= V.NPCSexSupply.upperClass;
	}

	if (topClassSexDemand < V.NPCSexSupply.topClass) {
		topClassSexDemand = 0;
	} else {
		topClassSexDemand -= V.NPCSexSupply.topClass;
	}

	if ((V.cheatMode === 1) || (V.debugMode === 1)) {
		_printDebugInfo(renderContainer);
	}

	// Sexual satisfaction effects
	V.sexDemandResult.lowerClass = Math.trunc(((V.NPCSexSupply.lowerClass + sjv.brothel.lowerClass + clubSupply.lowerClass + arcadeSupply.lowerClass) * 1000) / lowerClassSexDemandRef);
	V.sexDemandResult.middleClass = Math.trunc(((V.NPCSexSupply.middleClass + sjv.brothel.middleClass + clubSupply.middleClass + arcadeSupply.middleClass) * 1000) / middleClassSexDemandRef);
	V.sexDemandResult.upperClass = Math.trunc(((V.NPCSexSupply.upperClass + sjv.brothel.upperClass + arcadeSupply.upperClass) * 1000) / upperClassSexDemandRef);
	V.sexDemandResult.topClass = Math.trunc(((V.NPCSexSupply.topClass + sjv.brothel.topClass) * 1000) / topClassSexDemandRef);

	if (V.sexDemandResult.lowerClass < 400) { // You are providing < 40% of their desired amount of sex
		if (V.week <= 30) {
			V.classSatisfied.lowerClass = 0;
		} else {
			V.classSatisfied.lowerClass = -1;
		}
	} else if (V.sexDemandResult.lowerClass < 600) { // You are providing between 40 and 60% of their desired amount of sex
		if (V.week <= 40) {
			V.classSatisfied.lowerClass = 1;
		} else if (V.week <= 60) {
			V.classSatisfied.lowerClass = 0;
		} else {
			V.classSatisfied.lowerClass = -1;
		}
	} else if (V.sexDemandResult.lowerClass < 800) { // You are providing between 60 and 80% of their desired amount of sex
		if (V.week <= 50) {
			V.classSatisfied.lowerClass = 1;
		} else {
			V.classSatisfied.lowerClass = 0;
		}
	} else if (V.sexDemandResult.lowerClass === 1000) { // You are providing 100% or more of their desired amount of sex
		V.classSatisfied.lowerClass = 2;
	} else { // You are providing > 80% of their desired amount of sex
		V.classSatisfied.lowerClass = 1;
	}

	if (V.sexDemandResult.middleClass < 400) { // You are providing < 40% of their desired amount of sex
		if (V.week <= 30) {
			V.classSatisfied.middleClass = 0;
		} else {
			V.classSatisfied.middleClass = -1;
		}
	} else if (V.sexDemandResult.middleClass < 600) { // You are providing between 40 and 60% of their desired amount of sex
		if (V.week <= 40) {
			V.classSatisfied.middleClass = 1;
		} else if (V.week <= 60) {
			V.classSatisfied.middleClass = 0;
		} else {
			V.classSatisfied.middleClass = -1;
		}
	} else if (V.sexDemandResult.middleClass < 800) { // You are providing between 60 and 80% of their desired amount of sex
		if (V.week <= 50) {
			V.classSatisfied.middleClass = 1;
		} else {
			V.classSatisfied.middleClass = 0;
		}
	} else if (V.sexDemandResult.middleClass === 1000) { // You are providing 100% or more of their desired amount of sex
		V.classSatisfied.middleClass = 2;
	} else { // You are providing > 80% of their desired amount of sex
		V.classSatisfied.middleClass = 1;
	}

	if (V.sexDemandResult.upperClass < 400) { // You are providing < 40% of their desired amount of sex
		if (V.week <= 30) {
			V.classSatisfied.upperClass = 0;
		} else {
			V.classSatisfied.upperClass = -1;
		}
	} else if (V.sexDemandResult.upperClass < 600) { // You are providing between 40 and 60% of their desired amount of sex
		if (V.week <= 40) {
			V.classSatisfied.upperClass = 1;
		} else if (V.week <= 60) {
			V.classSatisfied.upperClass = 0;
		} else {
			V.classSatisfied.upperClass = -1;
		}
	} else if (V.sexDemandResult.upperClass < 800) { // You are providing between 60 and 80% of their desired amount of sex
		if (V.week <= 50) {
			V.classSatisfied.upperClass = 1;
		} else {
			V.classSatisfied.upperClass = 0;
		}
	} else if (V.sexDemandResult.upperClass === 1000) { // You are providing 100% or more of their desired amount of sex
		V.classSatisfied.upperClass = 2;
	} else { // You are providing > 80% of their desired amount of sex
		V.classSatisfied.upperClass = 1;
	}

	if (V.sexDemandResult.topClass < 400) { // You are providing < 40% of their desired amount of sex
		if (V.week <= 30) {
			V.classSatisfied.topClass = 0;
		} else {
			V.classSatisfied.topClass = -1;
		}
	} else if (V.sexDemandResult.topClass < 600) { // You are providing between 40 and 60% of their desired amount of sex
		if (V.week <= 40) {
			V.classSatisfied.topClass = 1;
		} else if (V.week <= 60) {
			V.classSatisfied.topClass = 0;
		} else {
			V.classSatisfied.topClass = -1;
		}
	} else if (V.sexDemandResult.topClass < 800) { // You are providing between 60 and 80% of their desired amount of sex
		if (V.week <= 50) {
			V.classSatisfied.topClass = 1;
		} else {
			V.classSatisfied.topClass = 0;
		}
	} else if (V.sexDemandResult.topClass === 1000) { // You are providing 100% or more of their desired amount of sex
		V.classSatisfied.topClass = 2;
	} else { // You are providing > 80% of their desired amount of sex
		V.classSatisfied.topClass = 1;
	}

	/**
	 * @param {ParentNode} c
	 */
	function _printDebugInfo(c) {
		c.append(document.createElement("br"));
		c.append("Slave services supply and demand this week:");

		const table = App.UI.DOM.appendNewElement("table", c, null, "finance");
		table.style.border = "1";
		const titleRow = App.UI.DOM.appendNewElement("tr", table);
		titleRow.style.textAlign = "center";
		const tcl = App.UI.DOM.appendNewElement("th", titleRow, "Client's class");
		tcl.style.textAlign = "right";
		App.UI.DOM.appendNewElement("th", titleRow, "Lower");
		App.UI.DOM.appendNewElement("th", titleRow, "Middle");
		App.UI.DOM.appendNewElement("th", titleRow, "Upper");
		App.UI.DOM.appendNewElement("th", titleRow, "Top");

		/**
		 *
		 * @param {string} caption
		 * @param {number} [lower]
		 * @param {number} [middle]
		 * @param {number} [upper]
		 * @param {number} [top]
		 */
		function numericRow(caption, lower, middle, upper, top) {
			const row = App.UI.DOM.appendNewElement("tr", table);
			row.align = "right";
			App.UI.DOM.appendNewElement("td", row, caption);
			App.UI.DOM.appendNewElement("td", row, lower == null ? "N/A" : commaNum(lower));
			App.UI.DOM.appendNewElement("td", row, middle == null ? "N/A" : commaNum(middle));
			App.UI.DOM.appendNewElement("td", row, upper == null ? "N/A" : commaNum(upper));
			App.UI.DOM.appendNewElement("td", row, top == null ? "N/A" : commaNum(top));
		}

		numericRow("Demand", lowerClassSexDemandRef, middleClassSexDemandRef, upperClassSexDemandRef, topClassSexDemandRef);
		numericRow("Brothel Supply", sjv.brothel.lowerClass, sjv.brothel.middleClass, sjv.brothel.upperClass, sjv.brothel.topClass);
		if (clubSupply.lowerClass || clubSupply.middleClass) {
			numericRow("Club Supply", clubSupply.lowerClass, clubSupply.middleClass);
		}
		if (arcadeSupply.lowerClass || arcadeSupply.middleClass || arcadeSupply.upperClass) {
			numericRow("Arcade Supply", arcadeSupply.lowerClass, arcadeSupply.middleClass, arcadeSupply.upperClass);
		}
		numericRow("NPC Supply", V.NPCSexSupply.lowerClass, V.NPCSexSupply.middleClass, V.NPCSexSupply.upperClass, V.NPCSexSupply.topClass);
		numericRow("Unsatisfied", lowerClassSexDemand, middleClassSexDemand, upperClassSexDemand, topClassSexDemand);
	}
};


code:
App.EndWeek.slaveAssignmentReport = function() {
	const facilities = App.Entity.facilities;
	const arcology = V.arcologies[0];

	// V.nextLink = "Economics";
	// V.nextButton = "Continue";

	const initialEmployeesCount = App.Utils.countFacilityWorkers(); // will be used to show differences after all the assignment changes
	const initialPenthouseTotalEmployeesCount = _countPenthousePopulation();

	/* Initialize global SA variables */
	App.EndWeek.saVars = new App.EndWeek.SASharedVariables();

	/* Spa room */
	if (facilities.spa.established) {
		V.spaSpots = (facilities.spa.capacity - App.Entity.facilities.spa.employeesIDs().size) * 20;
		if (S.Attendant) {
			V.spaSpots = Math.trunc(V.spaSpots * (1 + (S.Attendant.skill.attendant / 400))); // A skilled attendant improves available space by 25%
		}
	}

	/** silent pass for any reassignments before generating reports */
	for (const slave of V.slaves) {
		if (slave.choosesOwnAssignment === 1) {
			App.SlaveAssignment.choosesOwnJob(slave);
		}
	}

	const res = document.createDocumentFragment();

	App.EndWeek.computeSexualServicesModel(res);

	res.appendChild(document.createElement("br"));
	res.appendChild(document.createElement("br"));

	const skillsToTest = {
		whoring: "whore",
		entertainment: "entertain",
		vaginal: "vaginal",
		anal: "anal",
		oral: "oral"
	};

	let slavesWithWorkingDicks = 0;
	for (const slave of V.slaves) {
		for (const sk in skillsToTest) {
			if (_.isNil(slave.skill[sk])) {
				_printSlaveError(`Reset bad ${skillsToTest[sk]} skill`, slave);
				slave.skill[sk] = 0;
			}
		}

		if ((V.seeDicks > 0) && canPenetrate(slave) && App.Utils.hasNonassignmentSex(slave)) {
			App.EndWeek.saVars.averageDick += slave.dick;
			slavesWithWorkingDicks++;
		}

		if (slave.lactation === 1) {
			slave.lactationDuration--;
		}

		_ensureEmployeeMeetsJobRequirements(slave); // this can fire special slaves

		switch (slave.assignment) {
			case Job.HEADGIRLSUITE:
				if (slave.devotion >= random(-30, 20)) {
					App.EndWeek.saVars.HGSlaveSuccess = true;
					App.EndWeek.saVars.HGEnergy++;
				}
				break;
			case Job.CONCUBINE:
			case Job.MASTERSUITE:
			case Job.FUCKTOY:
				V.fuckSlaves++;
				break;
		}

		if (slave.bellyPain !== 0) {
			slave.bellyPain = 0;
		}

		/* preg speed and advance*/

		if (slave.preg > 0) {
			let pregSpeed = 1; // base speed is normal
			if (slave.pregControl === "slow gestation") {
				pregSpeed = 0.25;
			} else if (slave.pregControl === "speed up") {
				pregSpeed = 2;
			}

			if (slave.broodmother === 1 && slave.broodmotherOnHold !== 1) { /* broodmother advance block */
				if ((V.week / slave.broodmotherFetuses === Math.round(V.week / slave.broodmotherFetuses)) && slave.broodmotherFetuses < 1) {
					// one fetus in few week - selection and adding
					WombImpregnate(slave, 1, slave.pregSource, 0);
				} else {
					// one or more fetuses in one week
					WombImpregnate(slave, Math.floor(slave.broodmotherFetuses), slave.pregSource, 0); /* really 0, it's will be advanced right few lines down.*/
				}
				if (slave.ovaryAge >= 47) {
					slave.broodmotherOnHold = 1;
					slave.broodmotherCountDown = 37 - WombMinPreg(slave);
				}
			}

			WombProgress(slave, pregSpeed, 1); /* drugs can affect speed of gestation, but not a real time */

			slave.pregKnown = 1;
			slave.pregWeek++;
		}
		if (slave.pregWeek < 0) { // postpartum state
			slave.pregWeek++;
		}

		SetBellySize(slave); /* here will be also set through WombGetVolume .bellyPreg, .pregType, to current values. */

		/* end of preg speed and advance*/

		/* set up sexual need */
		if (slave.devotion >= -50) {
			if (slave.energy > 20) {
				if (slave.physicalAge < slave.pubertyAgeXY && slave.genes === "XY" && slave.energy <= 80) {
					slave.need = slave.energy / 3;
				} else if (slave.physicalAge < slave.pubertyAgeXX && slave.genes === "XX" && slave.energy <= 80) {
					slave.need = slave.energy / 3;
				} else if (slave.physicalAge < 50) {
					slave.need = slave.energy;
				} else {
					slave.need = slave.energy / 5;
				}
				if (slave.balls > 0 && slave.pubertyXY === 1 && slave.physicalAge <= (slave.pubertyAgeXY + 1) && (slave.physicalAge > slave.pubertyAgeXY) && slave.physicalAge < 18) {
					slave.need = (slave.need * 1.25);
				}
				if ((slave.ovaries === 1 || slave.mpreg === 1) && slave.pubertyXX === 1 && slave.physicalAge <= (slave.pubertyAgeXX + 1) && (slave.physicalAge > slave.pubertyAgeXX) && slave.physicalAge < 18) {
					slave.need = (slave.need * 1.25);
				}
				if (slave.diet === "fertility") {
					slave.need += 10;
				}
				if (slave.aphrodisiacs === -1) {
					slave.need = (slave.need * 0.5);
				} else if (slave.aphrodisiacs === 1) {
					slave.need = (slave.need * 1.5);
				} else if (slave.aphrodisiacs === 2) {
					slave.need = (slave.need * 2);
				}
				poorHealthNeedReduction(slave);
				slave.need = Math.round(slave.need);
				slave.needCap = slave.need;
			}
		}

		if (slave.fetish === "mindbroken" && slave.relationship === -3) {
			if (slave.kindness > 0) {
				slave.kindness--;
			}
		}

		if (slave.assignment === Job.AGENT || slave.assignment === Job.AGENTPARTNER) {
			App.SlaveAssignment.agent(slave);
		}
	} // for (const slave of V.slaves)

	if (V.HeadGirlID !== 0) {
		App.EndWeek.saVars.HGEnergy++;
		const slave = slaveStateById(V.HeadGirlID);
		if (V.personalAttention === PersonalAttention.SUPPORTHG && V.PC.health.shortDamage < 30) {
			App.EndWeek.saVars.HGEnergy++;
			if (slave.trust > 95) {
				App.EndWeek.saVars.HGEnergy++;
			}
		}
		if (arcology.FSChineseRevivalistLaw === 1) {
			App.EndWeek.saVars.HGEnergy++;
		}
		if (slaveResting(slave)) {
			App.EndWeek.saVars.HGEnergy = 0;
		}
		if (canAchieveErection(slave)) {
			App.EndWeek.saVars.HGCum = 2 + Math.trunc((slave.balls / 5) + (slave.energy / 95) + (slave.health.condition / 95) + (slave.devotion / 95) + (V.reproductionFormula * 5));
		}
	}

	// Stud gets off based on impregnations, so we need to check and see if they actually do anyone
	if (V.StudID) {
		const stud = S.Stud;
		App.EndWeek.saVars.StudCum = 2 + Math.trunc(((stud.balls / 5) + (stud.energy / 95) + (stud.health.condition / 95) + (stud.devotion / 95) + (V.reproductionFormula * 5) - (stud.health.tired / 25)) * restEffects(stud, 5));
		if (stud.drugs === "hyper testicle enhancement") {
			App.EndWeek.saVars.StudCum += 3;
		} else if (stud.drugs === "testicle enhancement") {
			App.EndWeek.saVars.StudCum += 1;
		}
		if (stud.diet === "cum production") {
			App.EndWeek.saVars.StudCum += 1;
		}
		let studCumLimit = App.EndWeek.saVars.StudCum;
		for (const slave of V.slaves) {
			if (studCumLimit === 0 || stud.need <= 0) {
				break;
			}
			if (canGetPregnant(slave) && canBreed(stud, slave) && slave.ID !== V.StudID) {
				stud.need -= 20;
				if (stud.fetish === "pregnancy") {
					stud.need -= 30;
				}
			}
		}
	}

	if (App.EndWeek.saVars.averageDick > 0) {
		App.EndWeek.saVars.averageDick = App.EndWeek.saVars.averageDick / slavesWithWorkingDicks;
	}
	App.EndWeek.saVars.freeSexualEnergy = V.PC.sexualEnergy - V.fuckSlaves;
	const penthouseSlaves = App.Entity.facilities.penthouse.employees().length;
	if (App.EndWeek.saVars.freeSexualEnergy > 0) {
		if (App.EndWeek.saVars.freeSexualEnergy > penthouseSlaves / 2) {
			App.EndWeek.saVars.freeSexualEnergy = 3;
		} else if (App.EndWeek.saVars.freeSexualEnergy > penthouseSlaves / 4) {
			App.EndWeek.saVars.freeSexualEnergy = 2;
		} else {
			App.EndWeek.saVars.freeSexualEnergy = 1;
		}
	}

	if (S.HeadGirl) {
		/** @type {FC.HeadGirlTrainee[][]} */
		const HGPossibleSlaves = [[], [], [], [], [], []];
		for (const slave of V.slaves) {
			if (!assignmentVisible(slave) || slave.fuckdoll === 1 || slave.ID === V.BodyguardID || slave.ID === V.HeadGirlID || slave.fetish === "mindbroken") {
				continue;
			} else if (Array.isArray(V.personalAttention) && V.personalAttention.some(p => p.ID === slave.ID)) {
				continue;
			}

			if (V.headGirlTrainsHealth && slave.health.condition < -20) {
				HGPossibleSlaves[0].push({ID: slave.ID, training: "health"});
				continue;
			}

			if (slave.health.tired < 50) {
				const hasParaphilia = (["abusive", "anal addict", "attention whore", "breast growth", "breeder", "cum addict", "malicious", "neglectful", "self hating"].includes(slave.sexualFlaw));
				if (V.headGirlTrainsParaphilias && hasParaphilia) {
					HGPossibleSlaves[1].push({ID: slave.ID, training: "paraphilia"});
					continue;
				}

				if (V.headGirlTrainsFlaws || V.headGirlSoftensFlaws) {
					if (slave.behavioralFlaw !== "none" || (slave.sexualFlaw !== "none" && !hasParaphilia)) {
						if (V.headGirlSoftensFlaws) {
							if (slave.devotion > 20) {
								if ((slave.behavioralFlaw !== "none" && slave.behavioralQuirk === "none") || (slave.sexualFlaw !== "none" && slave.sexualQuirk === "none" && !hasParaphilia)) {
									HGPossibleSlaves[3].push({ID: slave.ID, training: "soften"});
								} else {
									HGPossibleSlaves[3].push({ID: slave.ID, training: "flaw"});
								}
								continue;
							}
						} else if (V.headGirlTrainsFlaws) {
							HGPossibleSlaves[2].push({ID: slave.ID, training: "flaw"});
							continue;
						}
					}
				}

				if (V.headGirlTrainsObedience && slave.devotion <= 20 && slave.trust >= -20) {
					HGPossibleSlaves[4].push({ID: slave.ID, training: "obedience"});
					continue;
				}

				if (V.headGirlTrainsSkills) {
					if (slave.skill.oral < S.HeadGirl.skill.oral) {
						HGPossibleSlaves[5].push({ID: slave.ID, training: "oral skill"});
					} else if ((slave.skill.vaginal < S.HeadGirl.skill.vaginal) && (slave.vagina > 0) && (canDoVaginal(slave))) {
						HGPossibleSlaves[5].push({ID: slave.ID, training: "fuck skill"});
					} else if ((slave.skill.anal < S.HeadGirl.skill.anal) && (slave.anus > 0) && (canDoAnal(slave))) {
						HGPossibleSlaves[5].push({ID: slave.ID, training: "anal skill"});
					} else if (slave.skill.whoring < S.HeadGirl.skill.whoring) {
						HGPossibleSlaves[5].push({ID: slave.ID, training: "whore skill"});
					} else if ((slave.skill.entertainment < S.HeadGirl.skill.entertainment) && !isAmputee(slave)) {
						HGPossibleSlaves[5].push({ID: slave.ID, training: "entertain skill"});
					}
				}
			}
		}
		V.HGTrainSlavesIDs = HGPossibleSlaves.flatten().slice(0, App.EndWeek.saVars.HGEnergy);
	} else {
		V.HGTrainSlavesIDs = [];
	}

	/**
	 * Accordion
	 * @version 0.7RC
	 * @author 000-250-006
	 *
	 * @param array _facListArr
	 *	Multidimensional temporary array
	 *	0: The SC passage name or DOM function for the facility's report
	 *	1: A facility object, or the title of the report if there is no facility obejct
	 *	2: If there is no facility object, a truthy value indicating whether the facility exists
	 *	3: If there is no facility object, the maximum capacity of the facility
	 *
	 * @todo This is a proof of concept construct, if it works and cuts overhead, intended to create an object
	 *	for deeper use in multiple locations, including streamlining reports/facilities code to one widget
	 * @todo Figure out if this would be better as an object rather than an array for overhead
	 *	StoryInit also?
	 */

	const facListArr = [
		["Arcade Report", App.Entity.facilities.arcade],
		["Brothel Report", App.Entity.facilities.brothel],
		["Cellblock Report", App.Entity.facilities.cellblock],
		["Clinic Report", App.Entity.facilities.clinic],
		["Club Report", App.Entity.facilities.club],
		["Dairy Report", App.Entity.facilities.dairy],
		["Farmyard Report", App.Entity.facilities.farmyard],
		["Schoolroom Report", App.Entity.facilities.schoolroom],
		["Spa Report", App.Entity.facilities.spa],
		["Servants' Quarters Report", App.Entity.facilities.servantsQuarters],
		["Nursery Report", App.Entity.facilities.nursery],
		["Children Report", "Nursery Children", V.nursery, V.nurseryChildren],
		["Incubator Report", App.Entity.facilities.incubator],
		["Master Suite Report", App.Entity.facilities.masterSuite],
		["Penthouse Report", "The Penthouse"],
		[App.EndWeek.rulesAssistantReport, "Rules Assistant", V.rulesAssistantAuto], /** should be last — may reassign slaves **/
		[App.EndWeek.labReport, "Lab", V.researchLab.level]
	];

	function _getReportElementStats(ar) {
		if (typeof ar[1] === "string") {
			if (ar[1] === "The Penthouse") { // special case because we have to combine several facilities
				return {
					name: ar[1],
					established: 1,
					entriesNumberInitial: initialPenthouseTotalEmployeesCount,
					entriesNumber: _countPenthousePopulation(),
					manager: null,
					alwaysExists: 0,
				};
			} else {
				return {
					name: ar[1],
					established: ar[2],
					entriesNumberInitial: null,
					entriesNumber: ar[3],
					manager: null,
					alwaysExists: ar[3] === undefined ? 1 : 0,
				};
			}
		} else {
			return {
				name: capFirstChar(ar[1].name),
				established: ar[1].established,
				entriesNumberInitial: initialEmployeesCount[ar[1].desc.baseName],
				entriesNumber: ar[1].hostedSlaves,
				manager: ar[1].manager,
				alwaysExists: 0,
			};
		}
	}

	for (const facSubArr of facListArr) {
		// TODO: this call should be in the individual reports' slave loops so slaves don't leak data to their coworkers if someone fucks up
		App.EndWeek.saVars.nextSlave();

		const reportContent = document.createElement("div");
		if (typeof facSubArr[0] === "function") {
			reportContent.append(facSubArr[0]());
		} else if (typeof facSubArr[0] === "string") {
			App.UI.DOM.includePassage(reportContent, facSubArr[0]);
		}

		// needs to be inside the loop after the report passage to get the employees number after re-assignments
		const stats = _getReportElementStats(facSubArr);

		if (stats.established) { // Do we have one of these facilities?
			const header = document.createDocumentFragment();
			App.UI.DOM.appendNewElement("span", header, `${stats.name} Report`, "title");

			if (!stats.alwaysExists) {
				const diffNum = stats.entriesNumber - stats.entriesNumberInitial;
				const diffText = diffNum === 0 ? "" : (diffNum > 0 ? ` (+${diffNum})` : ` (${diffNum})`);
				// Display the bar with information
				if (stats.entriesNumber > 0) {
					App.UI.DOM.appendNewElement("span", header,
						`${stats.entriesNumber}${diffText} slave${stats.entriesNumber !== 1 ? "s" : ""} ${
							stats.manager && stats.manager.currentEmployee ? `and ${capFirstChar(
								stats.manager.desc.position)}` : ""
						} in ${stats.name}`, ["info", "green"]);
				} else if (stats.manager && stats.manager.currentEmployee) {
					App.UI.DOM.appendNewElement("span", header,
						`${diffText} Only ${capFirstChar(stats.manager.desc.position)} in ${stats.name}`,
						["info", "orange"]);
				} else {
					App.UI.DOM.appendNewElement("span", header, `${diffText} ${stats.name} is empty`,
						["info", "gray"]);
				}
			}

			// Is there anyone inside the facility?
			if (stats.entriesNumber > 0 || (stats.manager && stats.manager.currentEmployee) || stats.alwaysExists) {
				res.append(App.UI.DOM.accordion(header, reportContent, V.useAccordion > 0));
			} else {
				res.append(App.UI.DOM.accordion(header));
			}
		}
	}

	/* Clean up global SA variables */
	App.EndWeek.saVars = null;
	delete V.flSex; // FIXME: remove, once this is passed as a parameter to saRules

	return res;

	function _countPenthousePopulation() {
		const fs = App.Entity.facilities;
		return fs.penthouse.employeesIDs().size + fs.headGirlSuite.totalEmployeesCount + fs.armory.totalEmployeesCount;
	}

	function _printSlaveError(warning, slave) {
		const warningLine = App.UI.DOM.appendNewElement("div", res);
		App.UI.DOM.appendNewElement("span", warningLine, warning + `for ${slave.slaveName}.`, "yellow");
		warningLine.appendChild(document.createTextNode(" Report this as a bug if it reoccurs."));
	}

	/**
	 * Check key employees. Fire those who do not satisfy their job requirements
	 * @param {App.Entity.SlaveState} slave
	 */
	function _ensureEmployeeMeetsJobRequirements(slave) {
		switch (slave.assignment) {
			case Job.HEADGIRL:
				if (slave.fetish === "mindbroken") {
					_printSlaveUnassignedNote(slave, "is mindbroken");
					V.HeadGirlID = 0;
				} else if (!canTalk(slave)) {
					_printSlaveUnassignedNote(slave, "can't give slaves verbal orders");
					V.HeadGirlID = 0;
				} else if (!canWalk(slave)) {
					_printSlaveUnassignedNote(slave, "is no longer independently mobile");
					V.HeadGirlID = 0;
				} else if (!canHold(slave)) {
					_printSlaveUnassignedNote(slave, "is no longer able to handle your slaves");
					V.HeadGirlID = 0;
				} else if (!canSee(slave)) {
					_printSlaveUnassignedNote(slave, "can no longer see");
					V.HeadGirlID = 0;
				} else if (!canHear(slave)) {
					_printSlaveUnassignedNote(slave, "can no longer hear");
					V.HeadGirlID = 0;
				} else if (slave.preg > 37 && slave.broodmother === 2) {
					_printSlaveUnassignedNote(slave, "spends so much time giving birth and laboring that", `${getPronouns(slave).he} cannot effectively serve as your Head Girl any longer`);
					V.HeadGirlID = 0;
				} else if (slave.devotion <= 20) {
					_printSlaveUnassignedNote(slave, "is no longer even accepting of you");
					V.HeadGirlID = 0;
				}
				if (V.HeadGirlID === 0) {
					removeJob(slave, Job.HEADGIRL);
				}
				break;
			case Job.RECRUITER:
				V.RecruiterID = slave.ID;
				if (slave.fetish === "mindbroken") {
					_printSlaveUnassignedNote(slave, "is mindbroken");
					V.RecruiterID = 0;
				} else if (!canTalk(slave)) {
					_printSlaveUnassignedNote(slave, "can't verbally entice marks");
					V.RecruiterID = 0;
				} else if (slave.preg > 37 && slave.broodmother === 2) {
					_printSlaveUnassignedNote(slave, "spends so much time giving birth and laboring that", `${getPronouns(slave).he} cannot effectively serve as your recruiter any longer`);
					V.RecruiterID = 0;
				} else if (!canWalk(slave)) {
					_printSlaveUnassignedNote(slave, "is no longer independently mobile");
					V.RecruiterID = 0;
				} else if (!canSee(slave)) {
					_printSlaveUnassignedNote(slave, "can no longer see");
					V.RecruiterID = 0;
				} else if (!canHear(S.Recruiter)) {
					_printSlaveUnassignedNote(slave, "an no longer hear");
					V.RecruiterID = 0;
				}
				if (V.RecruiterID === 0) {
					removeJob(slave, Job.RECRUITER);
				}
				break;
			case Job.MADAM:
				if (App.EndWeek.saVars.unMadam === 1) {
					_printSlaveUnassignedNote(slave, "can't give whores verbal orders");
				} else if (App.EndWeek.saVars.unMadam === 2) {
					_printSlaveUnassignedNote(slave, "spends so much time giving birth and laboring that", `${getPronouns(slave).he} cannot effectively serve as your Madam any longer`);
				} else if (App.EndWeek.saVars.unMadam === 3) {
					_printSlaveUnassignedNote(slave, "is mindbroken");
				} else if (App.EndWeek.saVars.unMadam === 4) {
					_printSlaveUnassignedNote(slave, "is no longer independently mobile");
				} else if (App.EndWeek.saVars.unMadam === 5) {
					_printSlaveUnassignedNote(slave, "can no longer see");
				} else if (App.EndWeek.saVars.unMadam === 6) {
					_printSlaveUnassignedNote(slave, "can no longer hear");
				} else if (App.EndWeek.saVars.unMadam === 7) {
					_printSlaveUnassignedNote(slave, `can no longer handle ${getPronouns(slave).his} underlings`);
				}
				if (V.MadamID === 0) {
					removeJob(slave, Job.MADAM);
				}
				break;
			case Job.DJ:
				if (App.EndWeek.saVars.unDJ === 1) {
					_printSlaveUnassignedNote(slave, "can't speak");
				} else if (App.EndWeek.saVars.unDJ === 2) {
					_printSlaveUnassignedNote(slave, "spends so much time giving birth and laboring that", `${getPronouns(slave).he} cannot effectively serve as your DJ any longer`);
				} else if (App.EndWeek.saVars.unDJ === 3) {
					_printSlaveUnassignedNote(slave, "is mindbroken");
				} else if (App.EndWeek.saVars.unDJ === 4) {
					_printSlaveUnassignedNote(slave, "is no longer independently mobile");
				} else if (App.EndWeek.saVars.unDJ === 5) {
					_printSlaveUnassignedNote(slave, "can no longer hear");
				}
				if (V.djID === 0) {
					removeJob(slave, Job.DJ);
				}
				break;
			case Job.MILKMAID:
				if (slave.fetish === "mindbroken") {
					_printSlaveUnassignedNote(slave, "is mindbroken");
					V.MilkmaidID = 0;
				} else if (slave.preg > 37 && slave.broodmother === 2) {
					_printSlaveUnassignedNote(slave, "spends so much time giving birth and laboring that", `${getPronouns(slave).he} cannot effectively serve as your Milkmaid any longer`);
					V.MilkmaidID = 0;
				} else if (!canWalk(slave)) {
					_printSlaveUnassignedNote(slave, "is no longer independently mobile");
					V.MilkmaidID = 0;
				} else if (!canHold(slave)) {
					_printSlaveUnassignedNote(slave, "can no longer handle nor milk your slaves");
					V.MilkmaidID = 0;
				} else if (!canSee(slave)) {
					_printSlaveUnassignedNote(slave, "can no longer see");
					V.MilkmaidID = 0;
				} else if (!canHear(slave)) {
					_printSlaveUnassignedNote(slave, "can no longer hear");
					V.MilkmaidID = 0;
				}
				if (V.MilkmaidID === 0) {
					removeJob(slave, Job.MILKMAID);
				}
				break;
			case Job.FARMER:
				if (S.Farmer.fetish === "mindbroken") {
					_printSlaveUnassignedNote(slave, "is mindbroken");
					V.FarmerID = 0;
				} else if (S.Farmer.preg > 37 && S.Farmer.broodmother === 2) {
					_printSlaveUnassignedNote(slave, "spends so much time giving birth and laboring that", `${getPronouns(slave).he} cannot effectively serve as your Farmer any longer`);
					V.FarmerID = 0;
				} else if (!canWalk(S.Farmer)) {
					_printSlaveUnassignedNote(slave, "is no longer independently mobile");
					V.FarmerID = 0;
				} else if (!canHold(S.Farmer)) {
					_printSlaveUnassignedNote(slave, "can no longer grip things");
					V.FarmerID = 0;
				} else if (!canSee(S.Farmer)) {
					_printSlaveUnassignedNote(slave, "can no longer see");
					V.FarmerID = 0;
				} else if (!canHear(S.Farmer)) {
					_printSlaveUnassignedNote(slave, "can no longer hear");
					V.FarmerID = 0;
				}
				if (V.FarmerID === 0) {
					removeJob(slave, Job.FARMER);
				}
				break;
			case Job.STEWARD:
				if (!canTalk(slave)) {
					_printSlaveUnassignedNote(slave, "can't give servants verbal orders");
					V.StewardessID = 0;
				} else if (slave.preg > 37 && slave.broodmother === 2) {
					_printSlaveUnassignedNote(slave, "spends so much time giving birth and laboring that", `${getPronouns(slave).he} cannot effectively serve as your Stewardess any longer`);
					V.StewardessID = 0;
				} else if (slave.fetish === "mindbroken") {
					_printSlaveUnassignedNote(slave, "is mindbroken");
					V.StewardessID = 0;
				} else if (!canWalk(slave)) {
					_printSlaveUnassignedNote(slave, "is no longer independently mobile");
					V.StewardessID = 0;
				} else if (!canHold(slave)) {
					_printSlaveUnassignedNote(slave, `can no longer handle ${getPronouns(slave).his} underlings nor effectively clean`);
					V.StewardessID = 0;
				} else if (!canSee(slave)) {
					_printSlaveUnassignedNote(slave, "can no longer see");
					V.StewardessID = 0;
				} else if (!canHear(slave)) {
					_printSlaveUnassignedNote(slave, "can no longer hear");
					V.StewardessID = 0;
				}
				if (V.StewardessID === 0) {
					removeJob(slave, Job.STEWARD);
				}
				break;
			case Job.TEACHER:
				if (!canTalk(slave)) {
					_printSlaveUnassignedNote(slave, "can't give verbal instruction");
					V.SchoolteacherID = 0;
				} else if (slave.preg > 37 && slave.broodmother === 2) {
					_printSlaveUnassignedNote(slave, "spends so much time giving birth and laboring that", `${getPronouns(slave).he} cannot effectively serve as your Schoolteacher any longer`);
					V.SchoolteacherID = 0;
				} else if (slave.fetish === "mindbroken") {
					_printSlaveUnassignedNote(slave, "is mindbroken");
					V.SchoolteacherID = 0;
				} else if (!canSee(slave)) {
					_printSlaveUnassignedNote(slave, "can no longer see");
					V.SchoolteacherID = 0;
				} else if (!canHear(slave)) {
					_printSlaveUnassignedNote(slave, "can no longer hear");
					V.SchoolteacherID = 0;
				}
				if (V.SchoolteacherID === 0) {
					removeJob(slave, Job.TEACHER);
				}
				break;
			case Job.WARDEN:
				if (!canWalk(slave)) {
					_printSlaveUnassignedNote(slave, "is no longer independently mobile");
					V.WardenessID = 0;
				} else if (!canHold(slave)) {
					_printSlaveUnassignedNote(slave, `can no longer handle ${getPronouns(slave).his} charges`);
					V.WardenessID = 0;
				} else if (slave.preg > 37 && slave.broodmother === 2) {
					_printSlaveUnassignedNote(slave, "spends so much time giving birth and laboring that", `${getPronouns(slave).he} cannot effectively serve as your Wardeness any longer`);
					V.WardenessID = 0;
				} else if (!canSee(slave)) {
					_printSlaveUnassignedNote(slave, "can no longer see");
					V.WardenessID = 0;
				} else if (!canHear(slave)) {
					_printSlaveUnassignedNote(slave, "can no longer hear");
					V.WardenessID = 0;
				}
				if (V.WardenessID === 0) {
					removeJob(slave, Job.WARDEN);
				}
				break;
			case Job.ATTENDANT:
				if (slave.fetish === "mindbroken") {
					_printSlaveUnassignedNote(slave, "is mindbroken");
					V.AttendantID = 0;
				} else if (slave.preg > 37 && slave.broodmother === 2) {
					_printSlaveUnassignedNote(slave, "spends so much time giving birth and laboring that", `${getPronouns(slave).he} cannot effectively serve as your Attendant any longer`);
					V.AttendantID = 0;
				} else if (!canWalk(slave)) {
					_printSlaveUnassignedNote(slave, "is no longer independently mobile");
					V.AttendantID = 0;
				} else if (!canHold(slave)) {
					_printSlaveUnassignedNote(slave, `can no longer support ${getPronouns(slave).his} charges`);
					V.AttendantID = 0;
				} else if (!canHear(slave)) {
					_printSlaveUnassignedNote(slave, "can no longer hear");
					V.AttendantID = 0;
				}
				if (V.AttendantID === 0) {
					removeJob(slave, Job.ATTENDANT);
				}
				break;
			case Job.MATRON:
				if (slave.fetish === "mindbroken") {
					_printSlaveUnassignedNote(slave, "is mindbroken");
					V.MatronID = 0;
				} else if (slave.preg > 37 && slave.broodmother === 2) {
					_printSlaveUnassignedNote(slave, "spends so much time giving birth and laboring that", `${getPronouns(slave).he} cannot effectively serve as your Matron any longer`);
					V.MatronID = 0;
				} else if (!canWalk(slave)) {
					_printSlaveUnassignedNote(slave, "is no longer independently mobile");
					V.MatronID = 0;
				} else if (!canHold(slave)) {
					_printSlaveUnassignedNote(slave, "can no longer hold infants");
					V.MatronID = 0;
				} else if (!canHear(slave)) {
					_printSlaveUnassignedNote(slave, "can no longer hear");
					V.MatronID = 0;
				} else if (!canSee(slave)) {
					_printSlaveUnassignedNote(slave, "can no longer see");
					V.MatronID = 0;
				}
				if (V.MatronID === 0) {
					removeJob(slave, Job.MATRON);
				}
				break;
			case Job.NURSE:
				if (slave.fetish === "mindbroken") {
					_printSlaveUnassignedNote(slave, "is mindbroken");
					V.NurseID = 0;
				} else if (slave.preg > 37 && slave.broodmother === 2) {
					_printSlaveUnassignedNote(slave, "spends so much time giving birth and laboring that", `${getPronouns(slave).he} cannot effectively serve as your Nurse any longer`);
					V.NurseID = 0;
				} else if (!canWalk(slave)) {
					_printSlaveUnassignedNote(slave, "is no longer independently mobile");
					V.NurseID = 0;
				} else if (!canHold(slave)) {
					_printSlaveUnassignedNote(slave, "can no longer hold onto patients");
					V.NurseID = 0;
				} else if (!canSee(slave)) {
					_printSlaveUnassignedNote(slave, "can no longer see");
					V.NurseID = 0;
				} else if (!canHear(slave)) {
					_printSlaveUnassignedNote(slave, "can no longer hear");
					V.NurseID = 0;
				}
				if (V.NurseID === 0) {
					removeJob(slave, Job.NURSE);
				}
				break;
			case Job.BODYGUARD:
				if (slave.fetish === "mindbroken") {
					_printSlaveUnassignedNote(slave, "is mindbroken");
					V.BodyguardID = 0;
				} else if (!canWalk(slave)) {
					_printSlaveUnassignedNote(slave, "is no longer independently mobile");
					V.BodyguardID = 0;
				} else if (!canHold(slave)) {
					_printSlaveUnassignedNote(slave, "is no longer able to hold a weapon");
					V.BodyguardID = 0;
				} else if (slave.preg > 37 && slave.broodmother === 2) {
					_printSlaveUnassignedNote(slave, "spends so much time giving birth and laboring that", `${getPronouns(slave).he} cannot effectively serve as your bodyguard any longer`);
					V.BodyguardID = 0;
				} else if (!canSee(slave)) {
					_printSlaveUnassignedNote(slave, "can no longer see");
					V.BodyguardID = 0;
				} else if (!canHear(slave)) {
					_printSlaveUnassignedNote(slave, "can no longer hear");
					V.BodyguardID = 0;
				}
				if (V.BodyguardID === 0) {
					removeJob(slave, Job.BODYGUARD);
				}
				break;
			case Job.SUBORDINATE:
				if (slave.subTarget === -1) {
					V.StudID = slave.ID;
					if (slave.balls === 0) {
						_printSlaveUnassignedNote(slave, "no longer has sperm", null, "@Stud");
						V.StudID = 0;
					} else if (slave.ballType === "sterile") {
						_printSlaveUnassignedNote(slave, "no longer produces potent sperm", null, "@Stud");
						V.StudID = 0;
					} else if (slave.pubertyXY !== 1) {
						_printSlaveUnassignedNote(slave, "no longer produces mature sperm", null, "@Stud");
						V.StudID = 0;
					} else if (slave.vasectomy === 1) {
						_printSlaveUnassignedNote(slave, "shoots blanks due to a vasectomy", null, "@Stud");
						V.StudID = 0;
					} else if (V.universalRulesImpregnation !== "Stud") {
						_printSlaveUnassignedNote(slave, "is", "no longer needed as a Stud", "@Stud");
					}
					if (V.StudID === 0) {
						slave.subTarget = 0;
					}
				}
				break;
			case Job.AGENT:
			{
				const arc = V.arcologies.find((a) => a.leaderID === slave.ID);
				if (!arc) {
					_printSlaveUnassignedNote(slave, "is not assigned to an arcology");
					removeJob(slave, Job.AGENT);
				} else if (arc.government !== "your agent") {
					_printSlaveUnassignedNote(slave, "is assigned to an arcology that is not lead by an agent");
					removeJob(slave, Job.AGENT);
				}
				break;
			}
		}

		if (slave.ID === V.LurcherID) {
			if (!canWalk(slave)) {
				_printSlaveUnassignedNote(slave, "is no longer able to run", null, Job.LURCHER);
				V.LurcherID = 0;
			} else if (!canHold(slave)) {
				_printSlaveUnassignedNote(slave, "is no longer able to catch the hares", null, Job.LURCHER);
				V.LurcherID = 0;
			} else if (!canHear(slave) && !canSee(slave)) {
				_printSlaveUnassignedNote(slave, "is no longer able to track the hares", null, Job.LURCHER);
				V.LurcherID = 0;
			} else if (slave.bellyPreg >= 60000) {
				_printSlaveUnassignedNote(slave, "is too pregnant to run", null, Job.LURCHER);
				V.LurcherID = 0;
			}
		}

		if (V.pit && V.pit.fighterIDs.includes(slave.ID)) {
			if (!canWalk(slave)) {
				_printSlaveUnassignedNote(slave, "is no longer independently mobile",
					`and cannot fight any more. ${getPronouns(slave).He} has been removed from ${App.Entity.facilities.pit.name} roster`);
				removeJob(slave, Job.PIT, true);
			} else if (!canHold(slave)) {
				_printSlaveUnassignedNote(slave, "is no longer able to strike",
					`and cannot fight any more. ${getPronouns(slave).he} has been removed from ${App.Entity.facilities.pit.name} roster`);
				removeJob(slave, Job.PIT, true);
			}
		}
	}

	/**
	 *
	 * @param {App.Entity.SlaveState} slave
	 * @param {string} condition
	 * @param {string} [outcome]
	 * @param {string} [assignment]
	 */
	function _printSlaveUnassignedNote(slave, condition, outcome, assignment) {
		const cantServeNotes = new Map([
			[Job.HEADGIRL, "cannot serve as your Head Girl any more"],
			[Job.RECRUITER, "and cannot serve as your recruiter any more"],
			[Job.MADAM, "cannot serve as your Madam any more"],
			[Job.DJ, "cannot serve as your DJ any more"],
			[Job.MILKMAID, "cannot serve as your Milkmaid any more"],
			[Job.FARMER, "cannot serve as your Farmer any more"],
			[Job.STEWARD, "cannot serve as your Stewardess any more"],
			[Job.TEACHER, "cannot serve as your Schoolteacher any more"],
			[Job.WARDEN, "cannot serve as your Wardeness any more"],
			[Job.ATTENDANT, "cannot serve as your Attendant any more"],
			[Job.BODYGUARD, " cannot serve as your Bodyguard any more"],
			["@Stud", "cannot serve as a Stud any more"],
			[Job.NURSE, "cannot serve as your Nurse any more"],
			[Job.MATRON, "cannot serve as your Matron any more"],
			[Job.LURCHER, "cannot course as a lurcher"],
			[Job.AGENT, "cannot serve as an Agent any more"]
		]);

		const warningLine = App.UI.DOM.appendNewElement("div", res);
		App.UI.DOM.appendNewElement("span", warningLine, slave.slaveName, 'slave-name');
		warningLine.appendChild(document.createTextNode(' ' + condition + ' '));
		App.UI.DOM.appendNewElement("span", warningLine, outcome ? outcome : `and ${cantServeNotes.get(assignment || slave.assignment)}`, "yellow");
		warningLine.appendChild(document.createTextNode("."));
	}
};

/**
 * @param {App.Entity.SlaveState} slave
 * @returns {Node}
 */
App.EndWeek.favoriteIcon = function(slave) {
	if (V.favorites.includes(slave.ID)) {
		return App.UI.DOM.makeElement("span", String.fromCharCode(0xe800), ["icons", "favorite"]);
	}
	return document.createDocumentFragment();
};


code:
/**
 * Generates (and returns if not silent) a standard slave report
 * @param {App.Entity.SlaveState} slave
 * @param {boolean} silent
 * @returns {HTMLElement|null}
 */
App.SlaveAssignment.standardSlaveReport = function(slave, silent=false) {
	const clothes = App.SlaveAssignment.choosesOwnClothes(slave);

	tired(slave);

	const rules = App.UI.DOM.renderPassage("SA rules");
	const diet = App.SlaveAssignment.diet(slave);
	const ltEffects = App.SlaveAssignment.longTermEffects(slave);
	const drugs = App.SlaveAssignment.drugs(slave);
	const relationships = App.SlaveAssignment.relationships(slave);
	const rivalries = App.SlaveAssignment.rivalries(slave);
	const devotion = App.SlaveAssignment.devotion(slave);

	if (!silent) {
		const content = App.UI.DOM.makeElement("div", '', "indent");

		$(content).append(clothes, rules, diet, ltEffects, drugs, relationships, rivalries, `<div class="indent">${devotion}</div>`);

		return content;
	}
};


code:
/** base class for class-based events */
App.Events.BaseEvent = class BaseEvent {
	/** build a new event
	 *  parameters are necessary for serialization (so that saving with the event active will work correctly) and should not normally be used directly
	 *  child classes will inherit this implementation automatically, and should not normally need their own constructor implementation
	 * @param {Array<number>} [actors]
	 * @param {object} [params]
	 */
	constructor(actors, params) {
		/** @member {Array<number>} actors - a list of IDs for the actors participating in this event. */
		this.actors = actors || [];
		/** @member {object} params - a set of parameters to pass to the event. */
		this.params = params || {};
	}

	/** Event predicates determine whether the event will occur or not.
	 * @callback eventPredicate
	 * @returns {boolean}
	 */
	/** generate an array of zero or more predicates which must all return true in order for the event to be valid.
	 *  lambda predicates may add properties to {@link App.Events.BaseEvent#params the params member} in order to pass information on to the event.
	 *  child classes should implement this.
	 * @returns {Array<eventPredicate>}
	 */
	eventPrerequisites() {
		return [];
	}

	/** Actor predicates determine whether an actor is qualified for an event or not.
	 * @callback actorPredicate
	 * @param {App.Entity.SlaveState} slave
	 * @returns {boolean}
	 */
	/** generate an array of zero or more arrays, each corresponding to an actor in the event, which contain zero or more predicates which must be satisfied by the actor.
	 *  child classes should implement this.
	 * @returns {Array<Array<actorPredicate>>}
	 */
	actorPrerequisites() {
		return [];
	}

	/** get the choice weight for the event. Weights should be integral; higher weight means higher probability of selection.
	 * @returns {number}
	 */
	get weight() {
		return 1;
	}

	/** run the event and attach DOM output to the event passage.
	 *  child classes must implement this.
	 * @param {DocumentFragment} node - Document fragment which event output should be attached to
	 */
	execute(node) {
	}

	/** clone the event (needed for serialization).
	 *  default implementation should suffice for child classes */
	clone() {
		let c = {};
		Reflect.setPrototypeOf(c, Reflect.getPrototypeOf(this));
		deepAssign(c, this);
		return c;
	}

	/** serialize the event instance so it persists through saves.
	 *  default implementation should suffice for child classes assigned to App.Events */
	toJSON() {
		return JSON.reviveWrapper(`new App.Events.${this.constructor.name}(${JSON.stringify(this.actors)},${JSON.stringify(this.params)})`);
	}

	/** get the event's (human-readable) name. must not include ":".
	 *  default implementation should generally suffice (though we might want to de-camelcase here or something).
	 * @returns {string}
	 */
	get eventName() {
		return this.constructor.name;
	}

	/** build the actual list of actors that will be involved in this event.
	 *  default implementation should suffice for child classes with a fixed number of actors; may be overridden for events with variable actor count.
	 * @param {App.Entity.SlaveState} firstActor - if non-null, the first actor should be this slave (fail if she is not qualified)
	 * @returns {boolean} - return false if sufficient qualified actors could not be found (cancel the event)
	 */
	castActors(firstActor) {
		const prereqs = this.actorPrerequisites();

		let i = 0;
		if (firstActor && prereqs.length > 0) {
			if (prereqs[0].every(p => p(firstActor))) {
				this.actors.push(firstActor.ID);
			} else {
				return false; // preselected first actor was not qualified
			}
			i = 1; // first actor is cast
		}

		for (; i < prereqs.length; ++i) {
			const qualified = V.slaves.filter(s => !this.actors.includes(s.ID) && prereqs[i].every(p => p(s)));
			if (qualified.length === 0) {
				return false; // a required actor was not found
			}
			this.actors.push(qualified.pluck().ID);
		}

		return true; // all actors cast
	}
};

/** This is a trivial event for use as an example. */
App.Events.TestEvent = class TestEvent extends App.Events.BaseEvent {
	constructor(actors, params) {
		super(actors, params);
	}

	eventPrerequisites() {
		return [];
	}

	actorPrerequisites() {
		return [
			[] // one actor, no requirements
		];
	}

	execute(node) {
		let [eventSlave] = this.actors.map(a => getSlave(a)); // mapped deconstruction of actors into local slave variables
		App.Events.drawEventArt(node, eventSlave);
		node.appendChild(document.createTextNode(`This test event for ${eventSlave.slaveName} was successful.`));
	}
};


code:
/*  Events are organized into several files and folders								*/
/*	genericPlotEvents.tw															*/
/*	PESS.tw: Player Event, Single Slave												*/
/*	PETS.tw: Player Event, Two Slaves												*/
/*	RECI.tw: Random Event, Check In													*/
/*	REFI.tw: Random Event, Fetish Interest											*/
/*	REFS.tw: Random Event, Future Societies											*/
/*	RESS.tw: Random Event, Single Slave												*/
/*	RESSTR.tw: Random Event, Single Slave (Test Realm, for debugging events)		*/
/*	RETS.tw: Random Event, Two Slaves												*/
/*																					*/
/* Events can also be in a dedicated *.tw file, formatted as follows:				*/
/*	jeXXXXX.tw: Justice Event														*/
/*	pXXXXXX.tw: Player event														*/
/*	peXXXXX.tw: Player Event focused on a slave										*/
/*	reXXXXX.tw: Random Event														*/
/*	resXXXX.tw: Random Event, School												*/
/*	seXXXXX.tw: Slave Event, focuses on slaves coming or going						*/
/*	securityForceXXXXX.tw: Special (Security) Force event							*/

path: src/events/About Events.txt