Skip to content

Repository files navigation

Универсальная платформа для опросов

Конфигурируемая платформа для персональных опросов на GitHub Pages с Google Sheets в роли базы данных. Содержание опроса хранится в таблице: сотрудники и токены, активные вопросы, варианты ответов, правильные ответы, принципы, промпт и результаты. Frontend показывает актуальный набор, сохраняет прогресс и формирует персональный протокол. Текущая конфигурация посвящена использованию ИИ, но код не привязан к этой теме или к фиксированному числу вопросов.

Браузер (GitHub Pages)  ──JSONP──►  Apps Script Web App  ──►  Google Sheet (единый источник)

Что и откуда берётся (таблица — база данных проекта)

Данные Лист Как используется
Сотрудники и коды Employees вход по Токен, статусы Не использован / Частично / Использован, итоги, персональный флажок Таймер
Итоги прошлых волн Employees_history архив: Волна, ID, ФИО, отдел, должность, все итоговые колонки, дата архивации
Тексты вопросов Questions ID вопроса / Вопрос / чекбокс Включен / Ограничение (по ID пользователя)
Варианты, правильный ответ, теги Answers Вариант A–D, Номер правильного ответа, Тег A–D
Принципы (7) Principles список на приветствии
Настройки Settings prompt, wave, min_answers (см. ниже)
Ответы (лог) Results пишется после каждого ответа, колонка Волна; там же заметки проверяющему
Признаки вовлечённости Usages связка по ID: агент, репозиторий, артефакт, мероприятия, использование инструмента

Ключи листа Settings:

Ключ Значение Зачем
prompt текст промпт, который сотрудник копирует вместе с протоколом
wave число номер текущей волны; пишется в колонку Волна листа Results
min_answers {"adoption":6,"interest":6,"safety":4} минимум ответов на характеристику: единый источник для frontend, backend и аналитики

Меняете вопрос/вариант/принцип/промпт в таблице → опрос обновляется при следующем успешном входе. js/questions.js — локальная генерируемая копия для разработки и не подключается и не публикуется на GitHub Pages. Публичные тесты используют минимальный контракт вариантов из tests/fixtures/profile-options.json. Тексты в js/config.js остаются офлайн-фолбэком для принципов и промпта.

getSurvey и отдельный getQuestions возвращают вопросы только после проверки пары ID + токен; использованный токен доступа к содержимому опроса не получает.

Как работает платформа

  1. Сотрудник вводит персональный токен; backend проверяет его по листу Employees.
  2. Backend читает сверху вниз только отмеченные строки Questions и связывает каждую с Answers по ID вопроса.
  3. Тип и механика вопроса определяются данными, а не жёстким списком в коде:
Данные вопроса Тип Поведение
Заполнен Номер правильного ответа оцениваемый блиц-вопрос учитывается в проценте правильных; при включённом сотруднику таймере даётся 20 секунд
В тегах вариантов есть level-* самооценка выбранный тег преобразуется в балл через CONFIG.selfScore
Нет правильного ответа и level-* обычный вопрос сохраняется выбранный или собственный ответ без таймера
  1. После первого сохранённого ответа токен получает статус Частично. Переходы по вопросам при этом не блокируются сетевыми запросами.
  2. На финале frontend передаёт backend весь текущий комплект ответов. Backend проверяет полноту, дописывает отсутствующие строки в Results, сохраняет итоги и последним шагом ставит Использован.
  3. Протокол строится из того же активного набора: каждый новый включённый вопрос и текст выбранного для него варианта автоматически попадают в скачиваемый файл.

Чекбокс Включен позволяет подготовить следующий набор в тех же листах и переключить опрос без удаления старых вопросов, вариантов или результатов.

Текущая конфигурация опроса об ИИ

511 самооценка (первый) → 101–110 отношение → 201–210 интерес → 301–310 навыки (таймер 20 сек, оцениваются) → 411 безопасность (последний). Сейчас включены 32 вопроса; runtime не привязан к этому числу и берёт включённые строки сверху вниз. Перед первым вопросом блока навыков показывается предупреждение; отсчёт начинается только после нажатия «Понятно».

В Employees флажок Таймер управляет ограничением индивидуально: установлен — блиц идёт с 20-секундным отсчётом, снят — предупреждение и таймер не показываются. Если колонки нет, backend сохраняет прежнее поведение и включает таймер.

Правильные ответы блока «Навыки» живут только в таблице (AnswersНомер правильного ответа) и намеренно не публикуются здесь: ключ, лежащий в открытом репозитории, обесценивает блок. Backend отдаёт его frontend только вместе с вопросами и только авторизованному сотруднику, а в скачиваемый протокол правильные ответы не попадают.

Персональные наборы вопросов

Колонка Ограничение (по ID пользователя) на листе Questions решает, кому вопрос показывается. Пусто — всем. Так можно давать разным респондентам разные наборы, одним укорачивать опрос, другим удлинять.

В ячейке Кому показывается
(пусто) всем
2,5,7 только этим ID
2-8 диапазон ID
!5 или -5 всем, кроме 5
кроме 5, 7 всем, кроме 5 и 7 (кроме действует до конца строки)
2-8, !5 2, 3, 4, 6, 7, 8
все / никому явно всем / никому

Набор считается один раз на запрос и применяется везде: выдача вопросов, приём ответов (чужой вопрос в Results не попадёт) и проверка полноты при завершении — иначе человек с укороченным набором не смог бы закончить опрос. Опечатка не угадывается: getQuestions падает с явным сообщением.

Осторожно с минимумом. Если у человека в наборе осталось меньше вопросов, чем задано в min_answers, характеристика не считается и портрет выходит пустым. Питают характеристики: принятие — 8 вопросов (минимум 6), интерес — 8 (6), безопасность — 5 (4); 108 и 110 работают сразу на две. Без последствий для баллов режутся 204, 206, любые 301–310 и 511. Проверить заранее — auditQuestionSets().

Волны

Номер текущей волны — Settings ▸ wave, он пишется в колонку Волна листа Results. Вся работа с ответами идёт только внутри текущей волны: строки прошлых волн не читаются, не обновляются и не мешают завершению. Без колонки Волна поведение прежнее — одна общая куча ответов, и validateSurvey() об этом предупреждает.

Разовая настройка: setupWaveColumn() — добавит колонку и проставит существующим строкам номер текущей волны.

Перезапуск волны:

  1. Снять статистику по текущей волне (см. раздел про скилл ниже).
  2. previewNewWave() — показывает, что изменится, ничего не трогая.
  3. startNewWave() — архивирует итоги в Employees_history, очищает итоговые колонки в Employees, ставит Не использован, увеличивает Settings ▸ wave. Лист Results не трогается вообще.
  4. Снять Включен со старых вопросов, поставить новым; в Answers добавить варианты с теми же ID (старые строки удалять не нужно).
  5. validateSurvey() и рассылка.

ID вопроса нельзя переиспользовать: именно по нему Answers и Results связываются с вопросом. Порядок строк Questions задаёт порядок показа. Для оцениваемого блиц-вопроса заполняется Номер правильного ответа, для самооценки — теги level-*. Портретные веса новых вопросов добавляются в CONFIG.portrait.scores; процент правильных работает автоматически.

Переключайте набор вопросов, когда нет сотрудников со статусом Частично: уже открытая страница содержит прежний набор.

Служебные функции Apps Script

Запускаются вручную из редактора (Run ▸ имя функции), результат — в Execution log.

Функция Что делает
validateSurvey() проверка перед волной: дубли ID и токенов, включённый вопрос без строки в Answers, неверный Номер правильного ответа, битые ограничения, сотрудник без токена, строки Usages с чужим ID, нечитаемый min_answers
auditQuestionSets() кто сколько вопросов получит и у кого набор короче минимума
setupWaveColumn() разово: колонка Волна в Results + заполнение существующих строк
previewNewWave() что изменит startNewWave, без изменений
startNewWave() архивация итогов, сброс статусов, wave + 1

Внутренние итоги (для вас, не для респондента)

Пишутся в строку сотрудника (Employees): Процент правильных, Балл самооценки (511: level0=0…level3=100), три характеристики 0–100, итоговый Средний балл и Портрет:

  • принятие/готовность использовать ИИ — 8 направленных вопросов блока отношения;
  • интерес и инициативность — 8 направленных вопросов блока интереса;
  • безопасность и ответственность — 5 вопросов, включая финальный кейс 411;
  • итоговый балл = 50% принятие + 30% интерес + 20% безопасность;
  • Оппонент ≤40 · Конформист 41–69 · Энтузиаст ≥70.

Веса конкретных вариантов задаются в CONFIG.portrait; собственный ответ без веса не превращается в нулевой балл. Настройки самооценки — в CONFIG.selfScore.

Минимальное число оценённых ответов на характеристику приходит из таблицы (Settings ▸ min_answers) и передаётся frontend в ответе getSurvey. Значения в CONFIG.portrait.dimensions.*.minAnswers остаются запасными: если строки в таблице нет или backend старый, поведение прежнее.

«Не успел» ≠ «не знал». Когда истекает таймер блиц-вопроса, frontend пишет в Results маркер TIMEOUT, а не пустую ячейку. Для процента правильных это по-прежнему неверный ответ, но в аналитике видно причину: нехватка времени и незнание требуют разных выводов.

Протокол для сотрудника

Кнопка «Скачать протокол» отдаёт .txt с только ФИО + вопрос → ответ. Без ID, без итогов, без названий блоков, без правильных ответов (чтобы не клеймить и чтобы ключ не расходился). Протокол формируется из текущих включённых строк, поэтому добавленные в таблицу вопросы и варианты не требуют отдельных изменений frontend-кода. Сотрудник берёт этот файл + «Скопировать промпт» и сам получает разбор у ИИ. Оценки и портрет — внутренние.

Прогресс и повторный вход

  • Сам ввод токена не меняет его статус.
  • После первого успешно записанного ответа ставится Частично.
  • Переход между вопросами не ждёт Apps Script: ответы записываются последовательной фоновой очередью. Финал не ждёт всю эту очередь: один идемпотентный finish синхронизирует все локальные ответы, записывает итоги и только последним действием выставляет статус Использован.
  • Повтор finish после сетевого таймаута не создаёт дубли. Если сервер успел завершить опрос, но JSONP-callback потерялся, frontend подтверждает статус отдельным чтением и всё равно открывает финальное видео и скачивание протокола.
  • Незавершённый опрос можно продолжить с сохранённого в этом браузере места; начать заново нельзя.
  • Если после обновления страницы локально уже сохранены все активные ответы, «Продолжить» сразу повторяет атомарную финализацию и не просит ещё раз отвечать на 411.
  • Сохранённый прогресс привязан к упорядоченному набору активных ID и не переносится на новый опрос.
  • При возврате на уже отвеченный вопрос блока «Навыки» ответ нельзя изменить: таймер повторно не запускается, а кнопка «Вернуться к текущему вопросу» возвращает к сохранённой позиции.
  • Использован ставится только после записи всех активных ответов и итогов. После этого токен блокируется.

Медиа и маскот

  • Приветствие: assets/media/start.mp4; благодарность: assets/media/end.mp4.
  • Для первых 32 вопросов используются картинки assets/mascot/1.png … 32.png без повторов; в более длинном опросе набор циклически повторяется. Сторона, сдвиг и наклон меняются детерминированно.
  • Первые маскоты загружаются во время приветствия, затем браузер держит запас следующих четырёх; предыдущая картинка скрывается до готовности новой и не повторяется на соседнем вопросе.
  • Широкие изображения автоматически получают отдельное ограничение размера и не заходят на карточку.
  • Видео используют preload="auto" и оптимизированы для веб-показа (960×540, H.264, faststart).
  • Маркер у принципов: assets/mascot/hand.png.

Подключение живой таблицы

  1. Теги в Answers: проверить, что портретные теги заполнены в колонках G:J.
  2. Лист Settings: создать и заполнить три строки — prompt (готовый текст есть в CONFIG.aiPromptTemplate), wave = 1, min_answers = {"adoption":6,"interest":6,"safety":4}.
  3. Деплой: Extensions ▸ Apps Script → вставить backend/Code.gs → Deploy ▸ Web app (Execute as: Me, Who has access: Anyone) → URL …/exec.
  4. В js/config.js: DEMO_MODE:false, API_URL:'…/exec'.
  5. Один раз запустить setupWaveColumn(), затем validateSurvey() — и можно рассылать.

Аналитика результатов: скилл ai-survey-pulse

В папке skill/ лежит обезличенная копия скилла для Claude Code, которым разбираются результаты волны: skill/ai-survey-pulse/ (исходники) и skill/ai-survey-pulse.zip (положить в ~/.claude/skills/). Перед запуском вписать ID своей таблицы в tools/config.json.

Инструмент Что делает
analyze.py качает таблицу, считает агрегаты и разбор по каждому: три характеристики, знания (в процентах от личного набора), калибровка «самооценка − факт», таймауты, вовлечённость с листа Usages, сравнение с прошлой волной
render_portraits.py из portraits.json (текст пишет модель) верстает по одному A4-PDF на человека; падает, если портрет не влез на лист
make_dashboard.py собирает HTML-дашборд: цифры из analysis.json, тексты из story.json
metrics_column.py считает столбец для отдельной таблицы метрик, сверяет с записанным, показывает просроченные мероприятия и сработавшие антиметрики
python tools/analyze.py --out DIR --fetch              # текущая волна
python tools/analyze.py --out DIR --fetch --list-waves # какие волны есть в таблице
python tools/analyze.py --out DIR --fetch --wave 1     # прошлая волна из Employees_history

Скилл только читает таблицы (по export-ссылке, без авторизации) — писать в них он не умеет. Портреты и снимки волн содержат ФИО и баллы: держите рабочую папку вне репозитория и не коммитьте.

При изменении backend/Code.gs обновите существующий deployment новой версией, чтобы сохранить тот же URL /exec.

Режим работы

Production-режим включён: CONFIG.DEMO_MODE=false, frontend обращается к опубликованному Google Apps Script из CONFIG.API_URL. js/questions.js остаётся локальной генерируемой копией; тексты в js/config.js — офлайн-фолбэк принципов и промпта.

Публикация

GitHub Pages: Settings ▸ Pages ▸ Deploy from a branch ▸ выбрать публикуемую ветку / root. Версия в query-параметрах CSS/JS в index.html обновляется при релизах, меняющих frontend, чтобы браузеры не запускали старый код из HTTP-кеша.

Проверка

node tests/run-tests.js

Проверяются динамическая загрузка и отключение вопросов, произвольные ID, попадание новых вопросов и ответов в протокол, статусы токена, атомарное завершение, мгновенный переход при фоновом сохранении, восстановление после ошибки записи, продолжение без кнопки перезапуска, таймер и ограничения разных композиций маскота. Команда работает в чистом clone без локального js/questions.js.

Оформление

Стиль: шрифт Raleway (Google Fonts), зелёный #005C32, светлые #E6EFEB/#F0F3F7, радиусы 8–16px. Токены — в :root в css/styles.css.

Шаблон таблицы

В репозитории лежит For Google Sheets - AiEmployeeTest.xlsx — скелет базы данных: все девять листов с правильными заголовками и рабочими значениями Settings, но без содержимого. Импортируйте его в свою Google-таблицу и заполняйте.

Что внутри и чего нет:

  • Employees — только заголовки и тестовый профиль; реальных сотрудников и токенов нет;
  • Questions — заголовки и одна строка-пример;
  • Answers — ID вопросов и портретные теги (по ним видно, как размечаются варианты), тексты вариантов убраны. У 301 оставлен один вариант и значение в Номер правильного ответа — это пример формата, а не настоящий ключ. Строки 302–310 содержат только ID: если включить такой вопрос, не заполнив варианты, backend откажется выдавать опрос с сообщением «Нет вариантов ответа для включенного вопроса: 302». Либо заполните варианты, либо снимите Включен, либо удалите строку;
  • Results, Employees_history, Usages, Metrics — заголовки и пояснения;
  • Settings — рабочие wave и min_answers, промпт-заглушка.

Пока листы не заполнены, validateSurvey() будет ругаться на пустые варианты ответов — это нормально для скелета, проверку имеет смысл запускать уже на своих данных.

P.S.

Если нужен файл questions.js — пишите на указанную в профиле почту. Если проект оказался Вам полезен, можете поддержать автора (СБП, крипта):

Donate QR

BTC: bc1q3frrup5neh7nhfg944etu2agd4j9u0vg3jyee6

ETH(Arbitrum): 0x43B349d8Cea83215D707EBa3bc35e9917f746b0a

TRX: THSzvy49KNeqRjXsGkurh2A5G4avV4RgN4

XRP: rLWZjS3DMupC4ZdXCX3BVYn4dEtC3iNhgy

SOL: 3xwfybxJ6Tz5t6pjBBkL5yYQCZo6wfbv932UNA4ThdP8

ADA: addr1q926ys75jp5wn2pv32a3t8r8pdhr7w02v0t9j4a8pmg0ruww5rlkctu4lnz2hfcwa5qfn3zhsd0s23r22uqwzx9gu6cq5c4e76

TON: UQC4qlAOD9Nly4K_66GJ_yCsSM3x2sB0vZ2GrBQbc--gZUui

DOGE: DTjNYmbtymzcjUiV4MsZY8MP4dM7MJ6qLC

XMR: 44qRqM6YtnxXUhkgCFqDDrKMPjWriu69FLBoop8Kwp7e1VQsBUJoVQ8JYQjfMV5C6uidTUgSSyoJ65mq8aYG2esZ1rrqfwt

About

Survey for workers skills in AI

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages