// Partitura — standalone data. // Day "вчера" 2026-05-17, 08:00 → now (14:35). All times are minutes-since-midnight. const PT_DAY = "2026-05-17"; const PT_NOW_MIN = 14 * 60 + 35; // 14:35 // Helpers const HM = (h, m = 0) => h * 60 + m; const fmtHM = (mins) => { const h = Math.floor(mins / 60) % 24; const m = Math.floor(mins) % 60; return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`; }; const fmtHMS = (mins) => { const total = mins * 60; const h = Math.floor(total / 3600) % 24; const m = Math.floor((total % 3600) / 60); const s = Math.floor(total % 60); return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`; }; // Agents — reuse the TEAM constant from data.jsx (already on window) // Each agent's day broken into SEQUENTIAL segments. No overlap. // segment = { start, end, state: "in-progress"|"blocked"|"review"|"testing"|"research"|"planning", // seq, title, module } const PT_AGENT_SEGMENTS = { lev: [ { start: HM(8,15), end: HM(9, 0), state: "planning", seq: 51, title: "Описать модуль уведомлений в KALK", module: "kalk" }, { start: HM(9, 0), end: HM(10,30), state: "planning", seq: 52, title: "ТЗ на интеграцию с 1С", module: "integrations" }, { start: HM(10,30), end: HM(10,45), state: "blocked", seq: 52, title: "ТЗ на интеграцию с 1С (жду Максима)", module: "integrations" }, { start: HM(10,45), end: HM(11,30), state: "planning", seq: 52, title: "ТЗ на интеграцию с 1С", module: "integrations" }, { start: HM(11,30), end: HM(12, 0), state: "planning", seq: 53, title: "Декомпозиция эпика «Личный кабинет»", module: "auth" }, { start: HM(12,30), end: HM(13,45), state: "planning", seq: 54, title: "Review архитектуры миграции БД", module: "migrations" }, { start: HM(13,45), end: PT_NOW_MIN,state: "planning", seq: 55, title: "Постановка PB-56..58 (KALK v2)", module: "kalk" }, ], kostya: [ { start: HM(8,20), end: HM(10, 0), state: "in-progress", seq: 47, title: "Аналитика заказов: фильтр по статусу", module: "orders" }, { start: HM(10, 0), end: HM(10,15), state: "blocked", seq: 47, title: "Аналитика заказов (жду Лёва)", module: "orders" }, { start: HM(10,15), end: HM(11,45), state: "in-progress", seq: 47, title: "Аналитика заказов: фильтр по статусу", module: "orders" }, { start: HM(12,30), end: HM(14, 0), state: "in-progress", seq: 49, title: "Ребрендинг Print-X в коде сайта", module: "seo" }, { start: HM(14, 0), end: HM(14,15), state: "blocked", seq: 49, title: "Ребрендинг Print-X (конфликт стилей)", module: "seo" }, { start: HM(14,15), end: PT_NOW_MIN,state: "in-progress", seq: 49, title: "Ребрендинг Print-X в коде сайта", module: "seo" }, ], roma: [ { start: HM(9,30), end: HM(10, 0), state: "review", seq: 46, title: "Review: миграция БД на v3", module: "migrations" }, { start: HM(10, 0), end: HM(10,30), state: "review", seq: 48, title: "Review: SEO meta-теги", module: "seo" }, { start: HM(11, 0), end: HM(11,45), state: "review", seq: 45, title: "Review: Onboarding wizard", module: "kalk" }, { start: HM(12, 0), end: HM(12,45), state: "review", seq: 47, title: "Review: фильтр аналитики заказов", module: "orders" }, { start: HM(13,30), end: HM(14,10), state: "review", seq: 50, title: "Review: каталог А4-референсов", module: "seo" }, ], timur: [ { start: HM(9, 0), end: HM(10,30), state: "testing", seq: 44, title: "E2E: Telegram-бот подсчёт стоимости", module: "tg-bot" }, { start: HM(11, 0), end: HM(12,15), state: "testing", seq: 45, title: "E2E: Onboarding wizard", module: "kalk" }, { start: HM(13, 0), end: PT_NOW_MIN,state: "testing", seq: 47, title: "E2E: фильтр аналитики заказов", module: "orders" }, ], semyon: [ { start: HM(8,30), end: HM(11, 0), state: "research", seq: 50, title: "Подобрать референсы для каталога A4", module: "seo" }, { start: HM(11, 0), end: HM(11,30), state: "blocked", seq: 50, title: "Подобрать референсы (rate-limit Pinterest)", module: "seo" }, { start: HM(11,30), end: HM(13,48), state: "research", seq: 50, title: "Подобрать референсы для каталога A4", module: "seo" }, // 13:48 — watcher offline (no segments after) ], viktor: [ { start: HM(9,15), end: HM(9,45), state: "planning", seq: 46, title: "Разбор алерта по PB-46", module: "watchdog" }, { start: HM(10,30), end: HM(11, 0), state: "planning", seq: 48, title: "Разбор алерта по PB-48", module: "watchdog" }, { start: HM(12,30), end: HM(13,30), state: "planning", seq: 52, title: "Сверка с архитектурой", module: "integrations" }, { start: HM(13,50), end: PT_NOW_MIN,state: "planning", seq: 55, title: "Разбор алертов за смену", module: "watchdog" }, ], }; // Events — one stream, classified by kind. Plotted on the agent's track. // kinds: new_task, state_change, assigned, label, comment, commit, alert const PT_EVENTS = [ // Lev events { t: HM(8,15), agent: "lev", kind: "new_task", seq: 51, text: "создал PB-51 «Описать модуль уведомлений в KALK»" }, { t: HM(8,17), agent: "lev", kind: "label", seq: 51, text: "ставит лейбл for-architect" }, { t: HM(8,52), agent: "lev", kind: "comment", seq: 51, text: "первый драфт ТЗ готов" }, { t: HM(9, 2), agent: "lev", kind: "new_task", seq: 52, text: "создал PB-52 «ТЗ на интеграцию с 1С»" }, { t: HM(10,30), agent: "lev", kind: "state_change",seq: 52, text: "PB-52 Todo → Blocked (жду ответа Максима)" }, { t: HM(10,45), agent: "lev", kind: "state_change",seq: 52, text: "PB-52 Blocked → In Progress" }, { t: HM(11,20), agent: "lev", kind: "comment", seq: 52, text: "схема интеграции 1С v2, на review" }, { t: HM(11,30), agent: "lev", kind: "new_task", seq: 53, text: "создал PB-53 «Эпик: Личный кабинет»" }, { t: HM(13,12), agent: "lev", kind: "comment", seq: 54, text: "архитектура миграции одобрена" }, { t: HM(13,45), agent: "lev", kind: "new_task", seq: 55, text: "создал PB-55 «KALK v2 — постановка»" }, { t: HM(14,18), agent: "lev", kind: "label", seq: 56, text: "ставит for-coder на PB-56" }, // Kostya events { t: HM(8,20), agent: "kostya", kind: "assigned", seq: 47, text: "взял PB-47 в работу" }, { t: HM(8,21), agent: "kostya", kind: "state_change",seq: 47, text: "PB-47 Todo → In Progress" }, { t: HM(8,52), agent: "kostya", kind: "commit", seq: 47, text: "feat(orders): фильтр-чипы (черновик)" }, { t: HM(9,38), agent: "kostya", kind: "commit", seq: 47, text: "wip: useFilterStore" }, { t: HM(10, 0), agent: "kostya", kind: "state_change",seq: 47, text: "PB-47 In Progress → Blocked" }, { t: HM(10, 1), agent: "kostya", kind: "comment", seq: 47, text: "@lev уточни schema для FilterState" }, { t: HM(10,15), agent: "kostya", kind: "state_change",seq: 47, text: "PB-47 Blocked → In Progress" }, { t: HM(10,42), agent: "kostya", kind: "commit", seq: 47, text: "feat: filter state shape per @lev" }, { t: HM(11,30), agent: "kostya", kind: "commit", seq: 47, text: "tests: фильтрация по статусам" }, { t: HM(11,45), agent: "kostya", kind: "label", seq: 47, text: "ставит for-reviewer" }, { t: HM(11,45), agent: "kostya", kind: "state_change",seq: 47, text: "PB-47 In Progress → Review" }, { t: HM(12,30), agent: "kostya", kind: "assigned", seq: 49, text: "взял PB-49 в работу" }, { t: HM(12,33), agent: "kostya", kind: "commit", seq: 49, text: "chore: grep Print-X across repo" }, { t: HM(13,18), agent: "kostya", kind: "comment", seq: 49, text: "progress: правлю Footer.tsx, осталось header" }, { t: HM(13,42), agent: "kostya", kind: "commit", seq: 49, text: "fix(seo): заменил title-теги" }, { t: HM(14, 0), agent: "kostya", kind: "state_change",seq: 49, text: "PB-49 In Progress → Blocked" }, { t: HM(14, 8), agent: "kostya", kind: "alert", sev: "warning", seq: 49, ruleName: "stuck_blocked_15min", text: "правило сработало: задача висит в Blocked > 15 мин" }, { t: HM(14,15), agent: "kostya", kind: "state_change",seq: 49, text: "PB-49 Blocked → In Progress" }, // Roma events { t: HM(9,28), agent: "roma", kind: "assigned", seq: 46, text: "взял PB-46 на ревью" }, { t: HM(9,55), agent: "roma", kind: "comment", seq: 46, text: "approved — переименуй migrate_v3.py → migrations/003_v3.py" }, { t: HM(9,57), agent: "roma", kind: "state_change",seq: 46, text: "PB-46 Review → Done" }, { t: HM(10, 0), agent: "roma", kind: "assigned", seq: 48, text: "взял PB-48 на ревью" }, { t: HM(10,28), agent: "roma", kind: "state_change",seq: 48, text: "PB-48 Review → Done" }, { t: HM(11, 0), agent: "roma", kind: "assigned", seq: 45, text: "взял PB-45 на ревью" }, { t: HM(11,32), agent: "roma", kind: "comment", seq: 45, text: "LGTM, переименуй handleSubmit2" }, { t: HM(11,44), agent: "roma", kind: "state_change",seq: 45, text: "PB-45 Review → Done" }, { t: HM(12, 0), agent: "roma", kind: "assigned", seq: 47, text: "взял PB-47 на ревью" }, { t: HM(12,42), agent: "roma", kind: "comment", seq: 47, text: "approved" }, { t: HM(12,44), agent: "roma", kind: "state_change",seq: 47, text: "PB-47 Review → Done" }, { t: HM(13,30), agent: "roma", kind: "assigned", seq: 50, text: "взял PB-50 на ревью" }, { t: HM(14, 8), agent: "roma", kind: "state_change",seq: 50, text: "PB-50 Review → Done" }, // Timur events { t: HM(9, 0), agent: "timur", kind: "assigned", seq: 44, text: "тестирует PB-44" }, { t: HM(9,18), agent: "timur", kind: "comment", seq: 44, text: "happy-path ок" }, { t: HM(10,12), agent: "timur", kind: "comment", seq: 44, text: "edge: пустая корзина — падает" }, { t: HM(10,28), agent: "timur", kind: "label", seq: 44, text: "ставит tested" }, { t: HM(11,10), agent: "timur", kind: "comment", seq: 45, text: "happy-path ок" }, { t: HM(12,13), agent: "timur", kind: "label", seq: 45, text: "ставит tested" }, { t: HM(13,22), agent: "timur", kind: "comment", seq: 47, text: "playwright spec написан" }, // Semyon events { t: HM(8,30), agent: "semyon", kind: "assigned", seq: 50, text: "начал PB-50 — поиск референсов" }, { t: HM(9,18), agent: "semyon", kind: "comment", seq: 50, text: "10 ссылок на printio собрал" }, { t: HM(10,40), agent: "semyon", kind: "comment", seq: 50, text: "ещё 6 ссылок c Behance" }, { t: HM(11, 0), agent: "semyon", kind: "state_change",seq: 50, text: "PB-50 In Progress → Blocked" }, { t: HM(11, 2), agent: "semyon", kind: "comment", seq: 50, text: "Pinterest 429 rate-limit, жду 30 мин" }, { t: HM(11,30), agent: "semyon", kind: "state_change",seq: 50, text: "PB-50 Blocked → In Progress" }, { t: HM(12,15), agent: "semyon", kind: "comment", seq: 50, text: "перешёл к Pinterest, 8 досок" }, { t: HM(13,30), agent: "semyon", kind: "comment", seq: 50, text: "сборка отчёта" }, { t: HM(13,48), agent: "semyon", kind: "alert", sev: "critical", seq: 50, ruleName: "watcher_heartbeat_missing", text: "правило сработало: watcher Семёна молчит > 5 мин" }, // Viktor events { t: HM(9,15), agent: "viktor", kind: "assigned", seq: 46, text: "разбирает алерт по PB-46" }, { t: HM(9,44), agent: "viktor", kind: "comment", seq: 46, text: "выяснил: Рома уложился, false-positive" }, { t: HM(10,30), agent: "viktor", kind: "assigned", seq: 48, text: "разбирает алерт по PB-48" }, { t: HM(12,30), agent: "viktor", kind: "comment", seq: 52, text: "согласовал с Лёвом схему 1С" }, { t: HM(13,15), agent: "viktor", kind: "alert", sev: "info", seq: 46, ruleName: "reviewer_pickup_within_1h", text: "правило сработало: info — на грани SLA" }, { t: HM(14, 5), agent: "viktor", kind: "comment", seq: 55, text: "разбираю warning по Косте" }, ]; // Modules — derive set from segments function ptModulesList() { const s = new Set(); Object.values(PT_AGENT_SEGMENTS).forEach((segs) => segs.forEach((sg) => s.add(sg.module))); return Array.from(s); } // Watcher offline windows (for grey "offline" zones) const PT_OFFLINE = { semyon: [{ start: HM(13, 48), end: PT_NOW_MIN }], }; // Module color palette const MODULE_COLORS = { orders: "#a78bfa", kalk: "#ec4899", "tg-bot": "#60a5fa", seo: "#34d399", auth: "#fb923c", watchdog: "#f472b6", integrations: "#22d3ee", migrations: "#facc15", notifications:"#f59e0b", spec: "#a3e635", docs: "#94a3b8", scaffold: "#cbd5e1", }; function moduleColor(m) { return MODULE_COLORS[m] || "#a1a1aa"; } // State / segment styling — driven by CSS variables so themes can override. const STATE_STYLES = { "in-progress": { label: "В работе", color: "var(--st-coding-color)", bg: "var(--st-coding-bg)", border: "var(--st-coding-border)" }, "review": { label: "Ревью", color: "var(--st-review-color)", bg: "var(--st-review-bg)", border: "var(--st-review-border)" }, "testing": { label: "Тестинг", color: "var(--st-testing-color)", bg: "var(--st-testing-bg)", border: "var(--st-testing-border)" }, "research": { label: "Разведка", color: "var(--st-research-color)", bg: "var(--st-research-bg)", border: "var(--st-research-border)" }, "planning": { label: "Постановка", color: "var(--st-planning-color)", bg: "var(--st-planning-bg)", border: "var(--st-planning-border)" }, "blocked": { label: "Заблокировано", color: "var(--st-blocked-color)", bg: "transparent", border: "var(--st-blocked-border)", hatch: true }, }; // --------------------------------------------------------------------------- // History / aggregates (used by period views: week/month/year) // --------------------------------------------------------------------------- function _hash(s) { let h = 0; for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) | 0; return Math.abs(h); } function _rng(seed) { return () => { seed = (seed * 9301 + 49297) % 233280; return seed / 233280; }; } // Generate synthetic per-day history for an agent over the last N days. // Returns array indexed by daysAgo (0 = today, N-1 = oldest), each entry has minutes per state. function genHistory(agentId, days) { const r = _rng(_hash(agentId)); const out = []; const states = ["planning", "in-progress", "review", "testing", "research", "blocked"]; // Today is partial — use today's actual segments to seed the first entry where possible const todaySegs = PT_AGENT_SEGMENTS[agentId] || []; const todayByState = {}; todaySegs.forEach(s => { todayByState[s.state] = (todayByState[s.state] || 0) + (s.end - s.start); }); for (let d = 0; d < days; d++) { let entry; if (d === 0) { // today's actual breakdown entry = { ...todayByState }; } else { // synthetic but with agent-stable shape — pick a "profile" by role weights const a = (window.teamById || (() => null))(agentId); const role = a?.role || "coder"; entry = {}; const intensity = 0.55 + 0.45 * r(); const isWeekend = ((new Date().getDay() - d + 7) % 7) >= 5; const mult = isWeekend ? 0.25 + r() * 0.3 : intensity; if (role === "architect" || role === "workflow-lead") { entry.planning = Math.round((180 + r() * 240) * mult); entry["in-progress"] = Math.round((30 + r() * 60) * mult); } else if (role === "reviewer") { entry.review = Math.round((120 + r() * 180) * mult); entry.planning = Math.round((20 + r() * 40) * mult); } else if (role === "tester") { entry.testing = Math.round((180 + r() * 200) * mult); } else if (role === "scout") { entry.research = Math.round((140 + r() * 220) * mult); } else { entry["in-progress"] = Math.round((240 + r() * 240) * mult); entry.review = Math.round((20 + r() * 30) * mult); } if (r() > 0.75) entry.blocked = Math.round(r() * 45); } let total = 0; states.forEach(s => { total += entry[s] || 0; }); entry.total = total; out.push(entry); } return out; } // Aggregate one history array over a period (e.g. 7 → week summary, 30 → month). function aggregateHistory(history) { const acc = { planning: 0, "in-progress": 0, review: 0, testing: 0, research: 0, blocked: 0, total: 0 }; history.forEach(h => { Object.keys(acc).forEach(k => { acc[k] += h[k] || 0; }); }); return acc; } // Group history into N-day buckets (e.g. for year mode, group 365 days into 52 weeks). function bucketHistory(history, bucketSize) { const buckets = []; for (let i = 0; i < history.length; i += bucketSize) { const slice = history.slice(i, i + bucketSize); buckets.push(aggregateHistory(slice)); } return buckets; } // Synthetic per-day event counts (alerts, total events) per agent. function genEventCounts(agentId, days) { const r = _rng(_hash(agentId + "_events")); return Array.from({ length: days }, () => ({ events: Math.round(40 + r() * 80), alerts: r() > 0.85 ? Math.round(1 + r() * 2) : 0, })); } // Period descriptors const PERIODS = [ { id: "day", label: "День", days: 1 }, { id: "week", label: "Неделя", days: 7 }, { id: "month", label: "Месяц", days: 30 }, { id: "year", label: "Год", days: 365, bucket: 7 }, // bucketed to weeks ]; function periodDef(id) { return PERIODS.find(p => p.id === id) || PERIODS[0]; } Object.assign(window, { PT_DAY, PT_NOW_MIN, HM, fmtHM, fmtHMS, PT_AGENT_SEGMENTS, PT_EVENTS, PT_OFFLINE, ptModulesList, MODULE_COLORS, moduleColor, STATE_STYLES, genHistory, aggregateHistory, bucketHistory, genEventCounts, PERIODS, periodDef, });