auron
Version:
Interact with your ATProto labeler from your terminal
232 lines (208 loc) • 7.42 kB
text/typescript
import EventEmitter from "events";
import { withLoader } from "../utils/loader";
import { database } from "../services/db";
import { writeFile } from "node:fs/promises";
type ActionTimeMetric = {
item: number;
time: number;
};
type ActionTimeMetricGroup = {
human: ActionTimeMetric;
automated: ActionTimeMetric;
unhandled: number;
};
const automods = [
"did:plc:fokhsjjl5opb2hrkcmc2swfi",
"did:plc:znoll2wxc7jt736ulwzlxiqk",
"did:plc:ar7c4by46qjdydhdevvrndac",
];
// Define histogram buckets (time in minutes)
const timeBins = [
{ label: "0-10 secs", min: 0, max: 10 },
{ label: "10-30 secs", min: 10, max: 30 },
{ label: "30-60 secs", min: 30, max: 60 },
{ label: "1-5 mins", min: 60, max: 300 },
{ label: "5-10 mins", min: 300, max: 600 },
{ label: "10-30 mins", min: 600, max: 1800 },
{ label: "30-60 mins", min: 1800, max: 3600 },
{ label: "1-3 hrs", min: 3600, max: 10800 },
{ label: "3-6 hrs", min: 10800, max: 21600 },
{ label: "6-12 hrs", min: 21600, max: 43200 },
{ label: "12-24 hrs", min: 43200, max: 86400 },
{ label: "1-2 days", min: 86400, max: 172800 },
{ label: "2-3 days", min: 172800, max: 259200 },
{ label: "3-4 days", min: 259200, max: 345600 },
{ label: "4-5 days", min: 345600, max: 432000 },
{ label: "5-7 days", min: 432000, max: 604800 },
{ label: "7-10 days", min: 604800, max: 864000 },
{ label: "10-15 days", min: 864000, max: 1296000 },
{ label: "15-20 days", min: 1296000, max: 1728000 },
];
export const computeActionTime = async (options: {
start?: string;
end?: string;
file?: string;
}) => {
const emitter = new EventEmitter();
const allEvents = await withLoader(
`Computing action time...`,
async (updateMessage) => {
const startedAt = new Date().getTime();
emitter.on("update", ({ message }) => {
updateMessage(message);
});
const events = await database.getEvents({
cursor: 0,
createdAfter: options.start,
createdBefore: options.end,
types: [
"tools.ozone.moderation.defs#modEventReport",
"tools.ozone.moderation.defs#modEventAcknowledge",
"tools.ozone.moderation.defs#modEventTakeDown",
"tools.ozone.moderation.defs#modEventLabel",
],
});
const metrics: Record<string, ActionTimeMetricGroup> = {
account: {
human: { item: 0, time: 0 },
automated: { item: 0, time: 0 },
unhandled: 0,
},
record: {
human: { item: 0, time: 0 },
automated: { item: 0, time: 0 },
unhandled: 0,
},
};
// Initialize separate histograms for human and automated actions
const humanHandledRecords: Record<string, number> = {};
const automatedHandledRecords: Record<string, number> = {};
const humanHandledAccounts: Record<string, number> = {};
const automatedHandledAccounts: Record<string, number> = {};
for (const bin of timeBins) {
humanHandledRecords[bin.max] = 0;
automatedHandledRecords[bin.max] = 0;
humanHandledAccounts[bin.max] = 0;
automatedHandledAccounts[bin.max] = 0;
}
let i = 0;
const subjectReportTime = new Map<string, string>();
const unhandledSubjects = new Set<string>();
let oldestReportTime = new Date().toISOString();
let latestReportTime = new Date(0).toISOString();
for (const event of events) {
const isRecord = !!event.subjectUri;
const subject = event.subjectUri || event.subjectDid;
const reportTime = subjectReportTime.get(subject);
if (event.action === "tools.ozone.moderation.defs#modEventReport") {
if (!reportTime) {
subjectReportTime.set(subject, event.createdAt);
}
if (new Date(event.createdAt) < new Date(oldestReportTime)) {
oldestReportTime = event.createdAt;
}
if (new Date(event.createdAt) > new Date(latestReportTime)) {
latestReportTime = event.createdAt;
}
unhandledSubjects.add(subject);
continue;
}
if (!reportTime) {
continue;
}
const isAutomated = automods.includes(event.createdBy);
const timeDiffMs =
new Date(event.createdAt).getTime() - new Date(reportTime).getTime();
const timeDiffMins = timeDiffMs / 1000; // Convert ms to minutes
// Assign to histogram bins
const targetHistogram = isRecord
? isAutomated
? automatedHandledRecords
: humanHandledRecords
: isAutomated
? automatedHandledAccounts
: humanHandledAccounts;
for (const bin of timeBins) {
// if (bin.max === 10) {
// console.log(event)
// }
if (timeDiffMins >= bin.min && timeDiffMins < bin.max) {
targetHistogram[bin.max]++;
break;
}
}
// Store into metrics
if (isRecord) {
if (isAutomated) {
metrics.record.automated.item++;
metrics.record.automated.time += timeDiffMs;
} else {
metrics.record.human.item++;
metrics.record.human.time += timeDiffMs;
}
} else {
if (isAutomated) {
metrics.account.automated.item++;
metrics.account.automated.time += timeDiffMs;
} else {
metrics.account.human.item++;
metrics.account.human.time += timeDiffMs;
}
}
unhandledSubjects.delete(subject);
subjectReportTime.delete(subject);
i++;
if (i % 10000 === 0) {
const timeSpent = parseInt(
`${(new Date().getTime() - startedAt) / 1000}`
);
emitter.emit("update", {
message: `Processed ${i} out of ${events.length} event chunks. Time spent: ${timeSpent}s`,
});
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
console.log(
`\nReport time range: ${oldestReportTime} - ${latestReportTime}`
);
if (unhandledSubjects.size > 0) {
for (const unhandled of unhandledSubjects) {
if (unhandled.startsWith("did:")) {
metrics.account.unhandled++;
} else {
metrics.record.unhandled++;
}
}
}
if (options.file) {
await writeFile(
options.file,
JSON.stringify(
{
metrics,
humanHandledRecords,
automatedHandledRecords,
humanHandledAccounts,
automatedHandledAccounts,
oldestReportTime,
latestReportTime,
},
null,
2
),
"utf-8"
);
} else {
console.log(`\n Human-Handled Action Time Histogram (Records):`);
console.table(humanHandledRecords);
console.log(`\n Automated-Handled Action Time Histogram (Records):`);
console.table(automatedHandledRecords);
console.log(`\n Human-Handled Action Time Histogram (Accounts):`);
console.table(humanHandledAccounts);
console.log(`\n Automated-Handled Action Time Histogram (Accounts):`);
console.table(automatedHandledAccounts);
}
}
);
return allEvents;
};