notification changes

This commit is contained in:
indifferentketchup
2026-04-08 09:22:47 -05:00
parent 4d53ef179f
commit a4fb82620a
9 changed files with 740 additions and 131 deletions

View File

@@ -30,6 +30,83 @@ if (!envPath) {
}
}
const DEFAULT_NOTIFICATION_THRESHOLDS = {
// patternChecker - age-based (time since condition first became true)
user_tickets: ['15m', '30m', '1h', '3h'],
user_reopen: ['1h', '4h', '1d'],
user_crossgame: ['1h', '1d'],
game_surge: ['15m', '30m', '1h'],
game_backlog: ['30m', '1h', '3h', '6h'],
game_resolution: ['1d'],
game_spike: ['15m', '30m'],
tag_top: ['1h', '6h', '1d'],
tag_escalation: ['1h', '6h', '1d'],
untagged_closes: ['1h', '1d'],
tag_game_corr: ['1d'],
user_esc: ['1h', '6h', '1d'],
game_esc_rate: ['1d'],
rapid_t2_t3: ['3', '5', '10', '15', '20', '30', '50'], // count-based milestones, not time
staff_no_close: ['1h', '3h'],
staff_overloaded: ['1h', '3h', '6h'],
staff_stale: ['1h', '3h'],
staff_transfer_rate: ['1h', '1d'],
staff_esc: ['1h', '6h', '1d'],
staff_game_esc: ['1d'],
game_tag_spike: ['1h', '6h'],
overnight_gap: ['1d'],
staff_always_esc: ['1d'],
// surgeChecker - cooldown-escalating (repeat alerts spaced further apart)
surge_tickets: ['10m', '30m', '1h', '2h', '3h'],
surge_game: ['10m', '30m', '1h', '2h'],
surge_stale: ['30m', '1h', '2h', '4h'],
surge_needs_response: ['15m', '30m', '1h', '3h'],
surge_unclaimed: ['15m', '30m', '1h', '2h', '4h'],
surge_tier3_unclaimed: ['10m', '15m', '30m', '1h', '2h'],
surge_no_staff: ['10m', '20m', '30m', '1h'],
// staffNotifications - age-based per ticket (hours)
unclaimed_reminder: ['1h', '2h', '4h', '8h', '1d'],
// chatAlertChecker - cooldown-escalating
chat_messages: ['15m', '30m', '1h', '3h'],
chat_time: ['30m', '1h', '2h', '4h']
};
function parseThresholdString(str) {
const value = String(str || '').trim();
if (!value) return NaN;
// Integers without a unit are raw count milestones.
if (/^\d+$/.test(value)) return parseInt(value, 10);
let totalMs = 0;
const re = /(\d+)([mhd])/g;
let match;
let consumed = '';
while ((match = re.exec(value)) !== null) {
const amount = parseInt(match[1], 10);
const unit = match[2];
consumed += match[0];
if (unit === 'm') totalMs += amount * 60 * 1000;
else if (unit === 'h') totalMs += amount * 60 * 60 * 1000;
else if (unit === 'd') totalMs += amount * 24 * 60 * 60 * 1000;
}
if (!consumed || consumed !== value) return NaN;
return totalMs;
}
function parseNotificationThresholdsJson(raw) {
if (!raw || !String(raw).trim()) return DEFAULT_NOTIFICATION_THRESHOLDS;
try {
const parsedJson = JSON.parse(raw);
if (parsedJson && typeof parsedJson === 'object' && !Array.isArray(parsedJson)) {
return parsedJson;
}
} catch (err) {
console.warn('[config] Failed to parse NOTIFICATION_THRESHOLDS_JSON, using default:', err.message);
}
return DEFAULT_NOTIFICATION_THRESHOLDS;
}
const CONFIG = {
DISCORD_TOKEN: (process.env.DISCORD_TOKEN || process.env.DISCORD_BOT_TOKEN || '').trim(),
DISCORD_GUILD_ID: process.env.DISCORD_GUILD_ID || null,
@@ -191,6 +268,7 @@ const CONFIG = {
SETTINGS_DOMAIN: process.env.SETTINGS_DOMAIN || 'tickets.indifferentketchup.com',
INTERNAL_API_PORT: parseInt(process.env.INTERNAL_API_PORT) || 12753,
INTERNAL_API_SECRET: process.env.INTERNAL_API_SECRET || null,
NOTIFICATION_THRESHOLDS: parseNotificationThresholdsJson(process.env.NOTIFICATION_THRESHOLDS_JSON),
UNCLAIMED_REMINDER_THRESHOLDS: (process.env.UNCLAIMED_REMINDER_THRESHOLDS || '1,2,4')
.split(',')
.map(s => parseInt(s.trim(), 10))
@@ -262,6 +340,7 @@ const GAME_NAME_TO_KEY = {
module.exports = {
CONFIG,
parseThresholdString,
TICKET_TAGS,
GAME_NAMES,
GAME_ALIASES,