Загрузка данных


const SHEET_RESULTS = 'Результаты';

const CONFIG = {
  title: 'Подростки о родителях (ПоР / ADOR)',
  description: 'Оцените, насколько каждое утверждение соответствует воспитательным принципам вашей матери и вашего отца. 0 — не относится, 1 — частично, 2 — полностью.',
  metaFields: [
    { key: 'name',  label: 'ФИО',    type: 'text' },
    { key: 'group', label: 'Группа', type: 'list',
      options: ['ИС-21', 'ИС-22', 'ПИ-21', 'ПИ-22'] }
  ],
  scales: {
    poz_m: { name: 'Позитивный интерес (мать)',  norm: { min: 8, max: 14 } },
    dir_m: { name: 'Директивность (мать)',       norm: { min: 10, max: 16 } },
    hos_m: { name: 'Враждебность (мать)',        norm: { min: 6, max: 12 } },
    aut_m: { name: 'Автономность (мать)',        norm: { min: 8, max: 14 } },
    ned_m: { name: 'Непоследовательность (мать)', norm: { min: 8, max: 14 } },
    poz_f: { name: 'Позитивный интерес (отец)',  norm: { min: 8, max: 14 } },
    dir_f: { name: 'Директивность (отец)',       norm: { min: 10, max: 16 } },
    hos_f: { name: 'Враждебность (отец)',        norm: { min: 6, max: 12 } },
    aut_f: { name: 'Автономность (отец)',        norm: { min: 8, max: 14 } },
    ned_f: { name: 'Непоследовательность (отец)', norm: { min: 8, max: 14 } }
  },
  questions: [
    { id: 1,  text: 'Очень часто улыбается мне.', keys: { poz_m: 0 } },
    { id: 2,  text: 'Категорически требует, чтобы я усвоил(а), что могу делать, а что нет.', keys: { dir_m: 0 } },
    { id: 3,  text: 'Недостаточно терпелив(а) по отношению ко мне.', keys: { hos_m: 0 } },
    { id: 4,  text: 'Когда я ухожу, сам(а) решает, когда я должен (должна) вернуться.', keys: { aut_m: 0 } },
    { id: 5,  text: 'Всегда быстро забывает то, что сам(а) говорит или приказывает.', keys: { ned_m: 0 } },
    { id: 6,  text: 'Когда у меня плохое настроение, советует мне успокоиться или развеселиться.', keys: { poz_m: 0 } },
    { id: 7,  text: 'Считает, что для меня должно существовать много правил, которые я обязан(а) выполнять.', keys: { dir_m: 0 } },
    { id: 8,  text: 'Постоянно на меня кому-то жалуется.', keys: { hos_m: 0 } },
    { id: 9,  text: 'Предоставляет мне столько свободы, сколько мне надо.', keys: { aut_m: 0 } },
    { id: 10, text: 'За одно и то же один раз наказывает, а другой – прощает.', keys: { ned_m: 0 } },
    { id: 11, text: 'Очень любит делать что-нибудь вместе.', keys: { poz_m: 0 } },
    { id: 12, text: 'Если поручает мне какую-нибудь работу, то считает, что я должен (должна) делать только ее, пока не закончу.', keys: { dir_m: 0 } },
    { id: 13, text: 'Начинает сердиться и возмущаться по поводу любого пустяка, который я сделал(а).', keys: { hos_m: 0 } },
    { id: 14, text: 'Могу идти куда захочу и не спрашивать у него (нее) разрешения.', keys: { aut_m: 0 } },
    { id: 15, text: 'В зависимости от моего настроения отказывается от многих своих дел.', keys: { ned_m: 0 } },
    { id: 16, text: 'Когда мне грустно, пытается развеселить и воодушевить меня.', keys: { poz_m: 0 } },
    { id: 17, text: 'Всегда настаивает на том, что за все мои проступки я должен (должна) быть наказан(а).', keys: { dir_m: 0 } },
    { id: 18, text: 'Мало интересуется тем, что меня волнует и чего я хочу.', keys: { hos_m: 0 } },
    { id: 19, text: 'Если бы мне захотелось, то я мог(ла) бы идти куда захочу каждый вечер.', keys: { aut_m: 0 } },
    { id: 20, text: 'Имеет определенные правила, но иногда соблюдает их, а иногда – нет.', keys: { ned_m: 0 } },
    { id: 21, text: 'Всегда с пониманием выслушивает мои взгляды и мнения.', keys: { poz_m: 0 } },
    { id: 22, text: 'Следит за тем, чтобы я всегда делал(а) то, что мне сказано.', keys: { dir_m: 0 } },
    { id: 23, text: 'Иногда у меня возникает чувство, что я ему (ей) противен (противна).', keys: { hos_m: 0 } },
    { id: 24, text: 'Позволяет мне делать то, что нравится, и не требует ничего другого.', keys: { aut_m: 0 } },
    { id: 25, text: 'Отменяет свои требования, если я возражаю.', keys: { ned_m: 0 } },
    { id: 26, text: 'Старается меня поддержать, если у меня что-то не получается.', keys: { poz_m: 0 } },
    { id: 27, text: 'Требует от меня беспрекословного подчинения.', keys: { dir_m: 0 } },
    { id: 28, text: 'Считает, что я должен (должна) быть благодарен (благодарна) за все, что он (она) для меня делает.', keys: { hos_m: 0 } },
    { id: 29, text: 'Не вмешивается в мои дела, когда я этого не хочу.', keys: { aut_m: 0 } },
    { id: 30, text: 'Ведет себя по-разному по отношению ко мне в зависимости от настроения.', keys: { ned_m: 0 } },
    { id: 31, text: 'Всегда старается помочь мне в трудной ситуации.', keys: { poz_m: 0 } },
    { id: 32, text: 'Навязывает мне свою волю.', keys: { dir_m: 0 } },
    { id: 33, text: 'Часто бывает недоволен (недовольна) мной, когда я этого не заслуживаю.', keys: { hos_m: 0 } },
    { id: 34, text: 'Разрешает мне то, что другие родители запрещают.', keys: { aut_m: 0 } },
    { id: 35, text: 'Иногда бывает строг(а), а иногда – нет.', keys: { ned_m: 0 } },
    { id: 36, text: 'Умеет выразить свою признательность за то, что я делаю.', keys: { poz_m: 0 } },
    { id: 37, text: 'Контролирует каждый мой шаг.', keys: { dir_m: 0 } },
    { id: 38, text: 'Считает, что я не оправдываю его (ее) ожиданий.', keys: { hos_m: 0 } },
    { id: 39, text: 'Не ограничивает мою самостоятельность.', keys: { aut_m: 0 } },
    { id: 40, text: 'Его (ее) отношение ко мне меняется от случая к случаю.', keys: { ned_m: 0 } },
    { id: 41, text: 'Радуется моим успехам и огорчается неудачам.', keys: { poz_m: 0 } },
    { id: 42, text: 'Требует, чтобы я отчитывался (отчитывалась) перед ним (ней) за каждый поступок.', keys: { dir_m: 0 } },
    { id: 43, text: 'Никогда не хвалит меня.', keys: { hos_m: 0 } },
    { id: 44, text: 'Могу свободно высказывать свое мнение, даже если оно не совпадает с его (ее).', keys: { aut_m: 0 } },
    { id: 45, text: 'Иногда запрещает мне то, что разрешал(а) раньше.', keys: { ned_m: 0 } },
    { id: 46, text: 'Часто говорит мне, что я хороший (хорошая).', keys: { poz_m: 0 } },
    { id: 47, text: 'Устанавливает жесткие правила, которые я должен (должна) соблюдать.', keys: { dir_m: 0 } },
    { id: 48, text: 'Считает меня источником своих проблем.', keys: { hos_m: 0 } },
    { id: 49, text: 'Дает мне возможность самому (самой) решать, что делать.', keys: { aut_m: 0 } },
    { id: 50, text: 'Непредсказуем(а) в своих требованиях.', keys: { ned_m: 0 } },
    { id: 51, text: 'Очень часто улыбается мне.', keys: { poz_f: 0 } },
    { id: 52, text: 'Категорически требует, чтобы я усвоил(а), что могу делать, а что нет.', keys: { dir_f: 0 } },
    { id: 53, text: 'Недостаточно терпелив(а) по отношению ко мне.', keys: { hos_f: 0 } },
    { id: 54, text: 'Когда я ухожу, сам(а) решает, когда я должен (должна) вернуться.', keys: { aut_f: 0 } },
    { id: 55, text: 'Всегда быстро забывает то, что сам(а) говорит или приказывает.', keys: { ned_f: 0 } },
    { id: 56, text: 'Когда у меня плохое настроение, советует мне успокоиться или развеселиться.', keys: { poz_f: 0 } },
    { id: 57, text: 'Считает, что для меня должно существовать много правил, которые я обязан(а) выполнять.', keys: { dir_f: 0 } },
    { id: 58, text: 'Постоянно на меня кому-то жалуется.', keys: { hos_f: 0 } },
    { id: 59, text: 'Предоставляет мне столько свободы, сколько мне надо.', keys: { aut_f: 0 } },
    { id: 60, text: 'За одно и то же один раз наказывает, а другой – прощает.', keys: { ned_f: 0 } },
    { id: 61, text: 'Очень любит делать что-нибудь вместе.', keys: { poz_f: 0 } },
    { id: 62, text: 'Если поручает мне какую-нибудь работу, то считает, что я должен (должна) делать только ее, пока не закончу.', keys: { dir_f: 0 } },
    { id: 63, text: 'Начинает сердиться и возмущаться по поводу любого пустяка, который я сделал(а).', keys: { hos_f: 0 } },
    { id: 64, text: 'Могу идти куда захочу и не спрашивать у него (нее) разрешения.', keys: { aut_f: 0 } },
    { id: 65, text: 'В зависимости от моего настроения отказывается от многих своих дел.', keys: { ned_f: 0 } },
    { id: 66, text: 'Когда мне грустно, пытается развеселить и воодушевить меня.', keys: { poz_f: 0 } },
    { id: 67, text: 'Всегда настаивает на том, что за все мои проступки я должен (должна) быть наказан(а).', keys: { dir_f: 0 } },
    { id: 68, text: 'Мало интересуется тем, что меня волнует и чего я хочу.', keys: { hos_f: 0 } },
    { id: 69, text: 'Если бы мне захотелось, то я мог(ла) бы идти куда захочу каждый вечер.', keys: { aut_f: 0 } },
    { id: 70, text: 'Имеет определенные правила, но иногда соблюдает их, а иногда – нет.', keys: { ned_f: 0 } },
    { id: 71, text: 'Всегда с пониманием выслушивает мои взгляды и мнения.', keys: { poz_f: 0 } },
    { id: 72, text: 'Следит за тем, чтобы я всегда делал(а) то, что мне сказано.', keys: { dir_f: 0 } },
    { id: 73, text: 'Иногда у меня возникает чувство, что я ему (ей) противен (противна).', keys: { hos_f: 0 } },
    { id: 74, text: 'Позволяет мне делать то, что нравится, и не требует ничего другого.', keys: { aut_f: 0 } },
    { id: 75, text: 'Отменяет свои требования, если я возражаю.', keys: { ned_f: 0 } },
    { id: 76, text: 'Старается меня поддержать, если у меня что-то не получается.', keys: { poz_f: 0 } },
    { id: 77, text: 'Требует от меня беспрекословного подчинения.', keys: { dir_f: 0 } },
    { id: 78, text: 'Считает, что я должен (должна) быть благодарен (благодарна) за все, что он (она) для меня делает.', keys: { hos_f: 0 } },
    { id: 79, text: 'Не вмешивается в мои дела, когда я этого не хочу.', keys: { aut_f: 0 } },
    { id: 80, text: 'Ведет себя по-разному по отношению ко мне в зависимости от настроения.', keys: { ned_f: 0 } },
    { id: 81, text: 'Всегда старается помочь мне в трудной ситуации.', keys: { poz_f: 0 } },
    { id: 82, text: 'Навязывает мне свою волю.', keys: { dir_f: 0 } },
    { id: 83, text: 'Часто бывает недоволен (недовольна) мной, когда я этого не заслуживаю.', keys: { hos_f: 0 } },
    { id: 84, text: 'Разрешает мне то, что другие родители запрещают.', keys: { aut_f: 0 } },
    { id: 85, text: 'Иногда бывает строг(а), а иногда – нет.', keys: { ned_f: 0 } },
    { id: 86, text: 'Умеет выразить свою признательность за то, что я делаю.', keys: { poz_f: 0 } },
    { id: 87, text: 'Контролирует каждый мой шаг.', keys: { dir_f: 0 } },
    { id: 88, text: 'Считает, что я не оправдываю его (ее) ожиданий.', keys: { hos_f: 0 } },
    { id: 89, text: 'Не ограничивает мою самостоятельность.', keys: { aut_f: 0 } },
    { id: 90, text: 'Его (ее) отношение ко мне меняется от случая к случаю.', keys: { ned_f: 0 } },
    { id: 91, text: 'Радуется моим успехам и огорчается неудачам.', keys: { poz_f: 0 } },
    { id: 92, text: 'Требует, чтобы я отчитывался (отчитывалась) перед ним (ней) за каждый поступок.', keys: { dir_f: 0 } },
    { id: 93, text: 'Никогда не хвалит меня.', keys: { hos_f: 0 } },
    { id: 94, text: 'Могу свободно высказывать свое мнение, даже если оно не совпадает с его (ее).', keys: { aut_f: 0 } },
    { id: 95, text: 'Иногда запрещает мне то, что разрешал(а) раньше.', keys: { ned_f: 0 } },
    { id: 96, text: 'Часто говорит мне, что я хороший (хорошая).', keys: { poz_f: 0 } },
    { id: 97, text: 'Устанавливает жесткие правила, которые я должен (должна) соблюдать.', keys: { dir_f: 0 } },
    { id: 98, text: 'Считает меня источником своих проблем.', keys: { hos_f: 0 } },
    { id: 99, text: 'Дает мне возможность самому (самой) решать, что делать.', keys: { aut_f: 0 } },
    { id: 100, text: 'Непредсказуем(а) в своих требованиях.', keys: { ned_f: 0 } }
  ]
};

const ANSWER_OPTIONS = ['0', '1', '2'];

function normalizeConfig() {
  if (!Array.isArray(CONFIG.metaFields)) {
    CONFIG.metaFields = CONFIG.nameField
      ? [{ key: 'name', label: CONFIG.nameField, type: 'text' }]
      : [];
  }
  if (!CONFIG.questions) CONFIG.questions = [];
  if (!CONFIG.scales)   CONFIG.scales   = {};
}

function resolveForm_() {
  const active = FormApp.getActiveForm();
  if (active) {
    PropertiesService.getScriptProperties().setProperty('FORM_ID', active.getId());
    return active;
  }
  const props = PropertiesService.getScriptProperties();
  const id = props.getProperty('FORM_ID');
  if (id) {
    try { return FormApp.openById(id); }
    catch (e) { props.deleteProperty('FORM_ID'); }
  }
  throw new Error('Скрипт не привязан к форме. Открой Google Форму → ⋮ → Редактор скриптов и запусти setup().');
}

function setup() {
  normalizeConfig();
  const form = resolveForm_();
  const ss   = getOrCreateDestination_(form);
  syncForm_(form);
  ensureResultsSheet_(ss);
  installTrigger_(form);
  const info = {
    formId:         form.getId(),
    formEditUrl:    form.getEditUrl(),
    formLiveUrl:    form.getPublishedUrl(),
    spreadsheetUrl: ss.getUrl()
  };
  Logger.log(JSON.stringify(info, null, 2));
  return info;
}

function processExistingResponses() {
  normalizeConfig();
  const form = resolveForm_();
  const ss   = SpreadsheetApp.openById(form.getDestinationId());
  ensureResultsSheet_(ss);
  const responses = form.getResponses();
  let count = 0;
  responses.forEach(function (resp) {
    const parsed = parseResponse_(resp.getItemResponses());
    if (!parsed.meta.name) return;
    const scores   = calculateScores(parsed.answers);
    const abnormal = checkAbnormal(scores);
    writeResultRow_(form, parsed, scores, abnormal);
    count++;
  });
  Logger.log('Обработано ответов: ' + count);
  return count;
}

function getOrCreateDestination_(form) {
  let destId = null;
  try { destId = form.getDestinationId(); } catch (e) { destId = null; }
  if (!destId) {
    const ss = SpreadsheetApp.create(form.getTitle() + ' — результаты');
    form.setDestination(FormApp.DestinationType.SPREADSHEET, ss.getId());
    destId = ss.getId();
  }
  return SpreadsheetApp.openById(destId);
}

function syncForm_(form) {
  form.setTitle(CONFIG.title).setDescription(CONFIG.description);
  const expectedTitles = [
    ...CONFIG.metaFields.map(f => f.label),
    ...CONFIG.questions.map(q => '№' + q.id + '. ' + q.text)
  ];
  const current = form.getItems();
  const matches =
    current.length === expectedTitles.length &&
    current.every(function (it, i) { return it.getTitle() === expectedTitles[i]; });
  if (matches) return false;
  for (let i = current.length - 1; i >= 0; i--) form.deleteItem(current[i]);
  CONFIG.metaFields.forEach(function (f) {
    switch (f.type) {
      case 'list':
        form.addListItem().setTitle(f.label).setChoiceValues(f.options || []).setRequired(true);
        break;
      case 'paragraph':
        form.addParagraphTextItem().setTitle(f.label).setRequired(true);
        break;
      case 'date':
        form.addDateItem().setTitle(f.label).setRequired(true);
        break;
      default:
        form.addTextItem().setTitle(f.label).setRequired(true);
    }
  });
  CONFIG.questions.forEach(function (q) {
    form.addMultipleChoiceItem()
      .setTitle('№' + q.id + '. ' + q.text)
      .setChoiceValues(ANSWER_OPTIONS)
      .setRequired(true);
  });
  return true;
}

function ensureResultsSheet_(ss) {
  let sheet = ss.getSheetByName(SHEET_RESULTS);
  if (!sheet) sheet = ss.insertSheet(SHEET_RESULTS);
  const scaleKeys = Object.keys(CONFIG.scales);
  const header = [
    'Дата',
    ...CONFIG.metaFields.map(f => f.label),
    ...scaleKeys.map(k => headerLabel_(CONFIG.scales[k])),
    ...CONFIG.questions.map(q => '№' + q.id)
  ];
  const lastCol = Math.max(header.length, sheet.getLastColumn() || 1);
  sheet.getRange(1, 1, 1, lastCol).clearContent();
  sheet.getRange(1, 1, 1, header.length).setValues([header]);
  sheet.getRange(1, 1, 1, header.length)
    .setFontWeight('bold').setBackground('#4472c4').setFontColor('#ffffff')
    .setHorizontalAlignment('center').setVerticalAlignment('middle').setWrap(true);
  sheet.setFrozenRows(1);
  sheet.setFrozenColumns(1 + CONFIG.metaFields.length);
  sheet.setRowHeight(1, 40);
  sheet.setColumnWidth(1, 130);
  CONFIG.metaFields.forEach(function (_, i) { sheet.setColumnWidth(2 + i, 180); });
  const scoreCol = 2 + CONFIG.metaFields.length;
  scaleKeys.forEach(function (_, i) { sheet.setColumnWidth(scoreCol + i, 200); });
  const rawCol = scoreCol + scaleKeys.length;
  CONFIG.questions.forEach(function (_, i) { sheet.setColumnWidth(rawCol + i, 60); });
  return sheet;
}

function headerLabel_(scale) {
  if (scale.norm && scale.norm.min !== undefined && scale.norm.max !== undefined) {
    return scale.name + ' (' + scale.norm.min + '–' + scale.norm.max + ')';
  }
  return scale.name;
}

function calculateScores(answers) {
  const scores = {};
  Object.keys(CONFIG.scales).forEach(function (k) { scores[k] = 0; });
  CONFIG.questions.forEach(function (q) {
    const userVal = answers[q.id];
    if (userVal === undefined || userVal === null) return;
    const points = Number(userVal);
    if (isNaN(points)) return;
    Object.keys(q.keys).forEach(function (sk) {
      if (scores[sk] === undefined) scores[sk] = 0;
      scores[sk] += points;
    });
  });
  return scores;
}

function checkAbnormal(scores) {
  const res = {};
  Object.keys(CONFIG.scales).forEach(function (key) {
    const s = CONFIG.scales[key];
    const v = scores[key];
    if (s.norm && s.norm.min !== undefined && s.norm.max !== undefined) {
      res[key] = (v < s.norm.min) || (v > s.norm.max);
    } else {
      res[key] = false;
    }
  });
  return res;
}

function installTrigger_(form) {
  const exists = ScriptApp.getUserTriggers(form)
    .some(function (t) { return t.getHandlerFunction() === 'onFormSubmit'; });
  if (exists) return;
  ScriptApp.newTrigger('onFormSubmit').forForm(form).onFormSubmit().create();
}

function onFormSubmit(e) {
  try {
    const form   = e.source || FormApp.getActiveForm();
    const parsed = parseResponse_(e.response.getItemResponses());
    if (!parsed.meta.name) return;
    const scores   = calculateScores(parsed.answers);
    const abnormal = checkAbnormal(scores);
    writeResultRow_(form, parsed, scores, abnormal);
  } catch (err) {
    console.error('onFormSubmit error:', err.message, err.stack);
    throw err;
  }
}

function parseResponse_(responses) {
  const out = { meta: {}, answers: {}, raw: {} };
  const metaCount = CONFIG.metaFields.length;
  responses.forEach(function (ir, i) {
    const item  = ir.getItem();
    const title = item.getTitle();
    const value = ir.getResponse();
    if (i < metaCount) {
      const field = CONFIG.metaFields[i];
      if (field) out.meta[field.key] = (value === null || value === undefined)
        ? '' : String(value).trim();
      return;
    }
    const m = title.match(/^№\s*(\d+)\./);
    if (!m) return;
    const qid = Number(m[1]);
    const q = CONFIG.questions.filter(function (x) { return x.id === qid; })[0];
    if (!q) return;
    const text = (value === null || value === undefined) ? '' : String(value).trim();
    out.answers[qid] = text;
    out.raw[qid]     = text;
  });
  return out;
}

function writeResultRow_(form, parsed, scores, abnormal) {
  const ss    = SpreadsheetApp.openById(form.getDestinationId());
  const sheet = ensureResultsSheet_(ss);
  const scaleKeys = Object.keys(CONFIG.scales);
  const rowValues = [
    new Date(),
    ...CONFIG.metaFields.map(function (f) { return parsed.meta[f.key] || ''; }),
    ...scaleKeys.map(function (k) { return scores[k]; }),
    ...CONFIG.questions.map(function (q) {
      return parsed.raw[q.id] === undefined ? '' : parsed.raw[q.id];
    })
  ];
  sheet.appendRow(rowValues);
  const rowIndex = sheet.getLastRow();
  sheet.getRange(rowIndex, 1).setNumberFormat('dd.MM.yyyy HH:mm');
  sheet.getRange(rowIndex, 1, 1, rowValues.length)
    .setHorizontalAlignment('center').setVerticalAlignment('middle');
  if (CONFIG.metaFields.length > 0) {
    sheet.getRange(rowIndex, 2, 1, CONFIG.metaFields.length)
      .setHorizontalAlignment('left');
  }
  const scoreStart = 2 + CONFIG.metaFields.length;
  scaleKeys.forEach(function (key, i) {
    const cell = sheet.getRange(rowIndex, scoreStart + i);
    if (abnormal[key]) {
      cell.setBackground('#ff0000').setFontColor('#ffffff').setFontWeight('bold');
    } else {
      cell.setBackground('#e8f5e9').setFontColor('#000000').setFontWeight('normal');
    }
  });
}

function getUrls() {
  const form = resolveForm_();
  const ss   = getOrCreateDestination_(form);
  return {
    formEdit:  form.getEditUrl(),
    formLive:  form.getPublishedUrl(),
    sheetUrl:  ss.getUrl()
  };
}

function rebuildForm() {
  normalizeConfig();
  const form = resolveForm_();
  syncForm_(form);
  return getUrls();
}