R21/R22: 补齐公网安全文档 试点观测与最小导入脚本

这个提交包含在:
2026-03-22 08:34:26 +08:00
父节点 3f9738ac9c
当前提交 a878645007
共修改 9 个文件,包含 572 行新增和 0 行删除
@@ -125,6 +125,9 @@ function renderMarkdown(report) {
lines.push(`- 本周周报提交:${report.weeklySubmittedCount}/${report.activeEmployeeCount}(${report.weeklySubmissionRate}%)`);
lines.push(`- 今日状态更新:${report.presenceUpdatedCount}/${report.activeEmployeeCount}(${report.presenceCoverageRate}%)`);
lines.push(`- 当前公告数:${report.announcementCount}`);
lines.push(`- 活跃项目数:${report.activeProjectCount}`);
lines.push(`- 近 7 天有任务更新的项目:${report.projectTaskUpdated7dCount}/${report.activeProjectCount}(${report.projectTaskUpdateRate}%)`);
lines.push(`- 里程碑按期完成:${report.milestoneOnTimeCount}/${report.milestoneDueCount}(${report.milestoneOnTimeRate}%)`);
lines.push(`- 入职中:${report.onboardingCount}`);
lines.push(`- 离职中:${report.offboardingCount}`);
lines.push(`- 资产待归还:${report.assetReturnPendingCount}`);
@@ -135,6 +138,7 @@ function renderMarkdown(report) {
lines.push("## 建议动作");
lines.push("- 将本快照内容填入 `deploy/trialrun/observation-template.md` 对应周次。");
lines.push("- 若指标未达标,优先通过 Portal、公告与站内通知补提醒,不新增外部表格。");
lines.push("- 多平台重复录入次数和表格替代率仍建议在 observation template 中人工补记。");
return `${lines.join("\n")}\n`;
}
@@ -162,6 +166,9 @@ async function main() {
onboardingCount,
offboardingCount,
assetReturnPendingCount,
activeProjects,
updatedProjectTasks,
dueMilestones,
] = await Promise.all([
prisma.authSession.findMany({
where: {
@@ -185,6 +192,25 @@ async function main() {
prisma.employee.count({ where: { employment_status: "onboarding", status: "active" } }),
prisma.employee.count({ where: { employment_status: "offboarding" } }),
prisma.assetCard.count({ where: { assignee: { is: { employment_status: "offboarding" } } } }),
prisma.project.findMany({
where: { status: { in: ["draft", "active", "paused"] } },
select: { id: true },
}),
prisma.projectTask.findMany({
where: {
updated_at: { gte: since },
project: { status: { in: ["draft", "active", "paused"] } },
},
distinct: ["project_id"],
select: { project_id: true },
}),
prisma.projectMilestone.findMany({
where: {
due_date: { not: null },
project: { status: { in: ["draft", "active", "paused"] } },
},
select: { due_date: true, completed_at: true, status: true },
}),
]);
let presenceUpdatedCount = 0;
@@ -195,6 +221,15 @@ async function main() {
}
}
const milestoneOnTimeCount = dueMilestones.filter((item) => {
if (item.status !== "done" || !item.completed_at || !item.due_date) {
return false;
}
const dueEnd = new Date(item.due_date);
dueEnd.setUTCHours(23, 59, 59, 999);
return item.completed_at.getTime() <= dueEnd.getTime();
}).length;
const report = {
generatedAt: new Date().toISOString(),
date: dateString,
@@ -210,6 +245,14 @@ async function main() {
presenceMissingCount: Math.max(0, activeEmployeeCount - presenceUpdatedCount),
presenceCoverageRate: toPercent(presenceUpdatedCount, activeEmployeeCount),
announcementCount,
activeProjectCount: activeProjects.length,
projectTaskUpdated7dCount: updatedProjectTasks.length,
projectTaskUpdateRate: toPercent(updatedProjectTasks.length, activeProjects.length),
milestoneDueCount: dueMilestones.length,
milestoneOnTimeCount,
milestoneOnTimeRate: toPercent(milestoneOnTimeCount, dueMilestones.length),
duplicateEntryCount: null,
spreadsheetReplacementRate: null,
onboardingCount,
offboardingCount,
assetReturnPendingCount,