До списку уроків
9 клас · Урок 4 з 16

Власна форма: від кліку до рядка в CSV

виконано0%
Крок 1

Що робимо сьогодні

Модуль I · IT Skills (Cloud Services & Documents) · тема 2, урок 2 з 2 · 120 хвилин

Досі твоя сторінка тільки показувала: історію, текст, таблицю. Сьогодні вона вперше почне приймати.

Однокласник відкриє її, відповість на кілька питань і натисне кнопку. А на твоєму диску, у файлі ~/data/responses.csv, з'явиться новий рядок.

Роль сторінки змінилася, і відповідальність теж. Тепер у тебе лежить файл із відповідями живих людей.

На минулому уроці ти спроєктував поля в fields.json. І розібрав, як улаштований CSV усередині. Сьогодні кожне поле з того файлу стане тегом у формі й колонкою в таблиці.

Після уроку ти вмієш

Слова <label>, fetch() і валідація поки нічого не означають — розберемо їх по черзі.

Умова, з якою ти працюєш На сервері вже працює сервіс /api/collect/<логін>. Він приймає POST, звіряє поля з твоїм fields.json — і дописує один рядок у ~/data/responses.csv. До кожного рядка він додає ще дві свої колонки. session_id — випадкова мітка, за якою видно, що це різні люди, а не одна людина двадцять разів. ts — час, коли відповідь надійшла; це скорочення від англійського timestamp, «часова позначка». Ти цей сервіс не пишеш і не запускаєш: твоя частина — форма, яка правильно до нього звертається.
Що ти здаєш і що ніколи не здаєш По FTP їдуть три легкі текстові файли: form/index.html, form/stats.json і оновлений fields.json. responses.csv не завантажується вручну ніколи — його пише сам сервіс у приватну теку ~/data/, яку nginx по HTTP не віддає.

Хід уроку

ЕтапХвЩо робимо
Повторення10Звіряємо fields.json з трьома дослідницькими питаннями
Теорія: форма і поля20label, name проти id, типи input
Верстка форми30Конструктор → готовий код → form/index.html
Перерва10
Шлях даних і fetch15POST у DevTools, Content-Type, preventDefault()
Чому валідації мало10Учитель шле POST повз форму — рядок з'являється
Карусель відповідей15Заповнюємо форми одне одного, 10+ рядків
Підсумки і приватність10stats.json, аудит ~/www, check 04
Разом120
Чому тека зветься form, а урок — четвертий Теки практики звуться за темою, а не за номером уроку. Тема 2 — це уроки 3 і 4, і обидва складають роботу в одну теку ~/www/form/. Не шукай lesson-04: її немає.

У всіх прикладах на цій сторінці логін — ivan. Скрізь, де він трапляється, підставляй свій.

Крок 2

Анатомія форми: чотири слова, які треба знати напам'ять

Тег <form>, атрибут name, атрибут id і тег <label>. Плутанина між ними — джерело половини поламаних анкет.

Форма — це конверт. Тег <form> каже, куди надсилати (action) і яким способом (method).

У конверт кладеться не все. Туди потрапляє лише те, що має атрибут name. Поле без name не поїде нікуди — навіть заповнене.

найкоротша робоча форма
<form action="/api/collect/ТВІЙ-ЛОГІН" method="post"> <label for="f_sleep">Скільки годин ти спав минулої ночі</label> <input id="f_sleep" name="sleep_hours" type="number" min="0" max="14" required> <button type="submit">Надіслати</button> </form>

name проти id: два різні світи

Ці атрибути схожі й часто збігаються за значенням. Тому їх і плутають. Але живуть вони в різних місцях і потрібні різним читачам.

nameid
Хто читаєсервербраузер: CSS, JS, <label for>
Куди потрапляєу тіло запиту, далі — колонка в responses.csvнікуди не їде, лишається на сторінці
Має бути унікальнимні: у радіогрупі name навмисно спільнийтак, на всю сторінку — інакше label знайде не те поле
Якщо забутиполе мовчки зникає з данихклік по підпису перестає працювати
Правила записулише латиниця, цифри, _ — це заголовок CSVте саме, зазвичай із префіксом f_
Пастка з минулого уроку name="Скільки годин" здається зручним, але саме цей рядок стане заголовком колонки в CSV. Пробіл усередині і кирилиця перетворюють заголовок на мінне поле: одні програми його прочитають, інші — ні. Пиши sleep_hours, а людський підпис клади в <label>.

Чому <label> обов'язковий, а не «бажаний»

Підпис поля — не декорація. Він виконує три роботи одночасно, і жодну з них placeholder виконати не може.

Перша робота — для тих, хто сторінки не бачить. Незрячі користувачі ходять по сайтах із програмою, яка читає вміст екрана вголос. Таку програму звуть скрінрідером, від англійського screen reader.

  1. Доступність. Скрінрідер озвучує поле саме за пов'язаним <label>. Без нього людина чує «поле вводу» — і все.
  2. Клік по підпису. Пов'язаний підпис збільшує зону влучання. Для radio і checkbox на телефоні це різниця між «поставив галочку» і «промазав утретє».
  3. Підпис не зникає. Питання видно й тоді, коли поле вже заповнене — а користувач перечитує відповідь перед надсиланням.

Зв'язок робиться однією парою: <label for="f_sleep"><input id="f_sleep">. Ці значення мають збігтися символ у символ.

підпис — те, що читає людина і скрінрідер <label for="f_sleep" >Скільки годин ти спав?</label> однакове значення — саме воно зв'язує підпис із полем <input type="number" name="sleep" id="f_sleep" min="0" max="14" required > type Яку клавіатуру й підказку дасть браузер — і що він перевірить сам. name Ім'я значення в даних. Стане колонкою CSV. Без нього поле не поїде взагалі. id Адреса в DOM. Потрібна двом: label for і JS. На сервер не потрапляє. min / max Межі числа. Браузер не дасть надіслати 25 годин сну — але curl дасть. required Порожнє поле — форма не піде. Став лише там, де відповідь справді потрібна.
Схема 1. Один тег <input> і п'ять його атрибутів. Зелений блок — єдиний, без якого поле не існує для сервера.

placeholder — це приклад, а не назва

placeholder="7" у полі про сон — це добре. Він показує, у якому вигляді чекають відповідь.

А от placeholder="Скільки годин ти спав" замість підпису — погано. По-перше, текст зникне з першим натиснутим символом. По-друге, у частині браузерів його не озвучує скрінрідер.

По-третє, підказку в полі малюють блідо-сірим. А наскільки текст відрізняється від тла, міряють числом. Беруть яскравість літер, яскравість фону — і ділять одну на одну.

Правила доступності вимагають, щоб для звичайного тексту вийшло щонайменше 4,5:1. Тобто літери мають бути яскравіші за тло у чотири з половиною раза. Бліде сіре на білому цю межу зазвичай не проходить.

Питання 1

Ти зробив поле лише з placeholder="Скільки годин ти спав", без <label>. Однокласник зі скрінрідером не зрозумів, що вводити, а сусід почав вводити й забув питання. Що виправляти?

Питання 2

У responses.csv з'явилася колонка з правильним заголовком, але в усіх 12 рядках вона порожня. Поле в розмітці є, у браузері воно заповнюється. Що не так?

Крок 3

Один тег, вісім типів полів

Тип поля — це домовленість із трьома сторонами одразу: з людиною (яку клавіатуру вона побачить), з браузером (що він перевірить) і з таблицею (яким виявиться значення в CSV).

ТипЩо бачить людинаЩо приходить у CSVКоли брати
textзвичайне однорядкове полерядок як єкоротка вільна відповідь: назва, слово, ім'я предмета
emailклавіатура з @, браузер перевіряє форматрядоклише якщо пошта справді потрібна — а в анонімній анкеті вона не потрібна
numberлише цифри, стрілки, min/max/stepрядок, який читається як числогодини, кількість, оцінка, бали
dateкалендар рідною мовою системизавжди 2026-09-02 (ISO)дата, про яку відповідають
radioкружечки, обрати можна одинодне значення valueдо 5 варіантів, які видно всі одразу
checkboxквадратики, обрати можна кількакілька значень — склеюємо через ;«обери все, що підходить»
selectвипадний списокодне значення value6 і більше варіантів, коли місця мало
textareaвелике поле на кілька рядківрядок, часто з комами і переносамирозгорнута думка — але звести її формулами майже неможливо
Чому type="date" вирішує проблему минулого уроку Тоді ти бачив, як «01.02» перетворюється то на дату, то на число — залежно від налаштувань програми. Поле date закриває це назавжди: у дані завжди приїжджає YYYY-MM-DD, незалежно від того, що людина бачила в календарі. Такі рядки сортуються як текст і дають правильний порядок.

Радіогрупа тримається на спільному name

Три радіокнопки стають однією групою через однаковий name. Більше їх ніщо не пов'язує. А от value в них мають бути різні: саме воно поїде в CSV.

Даси різні name — і це вже три незалежні перемикачі. Людина зможе ввімкнути всі три одразу.

радіогрупа й вибір зі списку
<fieldset> <legend>Як ти почуваєшся зранку</legend> <label><input type="radio" name="morning" value="виспався" required> виспався</label> <label><input type="radio" name="morning" value="нормально"> нормально</label> <label><input type="radio" name="morning" value="розбитий"> розбитий</label> </fieldset> <label for="f_bedtime">О котрій ти зазвичай лягаєш</label> <select id="f_bedtime" name="bedtime" required> <option value="">— обери —</option> <option value="до 22:00">до 22:00</option> <option value="після 00:00">після 00:00</option> </select>

Три деталі, на яких спотикаються:

Що браузер уміє перевіряти сам

АтрибутЩо робитьПриклад
requiredне дає надіслати порожнє полеrequired
min / maxмежі для number, date, rangemin="0" max="14"
stepкрок числа: цілі, півгодиниstep="0.5"
maxlengthобмежує довжину текстуmaxlength="120"
patternрегулярний вираз для textpattern="9[АБ]"
type="email"перевіряє, що є @ і крапка після нього
Автоперевірка вимагає одного такого поля check 04 шукає у твоїй формі щонайменше одне поле з min/max або pattern. Найпростіше — поле number із розумними межами: годин сну не буває 30, оцінка не буває 15.
Питання 3

Ти зробив три <input type="radio"> для варіантів «добре / так собі / погано», але в анкеті можна позначити всі три одночасно. Що виправити?

Крок 4

Конструктор форми: збери зліва — забери код знизу

Ліворуч ти проєктуєш поля, праворуч одразу видно готову форму очима однокласника. Унизу синхронно збирається код, який можна вставити у свій form/index.html.

Натисни «Надіслати» в правій колонці, не заповнивши обов'язкове поле. Побачиш рівно те, що побачить однокласник.

Тепер заповни й надішли. Унизу правої колонки з'явиться тіло запиту і рядок, який ляже в CSV. Це весь шлях даних, стиснутий до одного кліку.

Твій логін на сервері
action = /api/collect/
Готові набори — можна взяти й переробити
Поля анкети

Що бачить однокласник
Що поїде на сервер
Заповни форму зліва й натисни «Надіслати».
Що ляже в responses.csv

Ця кнопка не відкриває твою сторінку взагалі — вона складає тіло запиту руками. Подивись, що при цьому потрапить у CSV.

form/index.html — згенерований код
Що робити з цим кодом Скопіювати, вставити у свій form/index.html між <body> і </body>, додати <h1>, один абзац «що я збираю і навіщо» і посилання назад на головну. Далі — правити руками: конструктор дає каркас, а не готову здачу.
Питання 4

Ти поставив required на всі дев'ять полів. Половина класу не змогла надіслати анкету, і в CSV лише 5 рядків замість 15. Що пішло не так?

Крок 5

Дорога від кліку до рядка

П'ять станцій, дві з яких на твоєму сервері. Якщо десь у CSV з'явився не той рядок — ламалося на одній із цих п'яти.

у браузері однокласника на твоєму сервері 1 2 3 4 5 Клік Браузер Запит Сервіс CSV Однокласник натискає «Надіслати» у твоїй формі. Збирає значення полів за їхніми name і пакує їх у JSON. POST /api/collect/ ivan + Content-Type Перевіряє поля, екранує коми, додає session_id і ts. ~/data/ responses.csv +1 рядок. Тека приватна. відповідь 200 OK — і тільки тоді форма пише «дякую, відповідь записано»
Схема 2. Твоя робота — станції 1—3. Станції 4 і 5 робить сервіс, який уже стоїть на сервері.
Звідки береться заголовок CSV Сервіс бере назви колонок не з форми, а з твого fields.json — саме тому файл із минулого уроку треба тримати актуальним. Якщо в формі з'явилося нове поле, а в fields.json його немає, значення просто нікуди буде покласти.

GET проти POST: де саме лежать дані

method у формі — це не «швидше або надійніше». Це два різні повідомлення серверові. GET означає «покажи мені щось». POST — «зміни щось у себе». Дописати рядок у CSV — це зміна, тому POST.

GET — дані лежать просто в адресі https://ivan.school.ua/form/ ?mood=добре&sleep=7 Повторний захід за цією адресою з історії надішле ту саму відповідь удруге. Цей рядок потрапляє цілком: • в історію браузера • у закладки й автодоповнення • у логи сервера і проксі • на чужий екран через плече POST — дані лежать у тілі запиту https://ivan.school.ua/form/ чисто POST /api/collect/ivan HTTP/1.1 Content-Type: application/json {"mood":"добре","sleep":7} Тіло не видно в адресі, не потрапляє в історію й не кешується браузером. Але POST нічого не шифрує. Від чужих очей у мережі захищає тільки HTTPS.
Схема 3. Той самий набір відповідей у двох способах надсилання. Приклад адреси й тіла — з ivan; підстав свій логін.

Content-Type: чим позначено вміст запиту

Тіло запиту — просто послідовність байтів. Сервер не вгадує, як їх читати. Йому це каже заголовок Content-Type — етикетка на посилці.

Помилишся в етикетці — сервіс розбере тіло неправильно. І відповість 400, навіть якщо дані бездоганні.

Content-TypeЯк виглядає тілоКоли
application/x-www-form-urlencoded mood=%D0%B4%D0%BE%D0%B1%D1%80%D0%B5&sleep=7 типове для <form> без JS: браузер робить це сам
multipart/form-data тіло розрізане на частини межею boundary, у кожної свій заголовок єдиний спосіб надіслати файл: байти картинки не влізуть у рядок з &
application/json {"mood":"добре","sleep":7} наш випадок: зручно збирати в JS, зберігає вкладеність і числа

Останні два розрізнити просто. multipart громіздкіший, зате вміє нести будь-які байти — з ним ідуть файли. json дешевий і читабельний, але тільки текст. Наш сервіс файлів не приймає, тож беремо application/json.

Питання 5

Учень залишив method="get". Форма ніби працює, але після надсилання в адресному рядку видно ?mood=погано&sleep=4, і ця адреса залишилася в історії шкільного комп'ютера. У чому проблема?

Питання 6

Твій fetch() шле тіло JSON.stringify(data), але заголовок Content-Type ти не поставив. Сервіс відповідає 400 Bad Request. Чому?

Крок 6

fetch(): надіслати, не перезавантажуючи сторінку

Форма вміє надсилатися сама, без жодного рядка JS. Але тоді браузер піде на іншу сторінку, і людина побачить голу відповідь сервера замість твого «дякую».

Подія submit за замовчуванням означає «браузере, надішли форму сам». Ми цю дію скасовуємо через e.preventDefault().

Далі запитом розпоряджаємося самі. Збираємо значення, шлемо fetch() і показуємо повідомлення просто на місці.

обробник форми — той самий, що генерує конструктор
const form = document.getElementById('poll'); const msg = document.getElementById('msg'); form.addEventListener('submit', async (e) => { e.preventDefault(); // без цього сторінка перезавантажиться const fd = new FormData(form); // бере всі поля, у яких є name const data = {}; for (const key of new Set(fd.keys())) { const vals = fd.getAll(key); // checkbox-група дає кілька значень data[key] = vals.length > 1 ? vals.join(';') : vals[0]; } const btn = form.querySelector('button[type=submit]'); btn.disabled = true; // щоб не надіслати двічі try { const res = await fetch(form.action, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); msg.textContent = res.ok ? 'Дякую, відповідь записано.' : 'Сервер відмовив: ' + res.status; } catch (err) { msg.textContent = 'Немає зв’язку із сервером.'; } finally { btn.disabled = false; } });

Чотири деталі, які легко пропустити

Порожня галочка просто не приїде Незазначений checkbox не потрапляє у FormData взагалі — не як порожній рядок, а як відсутнє поле. Сервіс підставить у цю колонку порожнє значення, і це нормально. А от undefined у CSV — ознака того, що ти намагався читати поле вручну замість FormData.
Питання 7

Ти повісив fetch() на подію submit, але сторінка щоразу перезавантажується, а рядок у CSV то з'являється, то ні. Чого бракує?

Крок 7

Валідація в браузері — зручність, а не захист

required і min/max живуть у твоїй сторінці. А сторінку можна просто не відкривати.

Браузер перевіряє все для людини. Щоб вона не надіслала анкету з порожнім полем і не витратила час дарма. Робота корисна. Але до того, що приїде на сервер, вона стосунку не має.

Запит до /api/collect/ivan може скласти будь-хто й будь-чим. Терміналом, іншою сторінкою, скриптом на п'ять рядків. Сервер цих запитів не розрізняє: він бачить метод, заголовки й тіло — і більше нічого.

Твоя сторінка в браузері required, type="email", min/max не пустили порожнє поле — людині зручно й зрозуміло. curl, DevTools, скрипт Сторінка навіть не відкрита. Тіло запиту складає людина руками. Перевірок — нуль. Сервіс бачить лише HTTP-запит метод · заголовки · тіло Хто його зібрав і чи була взагалі сторінка — невідомо. Сервер перевіряє сам 400 Bad Request. Рядок у CSV не з'явився, підсумки не зіпсовані. Сервер не перевіряє 200 OK на що завгодно. У CSV лягає порожній або сміттєвий рядок назавжди. Обидва запити для сервера однакові. Різницю робить тільки те, що сервер перевіряє сам.
Схема 4. Ліворуч — два джерела одного запиту, праворуч — два можливі сервери. Твоя форма впливає лише на верхній лівий блок.

Перевір це власноруч

Ця команда не відкриває твоєї сторінки взагалі. Подивись у responses.csv: якщо там на один рядок більше, ти щойно довів собі, що перевірка в браузері нікого не зупиняє.

Git Bash, macOS, Linux
$ curl -i -X POST http://91.219.61.4/s/ТВІЙ-ЛОГІН/api/collect/ТВІЙ-ЛОГІН \ -H "Content-Type: application/json" \ -d '{"sleep_hours":"","morning":"!!!"}' HTTP/2 400 {"ok":false,"error":"sleep_hours is required"}
Windows: curl — це не curl У Windows PowerShell 5.1 слово curl — псевдонім до Invoke-WebRequest, і ключі -X, -H, -d він розуміє зовсім інакше. Пиши або curl.exe, або рідну команду PowerShell.
Windows PowerShell 5.1
$ Invoke-RestMethod -Uri "http://91.219.61.4/s/ТВІЙ-ЛОГІН/api/collect/ТВІЙ-ЛОГІН" -Method Post -ContentType "application/json" -Body '{"sleep_hours":"","morning":"!!!"}'

Що перевіряє сервер, коли до нього прилетів рядок

ПеревіркаЩо ловить
обов'язкові поля не порожнінапівзаповнені анкети
число справді число і в межах min/max"sleep":"багато" і "sleep":900
значення radio/select є у списку варіантіввигадані відповіді, яких у формі не було
довжина тексту обмеженавставлений роман на 40 000 символів
невідомі поля відкидаютьсяколонки, яких немає у fields.json
обмеження частоти запитіводин «дотепник», який надіслав 300 рядків
екранування для CSVкому, лапки й перенос рядка всередині відповіді

Останній пункт — прямо з минулого уроку. Людина написала читаю, коли не сплю. У файл має лягти "читаю, коли не сплю", у лапках. Інакше рядок роз'їдеться на дві колонки.

Наш сервіс ставить ці лапки сам. Але правило знати треба: чекер звіряє кількість колонок у кожному рядку.

Питання 8

Учитель, не відкриваючи твоєї сторінки, надіслав curl-ом POST із порожнім sleep_hours і morning="!!!". У CSV з'явився рядок — хоча у формі стоять required і min="0" max="14". Що це доводить?

Крок 8

Приватність: що взагалі можна питати

З цієї хвилини в тебе на диску лежать відповіді живих людей, які тобі повірили. Це не метафора — це файл, який можна відкрити.

Шкільна анкета збирає думки, звички й вподобання. Скільки спиш, який предмет любиш, скільки часу в телефоні. Цього досить для будь-якого дослідницького питання з минулого уроку.

Мінімізація даних: одне правило замість довгого списку

Збирай рівно те, без чого не відповіси на своє питання. Це принцип мінімізації даних, і він записаний у законі. В українському — «Про захист персональних даних». І в європейському: його звуть GDPR, скорочення від General Data Protection Regulation. Українською — «загальний регламент захисту даних».

Формулювання там різні, суть одна. Дані мають бути адекватними й не надмірними щодо мети.

Практичний тест для кожного поля, який займає п'ять секунд:

Тест одного питання «Яке з моїх трьох дослідницьких питань я не зможу закрити без цього поля?» Якщо відповіді немає — поле не потрібне. Не «поки що не потрібне» і не «раптом згодиться»: не потрібне.

Чому «про всяк випадок» — найгірша причина

Телефон і адреса не змінюють жодного підсумку в stats.json. Середнє за годинами сну від них не зрушиться. Зате вони змінюють статус усього файлу. Була анонімна табличка про сон — став список контактів однокласників із їхніми звичками.

Чого не питаємо в шкільній анкеті — ніколи Таких полів чотири групи. Нижче про кожну сказано, що саме ламається, якщо поле все-таки з'явиться у формі.

Перша група — те, що називає людину. Прізвище й повне ім'я, фото, повна дата народження. Варто додати одне таке поле — і файл перестає бути анонімним. Усі інші відповіді в рядку тепер прив'язані до конкретного однокласника, і твоя табличка про сон стала досьє.

Друга — те, чим людину знаходять у реальному світі. Номер телефону, нікнейм у месенджері, домашня адреса, маршрут до школи. Жодне з цих полів не змінить твоєї діаграми. Зате разом вони кажуть, де ця людина буває й о котрій виходить з дому.

Третя — те, чим відмикають чужі речі. Паролі, логіни, номер картки, серія паспорта, ІПН. Останнє — індивідуальний податковий номер: за ним людину впізнає держава. Такі номери не питають у жодній анкеті, і шкільна тут не виняток.

Четверта — те, за що однокласника почнуть цькувати. Здоров'я, релігія, політичні погляди, що коїться вдома. Закон називає це чутливими даними й вимагає для них особливого захисту. Діаграми ти з них не побудуєш, а от посварити клас вони здатні за один день.

Замість імені сервіс сам ставить session_id — випадкову мітку на кожну відповідь. Її досить, щоб побачити «це різні люди». І замало, щоб зрозуміти, хто саме.

Три речі, які треба зробити обов'язково

  1. Сказати, що збираєш і навіщо. Один абзац над формою: «Анкета анонімна, відповіді потрібні для діаграм у моїй презентації, імена не питаю». Це не формальність — це те, на що людина погоджується.
  2. Лишити право промовчати. Не роби required скрізь; там, де питання чутливе, додай варіант «не хочу відповідати».
  3. Прибрати на прохання. Якщо однокласник передумав — відкриваєш responses.csv і видаляєш його рядок. Без обговорень.

Чому responses.csv живе в ~/data/

Тека ~/www/ — це вітрина: усе, що поклав, віддає в інтернет nginx. Файл звідти відкриє будь-хто, хто вгадає адресу. Його ще й може проіндексувати пошуковик.

Тека ~/data/ — навпаки, шухляда. Вона має права 750 і лежить поза веб-корінням. Шляху до неї у веб-сервера просто немає.

перевір сам: приватна тека мовчить
$ curl -s -o /dev/null -w "%{http_code}\n" http://91.219.61.4/s/ТВІЙ-ЛОГІН/data/responses.csv 404 $ curl -s -o /dev/null -w "%{http_code}\n" http://91.219.61.4/s/ТВІЙ-ЛОГІН/form/ 200

Чекер шукає копію файлу в ~/www не за іменем, а за sha256. Тому перейменувати responses.csv на data2.txt не вийде. Так зроблено навмисно: правило про приватні дані не має обходитися хитрістю.

Питання 9

Однокласниця пропонує додати в анкету телефон — «раптом треба буде уточнити відповідь». Що відповісти?

Питання 10

Щоб швидше дивитися результати, учень скопіював responses.csv у ~/www/form/. Що сталося?

Крок 9

Підсумки: з рядків у stats.json

Останній крок теми — звести зібране в три числа на кожне поле. Саме цей файл на наступному уроці стане діаграмами.

~/data/responses.csv · приватна тека · 12 рядків session_id,ts,sleep_hours,morning,subject s-01,2026-09-02T10:14,7,виспався,математика s-02,2026-09-02T10:15,5,розбитий,історія s-03,2026-09-02T10:17,8,виспався,біологія … ще 8 рядків … s-12,2026-09-02T10:41,6,нормально,хімія заголовок береться з fields.json рахують формули ~/www/form/stats.json · публічний, без жодної відповіді { "count": 12, ← скільки рядків "by_field": { ← числові поля "sleep_hours": { "mean": 6.75, "min": 4, "max": 9 } }, "distribution": { ← категорійні "виспався": 7, "нормально": 4, "розбитий": 1 } 7 + 4 + 1 = 12: розподіл завжди має сходитися з count
Схема 5. Числа в прикладі узгоджені між собою: розподіл дає рівно 12 відповідей. Середнє 6,75 порахувати з чотирьох показаних рядків не можна — воно рахується з усіх дванадцяти.

Що в цьому файлі має бути

КлючЩо міститьЗвідки береться
countскільки всього відповідейкількість рядків даних, без заголовка
by_fieldmean, min, max на кожне числове полеAVERAGE, MIN, MAX по колонці
distributionскільки разів трапився кожен варіантCOUNTIF по колонці radio/select
Числа рахуються, а не пишуться Чекер перерахує responses.csv сам і звірить із твоїм файлом із допуском 0,01. Округлив 6,7532 до 6,75 — зарахує. А от 7, поставлене «бо приблизно так», у цей допуск не влізе, і критерій лишиться червоним, поки не перерахуєш.
PowerShell і кодування: пастка, на якій горять щороку Якщо ти збираєш stats.json скриптом і зберігаєш вивід у файл, Windows PowerShell 5.1 запише його в UTF-16, і чекер (він читає JSON як UTF-8) скаже «невалідний JSON» без жодного пояснення.
як зберігати правильно
# Windows PowerShell 5.1 python make_stats.py | Out-File -Encoding utf8 stats.json # Git Bash, macOS, Linux python make_stats.py > stats.json
Найнадійніше — не перенаправляти вивід узагалі, а зберегти файл із редактора, вибравши кодування UTF-8.
Питання 11

У stats.json ти написав "count": 15, бо стільки відповідей планував зібрати, а в CSV їх 11. Що буде?

Крок 10

Практика: форма, дані, підсумки

Кроки 1—5 — у себе на комп'ютері, далі — FTP і сервер. Галочки зберігаються, сторінку можна закрити.

Межа, яку не переходять По FTP їдуть тільки index.html, stats.json і fields.json — легкий текст. responses.csv пише сам сервіс у ~/data/. Ти його не заливаєш, не копіюєш у ~/www/ і не відкриваєш «щоб показати другові».
Крок 11

Автоперевірка

Чекер не читає відповідей однокласників. Він дивиться структуру форми, звіряє stats.json із CSV і перевіряє, що приватне лишилося приватним. Спроби не обмежені.

Демонстраційний режим: результат згенеровано для показу. На сервері ця кнопка викликає POST /api/check, який запускає check 04 від імені учня.

Що вчитель дивиться на твоєму екрані

Крок 12

Шкала виконання

Це те, що бачить учитель, коли виставляє оцінку.

СкладникВагаРезультат
Виконано
0%
Рекомендований бал за 12-бальною

Чотири речення, які варто запам'ятати

Де найчастіше помиляються

Домашнє завдання

Довести кількість відповідей до 15 і перерахувати stats.json. Потім дописати в form/README.md три речення рефлексії.

Одне з них обов'язкове: яке поле ти хотів додати й не додав. І яке дослідницьке питання від цього не постраждало.

Джерела

Звідки взяті правила

Усе, що на цій сторінці сказано про форми, HTTP і приватність, — зі специфікацій і офіційної документації. Перевіряти дозволено й корисно.

Повний список із поясненнями, що звідки взято, — у файлі urok-04-джерела.md поруч із цією сторінкою.