排班工具

This commit is contained in:
2023-02-16 03:01:04 +08:00
parent 3ec781cc76
commit 3b23b09909
8 changed files with 675 additions and 21 deletions

101
pages/api/tool.ts Normal file
View File

@@ -0,0 +1,101 @@
import type { NextApiRequest, NextApiResponse } from "next";
import fs from "fs";
import { store, html } from "@/store";
import glob from "glob";
import { promisify } from "util";
const g = promisify(glob);
const read = promisify(fs.readFile);
// 索引与工时
const indexToHour: Record<string, number> = JSON.parse(
fs.readFileSync("./hours.json", "utf8")
).selections;
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Record<string, string>>
) {
// 读入全部json文件
// users: {姓名: {坐标: 权重}}
const users: Record<string, Record<string, number>> = {};
const files = await g("./json/*.json");
for (const file of files) {
const jsonStr = await read(file, "utf8");
const json: {
user: string;
selections: Record<string, number>;
} = JSON.parse(jsonStr);
// Normalization
// 使用 标准分数 算法 z = (x - mean) / std
const { selections } = json;
const nums = Object.values(selections);
const mean = nums.reduce((a, b) => a + b) / nums.length;
const std = Math.sqrt(
nums.map((x) => Math.pow(x - mean, 2)).reduce((a, b) => a + b) /
nums.length
);
const normalizedNums = nums.map((x) => (std === 0 ? 0 : (x - mean) / std));
for (const index in selections) {
selections[index] = std === 0 ? 0 : (selections[index] - mean) / std;
}
users[json.user] = selections;
}
// 计算每一个格子有多少人选择
// counts: {坐标: {姓名: 权重}}
const counts: Record<string, Record<string, Record<string, number>>> = {};
for (const user in users) {
for (const select in users[user]) {
if (counts[select] === undefined) counts[select] = {};
counts[select][user] = users[user];
}
}
// 构造返回数据, key 为 坐标value 为姓名
const resp: Record<string, string> = {};
// 找到选择数量最少的格子
// sortable: [[坐标, 选择人数]]
// 如果遇到选择数量相同的格子,随机打乱顺序进行迭代
const sortable: [string, number][] = [];
for (const index in counts) {
sortable.push([index, Object.keys(counts[index]).length]);
}
sortable.sort((a, b) => a[1] - b[1] || Math.random() - 0.5);
// 记录这个人的出现次数,还有这个人的工时总数
const hoursCount: Record<string, number> = {};
for (const [smallestIndex, _] of sortable) {
// console.log("smallestIndex", smallestIndex);
// console.log("cell", counts[smallestIndex]);
// 找到格子里权重最低的人选 [姓名, 小时数, 出现次数, 权重]
const weightList: [string, number, number][] = [];
for (const user in counts[smallestIndex]) {
weightList.push([
user,
hoursCount[user] || 0,
counts[smallestIndex][user][smallestIndex],
]);
}
weightList.sort((a, b) => a[1] - b[1] || a[2] - b[2]);
// console.log("weightList", weightList);
const theChoosenUser = weightList[0][0];
// console.log("theChoosenUser", theChoosenUser);
// 记录结果
resp[smallestIndex] = theChoosenUser;
hoursCount[theChoosenUser] =
(hoursCount[theChoosenUser] || 0) + indexToHour[smallestIndex];
}
// console.log("hoursCount", hoursCount);
const sortedHoursCount: [string, number][] = [];
for (const user in hoursCount) {
sortedHoursCount.push([user, hoursCount[user]]);
}
sortedHoursCount.sort((a, b) => a[1] - b[1]);
console.log("sortedHoursCount", sortedHoursCount);
console.log("sortedHoursCount.length", sortedHoursCount.length);
res.status(200).json(resp);
}