Дашборд живёт, пока его обновляют. Каждый месяц приходят новые данные, коллеги вносят правки прогноза, команда открывает файл из общей папки. В этом уроке — порядок обновления одной кнопкой, автопроверки, журнал правок, который не задваивает цифры, выгрузки в Excel по правилам и публикация так, чтобы у всех была одна свежая версия.
A dashboard lives as long as it is refreshed. Every month new data arrives, colleagues make forecast edits, the team opens the file from a shared folder. This lesson covers the one-button refresh order, automated checks, an edit journal that does not double the numbers, Excel exports by the rules and publishing so that everybody has one fresh version.
На доске объявлений каждый приклеивает свою бумажку — через месяц никто не знает, какая актуальная. Газету каждый выпуск верстают заново из проверенных материалов, ставят дату и номер, старые выпуски хранят в архиве. Дашборд должен жить как газета.
On a notice board everyone pins their own note — a month later nobody knows which one is current. A newspaper is laid out anew every issue from checked material, gets a date and a number, and old issues are archived. A dashboard should live like a newspaper.
| ШагStep | СкриптScript | Если ошибкаOn error |
|---|---|---|
| 1. Загрузить закрытые месяцы1. Load closed months | load_exports.py (урок 05-20)(lesson 05-20) | стоп, дальше не идёмstop, go no further |
| 2. Собрать HTML2. Build the HTML | build_dashboard.py (урок 05-19)(lesson 05-19) | стопstop |
| 3. Проверить дашборд3. Check the dashboard | check_dashboard.py | стоп, не публиковатьstop, do not publish |
| 4. Опубликовать4. Publish | publish.py | сказать честно, что не записаноsay honestly what was not written |
Кнопка — файл RUN_update.bat: двойной клик, меню «что обновить?», шаги по порядку. Ошибка любого шага останавливает цепочку — устаревший дашборд лучше неверного.
The button is the RUN_update.bat file: a double click, a "what to update?" menu, steps in order. An error at any step stops the chain — an outdated dashboard is better than a wrong one.
Положите четыре файла в папку проекта рядом с load_exports.py и сборщиком. В publish_config.txt впишите, куда публиковать — путь от папки скрипта. Шаг, которого нет в папке, меню пропустит.
Put the four files into the project folder next to load_exports.py and the builder. In publish_config.txt write where to publish — a path from the script folder. The menu skips a step that is not in the folder.
@echo off rem Dashboard update. Only Latin letters here: Cyrillic breaks .bat files. chcp 65001 >nul cd /d "%~dp0" set PY=python if exist ".venv\Scripts\python.exe" set PY=.venv\Scripts\python.exe %PY% update_menu.py %1 if errorlevel 1 echo ERROR - read the messages above pause
# -*- coding: utf-8 -*-
"""Меню обновления дашборда: спрашивает, что обновить, и запускает шаги по порядку.
Dashboard update menu: asks what to update and runs the steps in order.
Русский текст живёт здесь, в .py; .bat только запускает этот файл (кириллица ломает .bat).
Шаг, которого нет в папке, пропускается. Ошибка шага останавливает цепочку.
"""
import subprocess
import sys
from pathlib import Path
HERE = Path(__file__).resolve().parent
STEPS = [("1", "Загрузить данные / load data", "load_exports.py"),
("2", "Собрать дашборд / build the dashboard", "build_dashboard.py"),
("3", "Проверить дашборд / check the dashboard", "check_dashboard.py"),
("4", "Опубликовать для команды / publish for the team", "publish.py")]
def run(script):
path = HERE / script
if not path.exists():
print(f" нет файла {script} - шаг пропущен")
return 0
print(f"--- {script}")
return subprocess.call([sys.executable, str(path)], cwd=str(HERE))
def main():
print("Что обновить? / What to update?")
for key, name, _ in STEPS:
print(f" {key} - {name}")
print(" 5 - всё по порядку / everything in order")
choice = (sys.argv[1] if len(sys.argv) > 1 else input("Номер / number: ")).strip()
todo = STEPS if choice == "5" else [s for s in STEPS if s[0] == choice]
if not todo:
print("Нет такого пункта / no such option")
return 1
for key, name, script in todo:
code = run(script)
if code != 0:
print(f"ОСТАНОВЛЕНО на шаге {key} ({script}), код {code}. Дальше не идём / stopped.")
return code
print("Все шаги прошли / all steps passed")
return 0
if __name__ == "__main__":
sys.exit(main()) # -*- coding: utf-8 -*-
"""Публикация дашборда для команды: HTML + скрипты в общую папку, копия версии и запись «что изменилось».
Publishing the dashboard for the team: HTML + scripts to the shared folder, a version copy and a 'what changed' note.
publish_config.txt рядом со скриптом / next to the script:
html=Dashboard.html # что публикуем / what to publish
target=..\\Shared\\Dashboard # куда: путь от папки скрипта / where: path relative to the script folder
Запуск / run: python publish.py "что изменилось / what changed"
"""
import datetime
import shutil
import sys
from pathlib import Path
HERE = Path(__file__).resolve().parent
KEEP = ("*.py", "*.bat", "README.md", "code_map.csv", "city_map.csv") # скрипты и справочники едут вместе с HTML
def config():
cfg = {}
for line in (HERE / "publish_config.txt").read_text(encoding="utf-8-sig").splitlines():
if "=" in line and not line.strip().startswith("#"):
k, v = line.split("=", 1)
cfg[k.strip()] = v.split("#")[0].strip()
return cfg
def copy_checked(src, dst):
"""Копирует и проверяет размер: «готово» только если файл правда на месте."""
shutil.copy2(src, dst)
if not dst.exists() or dst.stat().st_size != src.stat().st_size:
raise OSError(f"не скопировался {src.name}")
def main():
cfg = config()
html = HERE / cfg["html"]
target = (HERE / cfg["target"]).resolve()
if not html.exists():
print(f"СТОП: нет {html.name} - сначала соберите дашборд")
return 1
if not target.parent.exists():
print(f"СТОП: нет папки {target.parent} - проверьте target в publish_config.txt")
return 1
stamp = datetime.datetime.now().strftime("%Y-%m-%d_%H%M")
(target / "versions").mkdir(parents=True, exist_ok=True)
errors = []
try:
copy_checked(html, target / html.name)
copy_checked(html, target / "versions" / f"{html.stem}_{stamp}{html.suffix}")
except OSError as e: # файл открыт у коллеги / file open on a colleague's PC
errors.append(f"{html.name}: {e}")
(target / "scripts").mkdir(exist_ok=True)
for pattern in KEEP:
for f in HERE.glob(pattern):
try:
copy_checked(f, target / "scripts" / f.name)
except OSError as e:
errors.append(f"{f.name}: {e}")
note = sys.argv[1] if len(sys.argv) > 1 else "обновление данных / data refresh"
with open(target / "CHANGES.md", "a", encoding="utf-8") as fh:
fh.write(f"- {stamp}: {note}\n")
if errors:
print("НЕ ВСЁ опубликовано / not everything published:")
for e in errors:
print(" " + e)
return 1
print(f"ГОТОВО: {html.name} -> {target}")
return 0
if __name__ == "__main__":
sys.exit(main()) # What to publish and where. Paths are relative to this folder. # Что публиковать и куда. Пути - от этой папки. html=Dashboard.html target=..\Shared\Dashboard
Файлы проверены: RUN_update.bat запущен в командной строке Windows, меню прошло все шаги, publish.py положил дашборд, скрипты, копию версии и строку в CHANGES.md в тестовую общую папку.
The files were tested: RUN_update.bat was run in the Windows command line, the menu went through all steps, publish.py put the dashboard, scripts, a version copy and a CHANGES.md line into a test shared folder.
Исправили ошибку в загрузчике — проверили data.json — цифры верные. Но дашборд у команды показывает старые цифры: HTML собран вчера, до правки. Проверка JSON подтверждает данные, а люди смотрят на HTML.
You fixed a bug in the loader — checked data.json — the numbers are right. But the team's dashboard shows the old numbers: the HTML was built yesterday, before the fix. A JSON check confirms the data, while people look at the HTML.
Скрипт проверки открывает собранный HTML в браузере без окна и проходит его так, как прошёл бы человек. Минимальный набор:
The check script opens the built HTML in a headless browser and walks through it as a person would. The minimum set:
| ПроверкаCheck | Что ловитWhat it catches |
|---|---|
| Все листы × состояния фильтровAll sheets × filter states | лист, который падает только при одном фильтреa sheet that breaks only with one filter |
| Ошибки JavaScript в консолиJavaScript errors in the console | пустую страницу «без причины»a blank page "for no reason" |
| Сверка цифрNumber reconciliation | итог на экране ≠ итогу data.jsonthe on-screen total ≠ the data.json total |
| Повторные объявленияDuplicate declarations | два const с одним именем — пустая страница; две функции — молча работает последняяtwo const with one name — a blank page; two functions — the last one silently wins |
Такой скрипт ассистент пишет по заданию за один заход — пример проверки лежит в образце дашборда (урок 05-18).
An assistant writes such a script from a brief in one go — a sample check is in the sample dashboard (lesson 05-18).
Пусть сок в Каменске в базе — 1 000. Планер поднял до 1 100, канальный менеджер позже — до 1 150. Как записать?
Say juice in Kamensk has a baseline of 1,000. The planner raised it to 1,100, the channel manager later to 1,150. How to record it?
| Журнал измененийJournal of changes | Журнал решенийJournal of decisions | |
|---|---|---|
| ЗаписьRecord | +100, +50 | «стало 1 100», «стало 1 150»"became 1,100", "became 1,150" |
| ИтогResult | база + сумма измененийbaseline + sum of changes | последнее действующее решениеthe latest active decision |
| Запись пришла дваждыA record arrived twice | +100 сложится дважды: 1 250+100 adds twice: 1,250 | повтор по ID не считается: 1 150a repeat by ID does not count: 1,150 |
| Удалить правкуDelete an edit | только обратной записью — её забываютonly by a reverse record — people forget it | пометка «удалена», строка остаётсяa "deleted" mark, the row stays |
Каждая запись несёт автора, время и причину. Когда правка задаёт итог суббренда или города, её раскладывают на SKU по долям — и пишут в журнал уже разложенные решения.
Every record carries the author, time and reason. When an edit sets the total of a subbrand or a city, it is split to SKUs by shares — and the split decisions go into the journal.
Планер (журнал A) и канальный менеджер (журнал B) правят три пары «товар · город». Подключайте журналы, загружайте B второй раз, удаляйте правку и переключайте способ записи.
The planner (journal A) and the channel manager (journal B) edit three product · city pairs. Connect the journals, load B a second time, delete an edit and switch the recording method.
Журнал — обычная книга .xlsx в общей папке. Дашборд пишет в неё правки через File System Access (Chrome, Edge — урок 05-20), а в других браузерах — скачивает файл правок, который человек кладёт в папку. Сборщик при следующем обновлении читает все файлы журналов, сливает по ID и показывает итог. Если книга открыта у коллеги, запись не пройдёт — дашборд обязан сказать «не записано».
The journal is an ordinary .xlsx workbook in the shared folder. The dashboard writes edits into it via File System Access (Chrome, Edge — lesson 05-20), and in other browsers downloads an edits file that a person puts into the folder. On the next refresh the builder reads all journal files, merges them by ID and shows the result. If a colleague has the workbook open, writing fails — the dashboard must say "not written".
| ПравилоRule | ПочемуWhy |
|---|---|
| Числа — числами, с разделителем разрядовNumbers as numbers, with thousand separators | текст не складывается и не сортируетсяtext does not sum or sort |
| Формат — классом в <style>, без заливокFormat via a class in <style>, no fills | файл открывается чистым, его можно дальше считатьthe file opens clean and can be worked on |
| Формула — текстом в ячейке, если нужнаA formula as the text of a cell, if needed | видно, как посчитаноyou can see how it was calculated |
| Имя файла = что + все активные фильтрыFile name = what + all active filters | через неделю понятно, что внутри, без открытияa week later you know what is inside without opening it |
| Автор в свойствах файла — ваше имяThe author in the file properties — your name | файл ходит по почте, автор не теряетсяthe file travels by mail, the author is not lost |
В образце дашборда (урок 05-18) есть колода слайдов для встречи: заголовок-вывод, график, светофор, решения. Слайд сохраняется картинкой (html2canvas) или целой презентацией .pptx (PptxGenJS). Правило то же, что для Excel: слайд собирается из тех же проверенных цифр, а имя файла несёт цикл и фильтры.
The sample dashboard (lesson 05-18) has a slide deck for the meeting: a conclusion headline, a chart, a traffic light, decisions. A slide is saved as a picture (html2canvas) or a whole .pptx deck (PptxGenJS). The rule is the same as for Excel: a slide is built from the same checked numbers, and the file name carries the cycle and filters.
versions и строка в CHANGES.md: что изменилось.versions and a line in CHANGES.md: what changed.HTML-файл может прочитать любой, кто его открыл. Пароли, токены, ключи API к ИИ-сервисам в дашборд не кладём.
Anyone who opens an HTML file can read it. Passwords, tokens, API keys to AI services do not go into the dashboard.
Вместо подключения к ИИ — объяснение, которое считает сам дашборд: из чего сложена цифра и из какого файла она пришла.
Instead of connecting to AI — an explanation the dashboard calculates itself: what the number is made of and which file it came from.
Все данные лежат внутри HTML. Если коллеге нельзя видеть цифры другой страны — нужен отдельный дашборд, а не скрытая вкладка.
All data sits inside the HTML. If a colleague must not see another country's numbers — a separate dashboard is needed, not a hidden tab.
| Как вы делаете рукамиWhat you do by hand | Как это называетсяWhat it is called |
|---|---|
| каждый месяц копируете файлы и жмёте «обновить»every month copy files and press "refresh" | конвейер обновления (pipeline)a refresh pipeline |
| пролистываете все вкладки после обновленияscroll through every tab after a refresh | дымовой тест (smoke test)a smoke test |
| пишете в письме «что поменялось»write "what changed" in an email | журнал изменений (changelog)a changelog |
| храните «Дашборд_старый_2.html»keep "Dashboard_old_2.html" | версионированиеversioning |
| записываете, кто какую цифру поправилwrite down who changed which number | журнал аудита (audit trail)an audit trail |
Визуальная проверка обязательна. Автопроверка подтверждает, что дашборд открывается и цифры сходятся с data.json. Что цифры правдоподобны, видит только человек: откройте опубликованную копию — именно её, а не свою — и посмотрите дату обновления, последний месяц и пару знакомых цифр.
Visual check is a must. An automated check confirms the dashboard opens and the numbers match data.json. Whether the numbers are plausible only a person sees: open the published copy — that one, not yours — and look at the refresh date, the last month and a couple of familiar numbers.
29.09.2025 Microsoft объявил режим агента в Excel: ассистент сам строит листы и формулы по заданию. Это ускоряет черновик отчёта — но проверки и правила выгрузки остаются вашими (урок 02-08).
On 29.09.2025 Microsoft announced Agent Mode in Excel: the assistant builds sheets and formulas from a brief. It speeds up a report draft — but checks and export rules stay yours (lesson 02-08).
File System Access по-прежнему работает только в Chrome и Edge (урок 05-20). Журнал правок держите с запасным путём «скачать файл правок».
File System Access still works only in Chrome and Edge (lesson 05-20). Keep the edit journal with a "download the edits file" fallback.
Источники: GeekWire, 29.09.2025, режим агента в Excel; web.dev и Web Platform features explorer — File System Access.
Sources: GeekWire, 29.09.2025, Agent Mode in Excel; web.dev and Web Platform features explorer — File System Access.
Три случая из работы планера в компании-производителе напитков. Названия изменены.
Three cases from planning work at a beverage manufacturer. Names are changed.
В книге оба журнала из тренажёра на одном листе и формулы, которые решают, какая правка в силе: повтор, действует, последняя по времени, порог согласования. Лист «итог» сравнивает журнал решений с журналом изменений, лист «имя файла» собирает имя выгрузки из фильтров.
The workbook has both journals from the simulator on one sheet and formulas that decide which edit is in force: duplicate, active, latest by time, approval threshold. The "result" sheet compares the decision journal with the journal of changes, the "file name" sheet builds the export name from filters.
1. Журнал решений: сок 1 150, вода 760, чай 650. Журнал изменений: 1 250, 760, 890. Сок: E1 (+100) пришла дважды. Чай: удалённая E3 (+240) осталась в сумме, а E5 записана как +50 к тому, что видел канальный менеджер (600), хотя итог уже был 840.
2. При 20 % — только E3 (+40 %). При 10 % — E3 и E4 (+15 %); E1 ровно +10 % порог не превышает.
3. Нет, чай останется 650: E3 сделана 28.10 в 15:00, E5 — 29.10 в 16:00, в силе последняя по времени.
4. Имя меняется сразу, например «Точность_Страна-1_Цикл-10_M-1_SKU×город_тыс.л_2026-10-30.xlsx».
1. Decision journal: juice 1,150, water 760, tea 650. Journal of changes: 1,250, 760, 890. Juice: E1 (+100) arrived twice. Tea: the deleted E3 (+240) stayed in the sum, and E5 was logged as +50 to what the channel manager saw (600), though the total was already 840.
2. At 20% — only E3 (+40%). At 10% — E3 and E4 (+15%); E1 at exactly +10% does not exceed the threshold.
3. No, tea stays 650: E3 was made on 28.10 at 15:00, E5 on 29.10 at 16:00; the latest by time is in force.
4. The name changes at once, e.g. "Accuracy_Country-1_Cycle-10_M-1_SKU×city_k.L_2026-10-30.xlsx".
Кто что делает: я, Claude, Python?Who does what: me, Claude, Python?
Claude пишет скрипты обновления, проверки и публикации по вашему заданию. Python выполняет их по кнопке. Вы проверяете опубликованную копию глазами и отвечаете за цифры перед командой.
Claude writes the refresh, check and publishing scripts from your brief. Python runs them on the button. You check the published copy with your eyes and answer for the numbers to the team.
Можно обновлять по расписанию ночью?Can it refresh on a schedule at night?
Можно — через запуск по расписанию Windows (Task Scheduler). Но ноутбук должен быть включён и не спать, а результат кто-то должен проверить утром.
Yes — via Windows Task Scheduler. But the laptop must be on and awake, and someone must check the result in the morning.
Зачем публиковать скрипты, если команда смотрит только HTML?Why publish scripts if the team only looks at the HTML?
Чтобы дашборд пережил ваш отпуск: кто угодно из команды обновит его кнопкой.
So the dashboard survives your vacation: anyone on the team can refresh it with the button.
Что делать, если коллега правил в старой версии?What if a colleague edited in an old version?
Ничего страшного для журнала решений: его правки придут с ID и временем, слияние возьмёт последнюю действующую. Именно поэтому журнал хранит решения, а не изменения.
No harm for a decision journal: the edits come with an ID and a time, the merge takes the latest active one. That is exactly why the journal stores decisions, not changes.