Красивый дашборд с неверными цифрами хуже, чем таблица в Excel: ему верят. В уроках 05-18 и 05-19 вы собрали дашборд на учебных данных. Теперь подключаем свои: выгрузки продаж из учётной системы, справочник кодов, города. Разберём, как Python читает файлы, где прячутся ловушки и какие проверки ставить после каждого шага, чтобы в дашборд попадали только проверенные цифры. В конце — как подключить файл прямо из браузера.
A good-looking dashboard with wrong numbers is worse than an Excel table: people trust it. In lessons 05-18 and 05-19 you built a dashboard on training data. Now we connect your own: sales exports from the accounting system, the code list, cities. We look at how Python reads files, where the traps hide and which checks to put after every step so that only checked numbers reach the dashboard. At the end — how to connect a file right from the browser.
Кладовщик не ставит товар на полку, пока не сверил его с накладной: сколько коробок, те ли коды, не битая ли упаковка. Если не сходится — коробки стоят в зоне приёмки, а не на полке. Данные для дашборда принимают так же: выгрузку сначала сверяют, и только потом она «встаёт на полку» — в data.json.
A storekeeper does not put goods on the shelf until they are checked against the delivery note: how many cases, the right codes, no damaged packs. If it does not match — the cases stay in the receiving area, not on the shelf. Data for a dashboard is received the same way: an export is checked first and only then "goes on the shelf" — into data.json.
| ШагStep | Что делаетWhat it does | Контрольная точкаCheckpoint |
|---|---|---|
| 1 | Найти папку и файлыFind the folder and files | папка есть; один файл на месяц; месяц закрытfolder exists; one file per month; month closed |
| 2 | Прочитать строкиRead the rows | строк столько, сколько ждали; колонки найдены по названиюas many rows as expected; columns found by name |
| 3 | Почистить текст и числаClean text and numbers | чисел текстом и «nan» — ноль или каждое записано в отчётnumbers as text and "nan" — zero, or each one logged |
| 4 | Склеить со справочникамиJoin with master data | кодов и городов без пары — нольcodes and cities without a match — zero |
| 5 | Сложить и сверитьSum and reconcile | сумма до склейки = сумме послеtotal before join = total after |
| 6 | Записать data.jsonWrite data.json | файл правда записан; в нём список прочитанных файловthe file is really written; it lists the files that were read |
Дальше data.json забирает сборщик из урока 05-19, а обновление и публикацию разбираем в уроке 05-21.
Then the builder from lesson 05-19 takes data.json; refreshing and publishing are in lesson 05-21.
Sales_2026-09.xlsx. Файл «Sales_2026-09 (1).xlsx» — это задвоение, его видно по имениa name with the month: Sales_2026-09.xlsx. A file "Sales_2026-09 (1).xlsx" is a duplicate, visible by its name| ФайлFile | Чем читатьWhat reads it | На что смотретьWhat to watch |
|---|---|---|
| .xlsx | openpyxl, pandas | формулы без значений: книга, сохранённая не в Excel, может не хранить результаты формулformulas without values: a workbook not saved by Excel may not store formula results |
| .xlsb | pandas + pyxlsb | отдельная библиотека; даты приходят числамиa separate library; dates arrive as numbers |
| .csv | csv, pandas | кодировка (utf-8-sig или cp1251) и разделитель «;»encoding (utf-8-sig or cp1251) and the ";" separator |
# Колонки — по названию, а не по номеру / columns by name, not by number ws = load_workbook(path, read_only=True, data_only=True).active rows = list(ws.iter_rows(values_only=True)) head = [str(h).strip().lower() for h in rows[0]] i_qty = head.index("количество") # упадёт с понятной ошибкой, если колонку переименовали
Почему по названию: завтра в выгрузку добавят колонку «Канал», и колонка «Количество» уедет с третьего места на четвёртое. Скрипт с номером колонки молча сложит не то. Скрипт с названием либо найдёт правильную колонку, либо упадёт с понятной ошибкой.
Why by name: tomorrow a "Channel" column is added to the export and "Quantity" moves from third place to fourth. A script using the column number silently sums the wrong thing. A script using the name either finds the right column or fails with a clear error.
| ЛовушкаTrap | Как выглядитWhat it looks like | Что происходитWhat happens | ЧисткаCleaning |
|---|---|---|---|
| Число текстомNumber as text | «650», «1 200», «1200,5» | СУММ его не видит: август 4 000 → 3 350SUM ignores it: August 4,000 → 3,350 | убрать пробелы, запятую → точку, перевести в числоremove spaces, comma → dot, convert to a number |
| Неразрывный пробелNon-breaking space | «Озёрск » | город не находит пару в справочникеthe city finds no match in master data | заменить символ 160 обычным пробелом и обрезать краяreplace character 160 with a normal space and trim |
| Буква-двойникLook-alike letter | «Горный» с латинской «o»a Cyrillic city name with a Latin "o" | на экране одинаково, для компьютера — другой городidentical on screen, a different city to a computer | в русских словах менять латинских двойников на кириллицуin Cyrillic words replace Latin look-alikes with Cyrillic |
| «nan»"nan" | пустая ячейка в текстовой колонкеan empty cell in a text column | pandas читает её как текст «nan», и правило «если пусто» не срабатываетpandas reads it as the text "nan", and the "if empty" rule does not fire | считать «nan», «None», «-» пустымиtreat "nan", "None", "-" as empty |
| Код без парыCode without a match | 100199 | новый код, которого нет в справочнике: 600 бутылок пропадают при склейкеa new code missing in master data: 600 bottles vanish in the join | не чистить, а остановиться и дописать справочникdo not clean — stop and add it to master data |
# Excel (RU): ПОДСТАВИТЬ(СЖПРОБЕЛЫ(A2);СИМВОЛ(160);"") · ЗНАЧЕН(B2) # Excel (EN): SUBSTITUTE(TRIM(A2),CHAR(160),"") · VALUE(B2) # СЖПРОБЕЛЫ / TRIM символ 160 НЕ убирает — только обычные пробелы s = str(v).replace("\u00a0", " ").strip()
Старый и новый код одного товара сводятся к одному: 100103 → 100105. Таблицу только дописывают: строки не удаляют, иначе история старого кода выпадет.
The old and new code of one product map to one: 100103 → 100105. The table is only appended to: rows are not deleted, otherwise the old code history drops out.
В выгрузке бывает город склада, а планируем по территории. Справочник говорит, какая строка куда.
An export may have the warehouse city while we plan by territory. Master data says which row goes where.
Бутылки, коробки, литры. Перевод — по справочнику упаковок, у чая — килограммы, а не литры (урок 02-06).
Bottles, cases, litres. Conversion by the pack master data; tea is in kilograms, not litres (lesson 02-06).
# code_map.csv (UTF-8, разделитель «;»)
code;canon
100101;100101
100103;100105
100105;100105 | # | ПроверкаCheck | НормаNorm | Если нетIf not |
|---|---|---|---|
| 1 | Папка найдена, файлы прочитаныFolder found, files read | прочитано столько месяцев, сколько закрытоas many months read as are closed | СТОПSTOP |
| 2 | Файлов за месяцFiles per month | 1 | СТОП: оставить одинSTOP: keep one |
| 3 | Месяц закрытMonth closed | месяц ≤ последнего закрытогоmonth ≤ last closed | пропустить и записатьskip and log |
| 4 | ЧислаNumbers | «не число» — 0"not a number" — 0 | СТОП; числа текстом — перевести и записатьSTOP; numbers as text — convert and log |
| 5 | Без парыWithout a match | кодов и городов — 0codes and cities — 0 | СТОП: список и бутылкиSTOP: list and bottles |
| 6 | Сумма до = послеTotal before = after | разница 0difference 0 | СТОПSTOP |
Включайте поломки — такие, какие бывают в жизни, — и переключайте конвейер: «без проверок» или «с контрольными точками». Смотрите, что попадёт в дашборд и какая проверка что поймала.
Switch on breakdowns — the kind that happen in real life — and switch the conveyor: "no checks" or "with checkpoints". See what reaches the dashboard and which check caught what.
Скрипт с путём C:\Users\Имя\OneDrive - Компания\… работает только у автора. У коллеги другое имя пользователя, а библиотека OneDrive может называться иначе. Правила простые:
A script with the path C:\Users\Name\OneDrive - Company\… works only for its author. A colleague has another user name, and the OneDrive library may have a different name. The rules are simple:
Path(__file__).parent / "exports";Path(__file__).parent / "exports";# Общая папка — по содержимому, а не по имени / the shared folder by content, not by name
def find_root(markers, start=HERE):
for p in [start] + list(start.parents):
if all((p / m).exists() for m in markers):
return p
return None После каждого запуска скрипт пишет check_report.txt: какие файлы прочитаны, сколько строк, что почищено, что остановило. В дашборде рядом с датой обновления показывайте список реально прочитанных файлов — тогда видно, что сентябрь пришёл, а октябрь ещё нет.
After every run the script writes check_report.txt: which files were read, how many rows, what was cleaned, what stopped it. In the dashboard, next to the refresh date, show the list of files actually read — then it is visible that September arrived and October has not yet.
Иногда Python не нужен: коллега сама выбирает свой файл в дашборде, и страница читает его в браузере библиотекой SheetJS. Файл никуда не отправляется — он читается на её компьютере. Попробуйте: выберите любую небольшую книгу .xlsx или .csv, например книгу практики этого урока.
Sometimes Python is not needed: a colleague picks her own file in the dashboard and the page reads it in the browser with the SheetJS library. The file is not sent anywhere — it is read on her computer. Try it: pick any small .xlsx or .csv workbook, for example this lesson's practice workbook.
// Чтение выбранного файла в браузере / reading a picked file in the browser
input.onchange = async () => {
const wb = XLSX.read(await input.files[0].arrayBuffer());
const rows = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]], {header: 1});
}; Выбор файла даёт копию: дашборд может прочитать файл, но не может записать обратно. Для комментариев и правок, которые должны лечь в общий .xlsx, есть File System Access API: пользователь один раз «подключает» файл, и страница может перечитывать его и записывать изменения.
Picking a file gives a copy: the dashboard can read it but cannot write back. For comments and edits that must land in a shared .xlsx there is the File System Access API: the user "connects" a file once, and the page can re-read it and write changes.
| ЧтоWhat | Как естьThe facts |
|---|---|
| БраузерыBrowsers | Chrome и Edge (с версии 86); в Firefox и Safari нетChrome and Edge (since version 86); not in Firefox or Safari |
| УсловияConditions | только по действию пользователя (кнопка) и в безопасном контексте; браузер спрашивает разрешение на записьonly on a user action (a button) and in a secure context; the browser asks permission to write |
| БудущееFuture | у одного из производителей браузеров отрицательная позиция по стандарту — общим для всех браузеров API вряд ли станетone browser vendor has a negative standards position — the API is unlikely to become available in every browser |
// Подключить файл и записать изменения / connect a file and write changes if ("showOpenFilePicker" in window) { const [h] = await showOpenFilePicker({types: [{accept: {"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": [".xlsx"]}}]}); const wb = XLSX.read(await (await h.getFile()).arrayBuffer()); // ... добавить комментарий в лист / add a comment to a sheet ... const w = await h.createWritable(); await w.write(XLSX.write(wb, {type: "array", bookType: "xlsx"})); await w.close(); } else { // запасной путь: выбор файла + скачать новую версию / fallback: pick a file + download a new version }
Положите файлы в папку проекта из урока 05-19, рядом создайте папку exports и сложите туда свои выгрузки. Запуск: python load_exports.py. Скрипт читает только закрытые месяцы, чистит формат, останавливается на смысловых ошибках и пишет data.json и check_report.txt. Нужна только библиотека openpyxl.
Put the files into the project folder from lesson 05-19, create an exports folder next to them and put your exports there. Run: python load_exports.py. The script reads only closed months, cleans the format, stops on meaning errors and writes data.json and check_report.txt. It needs only the openpyxl library.
# -*- coding: utf-8 -*-
"""Загрузка ежемесячных выгрузок продаж в data.json для дашборда.
Load monthly sales exports into data.json for the dashboard.
Папка рядом со скриптом / folders next to the script:
exports/ Sales_2026-07.xlsx, Sales_2026-08.xlsx ... (колонки: Город | Код | Количество)
code_map.csv код в выгрузке;канонический код (export code;canonical code)
city_map.csv город в выгрузке;город справочника (export city;master city)
Запуск / run: python load_exports.py -> data.json + check_report.txt
python load_exports.py --force -> записать, даже если есть СТОП (не советую)
"""
import datetime
import json
import re
import sys
from pathlib import Path
from openpyxl import load_workbook
HERE = Path(__file__).resolve().parent # пути только от папки скрипта / paths only from the script folder
EXPORTS = HERE / "exports"
CLOSED = 202609 # последний закрытый месяц ГГГГММ / last closed month YYYYMM
NBSP = " "
# латинские буквы, похожие на русские / Latin letters that look like Cyrillic
TWINS = str.maketrans("ACEHKMOPTXaceopxy", "АСЕНКМОРТХасеорху")
HEAD = {"city": ("город", "city"), "code": ("код", "code"), "qty": ("количество", "qty", "quantity")}
def find_root(markers, start=HERE):
"""Ищет общую папку по уровням: поднимается вверх, пока не найдёт папку со всеми подпапками markers.
Finds the shared folder by levels: walks up until a folder contains all marker subfolders.
Имя библиотеки OneDrive у коллег разное, поэтому ищем по содержимому, а не по имени."""
for p in [start] + list(start.parents):
if all((p / m).exists() for m in markers):
return p
return None
def clean_text(s):
"""Убирает неразрывные пробелы и двойные пробелы; в русских словах меняет латинских двойников на кириллицу."""
s = str(s).replace(NBSP, " ").strip()
s = re.sub(r"\s+", " ", s)
if re.search("[А-Яа-яЁё]", s):
s = s.translate(TWINS)
return s
def to_number(v):
"""Число или None. Понимает «1 200», «1200,5», «1200.5». Пусто, «nan», «-» — не число."""
if isinstance(v, (int, float)):
return float(v)
if v is None:
return None
s = str(v).replace(NBSP, "").replace(" ", "").replace(",", ".")
if s.lower() in ("", "nan", "none", "-"):
return None
try:
return float(s)
except ValueError:
return None
def read_map(path):
"""Справочник «было;стало» из CSV (UTF-8). Ключ и значение чистятся так же, как данные."""
out = {}
for line in path.read_text(encoding="utf-8-sig").splitlines()[1:]:
if ";" in line:
a, b = line.split(";", 1)
out[clean_text(a)] = clean_text(b)
return out
def month_of(name):
m = re.search(r"(\d{4})-(\d{2})", name)
return int(m.group(1) + m.group(2)) if m else None
def pick_files(log):
"""Один месяц — один файл: при повторе берём самый новый по времени изменения и пишем об этом в отчёт."""
by_month = {}
for f in sorted(EXPORTS.glob("*.xlsx")):
if f.name.startswith("~$"):
continue # временный файл открытого Excel / temp file of an open Excel
mo = month_of(f.name)
if mo is None:
log.append(f"ПРОПУСК {f.name}: нет месяца в имени")
continue
by_month.setdefault(mo, []).append(f)
chosen, stops = {}, []
for mo, files in sorted(by_month.items()):
if mo > CLOSED:
log.append(f"ПРОПУСК {mo}: месяц не закрыт ({', '.join(x.name for x in files)})")
continue
files.sort(key=lambda x: x.stat().st_mtime)
if len(files) > 1:
stops.append(f"СТОП: за {mo} файлов {len(files)}: {', '.join(x.name for x in files)} - оставьте один")
chosen[mo] = files[-1]
return chosen, stops
def read_export(path):
ws = load_workbook(path, read_only=True, data_only=True).active
rows = list(ws.iter_rows(values_only=True))
head = [clean_text(h).lower() for h in rows[0]]
idx = {k: next(i for i, h in enumerate(head) if h in names) for k, names in HEAD.items()}
return [{k: r[i] for k, i in idx.items()} for r in rows[1:] if any(c is not None for c in r)]
def main():
start = datetime.datetime.now().timestamp()
log, stops = [], []
if not EXPORTS.exists():
print(f"СТОП: нет папки {EXPORTS}")
return 1
codes, cities = read_map(HERE / "code_map.csv"), read_map(HERE / "city_map.csv")
chosen, st = pick_files(log)
stops += st
total_in, data, bad_num, no_code, no_city = 0.0, {}, [], [], []
for mo, f in chosen.items():
rows = read_export(f)
log.append(f"ПРОЧИТАН {f.name}: строк {len(rows)}")
for r in rows:
q = to_number(r["qty"])
if q is None:
bad_num.append(f"{f.name}: {r}")
continue
if not isinstance(r["qty"], (int, float)):
log.append(f"ЧИСЛО ТЕКСТОМ {f.name}: {r['qty']!r} -> {q:g}")
total_in += q
code, city = clean_text(r["code"]), clean_text(r["city"])
code = codes.get(code)
city = cities.get(city)
if code is None:
no_code.append((f.name, r["code"], q))
continue
if city is None:
no_city.append((f.name, r["city"], q))
continue
key = f"{mo}|{city}|{code}"
data[key] = data.get(key, 0) + q
total_out = sum(data.values())
if bad_num:
stops.append(f"СТОП: не число в количестве, строк {len(bad_num)}")
if no_code:
stops.append(f"СТОП: кодов без пары {len(no_code)}, бутылок {sum(x[2] for x in no_code):g}: "
+ ", ".join(str(x[1]) for x in no_code))
if no_city:
stops.append(f"СТОП: городов без пары {len(no_city)}: " + ", ".join(repr(x[1]) for x in no_city))
if abs(total_in - total_out) > 1e-6 and not (no_code or no_city):
stops.append(f"СТОП: сумма до склейки {total_in:g} не равна сумме после {total_out:g}")
report = [f"Прочитано месяцев: {len(chosen)}", f"Сумма до склейки: {total_in:g}", f"Сумма после склейки: {total_out:g}"] + log + stops
(HERE / "check_report.txt").write_text("\n".join(report), encoding="utf-8")
for line in report:
print(line)
if stops and "--force" not in sys.argv:
print("Данные НЕ записаны. Исправьте и запустите снова. Отчёт: check_report.txt")
return 1
out = HERE / "data.json"
rows = [{"month": int(k.split("|")[0]), "city": k.split("|")[1], "code": k.split("|")[2], "qty": v} for k, v in sorted(data.items())]
out.write_text(json.dumps({"rows": rows, "files": [f.name for f in chosen.values()], "closed": CLOSED},
ensure_ascii=False, indent=1), encoding="utf-8")
if out.exists() and out.stat().st_mtime >= start - 1: # «готово» — только если файл правда записан
print(f"ГОТОВО: {out.name}, строк {len(rows)}, сумма {total_out:g}")
return 0
print("ОШИБКА: data.json не записан")
return 1
if __name__ == "__main__":
sys.exit(main()) Скрипт проверен на учебной папке: с ловушками он останавливается (задвоенный сентябрь, код 100199), после исправления пишет data.json на 12 000 бутылок.
The script was tested on the training folder: with traps it stops (duplicated September, code 100199); after fixing it writes data.json with 12,000 bottles.
| Как вы делаете руками в ExcelWhat you do by hand in Excel | Как это называетсяWhat it is called |
|---|---|
| копируете выгрузки в одну таблицуcopy exports into one table | извлечение (Extract)Extract |
| ищете ВПР-ом код в справочникеlook up a code with VLOOKUP | склейка (join, merge)join, merge |
| чистите пробелы и числа-текстclean spaces and numbers as text | преобразование (Transform)Transform |
| сохраняете итоговую таблицу для дашбордаsave the final table for the dashboard | загрузка (Load) — вместе ETLLoad — together ETL |
| сверяете итог с отчётомreconcile the total with a report | контроль качества данных (data validation)data validation |
Визуальная проверка обязательна. Контрольные точки ловят то, что вы заранее придумали. Новую беду ловит глаз: откройте дашборд после загрузки и посмотрите на график по месяцам. Ступенька, провал в последнем месяце, город с нулём — повод открыть check_report.txt раньше, чем показывать дашборд людям.
Visual check is a must. Checkpoints catch what you thought of in advance. A new problem is caught by the eye: open the dashboard after loading and look at the monthly chart. A step, a drop in the last month, a city with zero — a reason to open check_report.txt before showing the dashboard to people.
Каталог возможностей веб-платформы помечает File System Access как не базовую возможность: Chrome и Edge поддерживают, Firefox и Safari — нет, у одного производителя отрицательная позиция. Значит, в корпоративном Edge — пользуемся, но запасной путь держим всегда.
The web platform features catalogue marks File System Access as not Baseline: Chrome and Edge support it, Firefox and Safari do not, one vendor has a negative position. So in corporate Edge — use it, but always keep a fallback.
Скрипт вроде load_exports.py ассистент пишет за минуты (урок 02-08). Но контрольные точки придумываете вы: только вы знаете, что код 100103 — это 100105, а октябрь ещё не закрыт.
An assistant writes a script like load_exports.py in minutes (lesson 02-08). But you design the checkpoints: only you know that code 100103 is 100105 and that October is not closed yet.
Источники: web.dev, «How to open one or multiple files»; Web Platform features explorer, «File system access»; MDN, showOpenFilePicker().
Sources: web.dev, "How to open one or multiple files"; Web Platform features explorer, "File system access"; MDN, showOpenFilePicker().
Три случая из работы планера в компании-производителе напитков. Названия изменены.
Three cases from planning work at a beverage manufacturer. Names are changed.
В книге та же папка, что в тренажёре: лист «файлы», справочник, три выгрузки и лист «контроль» с проверками формулами. Статус «СТОП» значит: в дашборд не пускаем.
The workbook has the same folder as the simulator: the "files" sheet, master data, three exports and a "control" sheet with checks by formulas. Status "STOP" means: not into the dashboard.
1. Девять «СТОП» из одиннадцати строк: всё, кроме первой строки и строки «правда». СЧЁТ видит 7 чисел из 8 строк; число текстом 1; город без пары 1; код без пары 1 (600 бутылок); файлов за сентябрь 2; незакрытых файлов 1; сумма трёх выгрузок 11 350 вместо 12 000; «как есть» 14 950.
2. В августе в первой строке «650» записано текстом. Умножьте на 1 или перепишите числом: СЧЁТ станет 8, чисел текстом 0, сумма выгрузок 12 000.
3. Пятая строка августа — «Озёрск» с пробелом в конце. После замены городов без пары 0.
4. Дописать справочник: строка — настоящие продажи, удалять нельзя. Кодов без пары 0, бутылок без пары 0.
5. Брать один файл — самый свежий, второй убрать из папки (или в архив). Октябрь не брать, пока месяц не закрыт: лист «параметры», последний закрытый месяц 202609. Строки «файлов за сентябрь» и «незакрытых» исправляются в папке (в книге — удалите строки второго сентября и октября на листе «файлы»), а не формулами. Последняя строка «как есть» остаётся «СТОП» всегда — это память о том, что было бы без проверок.
1. Nine "STOP" out of eleven rows: all but the first row and the "truth" row. COUNT sees 7 numbers out of 8 rows; numbers as text 1; city without a match 1; code without a match 1 (600 bottles); files for September 2; open-month files 1; sum of the three exports 11,350 instead of 12,000; "as is" 14,950.
2. In August the first row has "650" stored as text. Multiply by 1 or retype as a number: COUNT becomes 8, numbers as text 0, sum of exports 12,000.
3. The fifth row of August is "Ozyorsk" with a trailing space. After replacing, cities without a match 0.
4. Add it to master data: the row is real sales, it must not be deleted. Codes without a match 0, bottles 0.
5. Take one file — the newest; remove the second from the folder (or archive it). Do not take October until the month is closed: sheet "parameters", last closed month 202609. The "files for September" and "open months" rows are fixed in the folder (in the workbook — delete the second September and the October rows on the "files" sheet), not with formulas. The last "as is" row always stays "STOP" — a reminder of what would happen without checks.
Кто что делает: я, Claude, Python?Who does what: me, Claude, Python?
Claude пишет скрипт по вашему заданию. Python читает файлы и считает. Вы проверяете: сверяете итог с отчётом, которому доверяете, и решаете, что делать с каждым «СТОП».
Claude writes the script from your brief. Python reads the files and calculates. You check: reconcile the total with a report you trust, and decide what to do with every "STOP".
Почему не Power Query?Why not Power Query?
Можно и Power Query — «из папки» он собирает все файлы (урок 05-13). Правила те же: один месяц — один файл, проверки после каждого шага. Python удобнее, когда шагов много и нужен отчёт о проверках.
Power Query works too — "from folder" combines all files (lesson 05-13). The rules are the same: one month — one file, checks after every step. Python is handier when there are many steps and a check report is needed.
Можно автоматически «угадывать» новый код по названию?Can a new code be "guessed" by name automatically?
Подсказать — можно, записать без человека — нет. Апсайз, новинка и смена кода выглядят похоже, а решения разные (урок 02-06).
Suggest — yes, write without a person — no. An upsize, a launch and a code change look alike, but the decisions differ (lesson 02-06).
Файл открыт у коллеги — что будет?A colleague has the file open — what happens?
Прочитать обычно можно, записать — нет. Скрипт должен сказать «не записано», а не «готово».
Reading usually works, writing does not. The script must say "not written", not "done".
Где хранить справочники?Where to keep the master data?
Рядом со скриптами в общей папке — тогда дашборд собирается с любого ноутбука, и у всех один справочник.
Next to the scripts in the shared folder — then the dashboard builds from any laptop and everybody has one master data.