module Quby module Compiler module Outputs class RoquaSerializer QUBY_TYPE_TO_ROQUA_TYPE = { check_box: 'multi_select', date: 'date_parts', float: 'float', integer: 'integer', radio: 'single_select', scale: 'single_select', select: 'single_select', string: 'text', textarea: 'text' } attr_reader :questionnaire def initialize(questionnaire) @questionnaire = questionnaire end def as_json(options = {}) { key: questionnaire.key, versions: versions, keys: questionnaire.roqua_keys, roqua_keys: questionnaire.roqua_keys, sbg_key: questionnaire.sbg_key, sbg_domains: questionnaire.sbg_domains, outcome_regeneration_requested_at: questionnaire.outcome_regeneration_requested_at, deactivate_answers_requested_at: questionnaire.deactivate_answers_requested_at, respondent_types: questionnaire.respondent_types, tags: questionnaire.tags.to_h.keys, charts: charts, outcome_tables_schema: outcome_tables_schema, questions: questions, scores: scores, } end def versions questionnaire.versions.map do |version| { number: version.number, release_notes: version.release_notes, regenerate_outcome: version.regenerate_outcome, deactivate_answers: version.deactivate_answers } end end def charts { overview: questionnaire.charts.overview && { subscore: questionnaire.charts.overview.subscore, y_max: questionnaire.charts.overview.y_max, }, others: questionnaire.charts.map do |chart| case chart when Quby::Compiler::Entities::Charting::LineChart { y_label: chart.y_label, tonality: chart.tonality, baseline: YAML.dump(chart.baseline), clinically_relevant_change: chart.clinically_relevant_change, } when Quby::Compiler::Entities::Charting::OverviewChart { subscore: chart.subscore, y_max: chart.y_max, } else {} end.merge( key: chart.key, type: chart.type, title: chart.title, plottables: chart.plottables, y_categories: chart.y_categories, y_range_categories: chart.y_range_categories, chart_type: chart.chart_type, y_range: chart.y_range, tick_interval: chart.tick_interval, plotbands: chart.plotbands, plotlines: chart.plotlines ) end } end # configuration for outcome tables. # tables: # : # each entry is a table. # score_keys: Set[] # rows in the table # subscore_keys: Set[] # columns in the table # headers: # : # headers for each subscore key for all tables. def outcome_tables_schema if questionnaire.outcome_tables.present? outcome_tables_from_definition else outcome_tables_from_score_schemas end end def outcome_tables_from_definition # hash of tables, with the score keys (rows) and subscore keys (columns) used for each tables = {} # hash of `subscore_key: subscore_label` pairs used in tables headers = {} questionnaire.outcome_tables.each do |table| tables[table.key] = {name: table.name, default_collapsed: table.default_collapsed, score_keys: table.score_keys, subscore_keys: table.subscore_keys}.compact table.subscore_keys.each do |subscore_key| table.score_keys.find do |score_key| subschema = questionnaire.score_schemas[score_key].subscore_schemas.find do |subschema| subschema.key == subscore_key end headers[subscore_key] = subschema&.label end end end { headers: headers, tables: tables, } end def outcome_tables_from_score_schemas # hash of tables, with the score keys (rows) and subscore keys (columns) used for each tables = Hash.new{ |hash, key| hash[key] = {score_keys: Set.new, subscore_keys: Set.new } } # hash of `subscore_key: subscore_label` pairs used in tables headers = {} questionnaire.score_schemas.values.each do |schema| schema.subscore_schemas.each do |subschema| next if subschema.outcome_table.blank? tables[subschema.outcome_table][:subscore_keys] << subschema.key tables[subschema.outcome_table][:score_keys] << schema.key headers[subschema.key] = subschema.label end end { headers: headers, tables: tables, } end # {v_1: {.., options: {..}, date_parts: {..}}, ..} # nils are removed from hash. # key [string] # sbg_key [nil/string] _not_ defaulted to key # type [string] # title [nil/string] no html # context_free_title [nil/string] no html, defaulted to title # unit [nil/string] no html (not in quby either) # deprecated [nil/true] only show if filled for old answers # default_invisible [nil/true] only show if filled or when all questions are shown # parent_question_key [nil/string] title_questions or question defined under option # parent_option_key [nil/string] question defined under option # title_question_key [nil/string] # date_parts [nil/{..}] # options: [nil/{..}] def questions questionnaire.questions.reject{_1.type == :hidden}.to_h { |question| [ question.key, { key: question.key, sbg_key: question.sbg_key, type: QUBY_TYPE_TO_ROQUA_TYPE.fetch(question.type), title: strip_tags_without_html_encode(question.title), context_free_title: strip_tags_without_html_encode(question.context_free_title), unit: question.unit, deprecated: question.hidden.presence, default_invisible: question.default_invisible.presence, parent_question_key: question.parent&.key, parent_option_key: question.parent_option_key, title_question_key: question.title_question&.key, date_parts: date_parts_for(question), options: options_for(question) }.compact ] } end # {year: {key: "v_date_yyyy"}, ..} # key [string] def date_parts_for(question) return nil unless question.type == :date question.components.to_h { |component| [ component, { key: question.send("#{component}_key") } ] } end # {a1: {..}} # key [string] # value [nil/string] nil for check_box, string otherwise # description [nil/string] context_free_description, no html # child_question_keys [nil/[string]] def options_for(question) return nil if question.options.empty? question.options.reject { |option| option.inner_title || option.placeholder } .to_h { |option| [ option.key, { key: option.key, value: (option.value.to_s unless question.type == :check_box), description: strip_tags_without_html_encode(option.context_free_description), child_question_keys: option.questions.map(&:key).presence }.compact ] } end # [{ key: { .., subscores: { subkey: {..} } } }] def scores questionnaire.score_schemas.transform_values { |score| { key: score.key, label: score.label, subscores: subscores_for(score) } } end # { subkey: {..} } def subscores_for(score) score.subscore_schemas.to_h { |subscore| [ subscore.key, { key: subscore.key, label: subscore.label, export_key: subscore.export_key, only_for_export: subscore.only_for_export.presence }.compact ] } end def strip_tags_without_html_encode(html) Nokogiri::HTML(html).text.presence end end end end end