lib/glific/clients/stir.ex
defmodule Glific.Clients.Stir do
@moduledoc """
Example implementation of survey computation for STiR
Glific.Clients.Stir.compute_art_content(res)
"""
alias Glific.{
Contacts,
Flows.ContactField,
Groups,
Groups.Group,
Messages.Message,
Messages.MessageMedia,
Repo
}
import Ecto.Query, warn: false
@priorities_list [
{
"safety",
%{
description: "Creating safe learning environments _(Safety)_",
keyword: "1",
tdc_survery_flow: "448ef122-d257-42a3-bbd4-dd2d6ff43761",
option_b_videos: %{
"s1" => %{
code: :s1,
title: "More reflective discussion on the strategy"
},
"s2" => %{
code: :s2,
title: "Participants' get improvement focused feedback"
},
"s3" => %{
code: :s3,
title: "Space for practising the strategy"
},
"s4" => %{
code: :s4,
title: "Participants referring to data"
}
},
translations: %{
"hi" => %{
description: "सीखने के लिए सुरक्षित माहौल बनाना(सुरक्षित वातावरण)",
option_b_videos: %{
"s1" => %{
code: :s1,
title: "रणनीति पर अधिक चिंतनशील चर्चा"
},
"s2" => %{
code: :s2,
title: "प्रतिभागियों की सुधार केंद्रित प्रतिक्रिया प्राप्त करें"
},
"s3" => %{
code: :s3,
title: "रणनीति का अभ्यास करने के लिए स्थान"
},
"s4" => %{
code: :s4,
title: "डेटा का जिक्र करते प्रतिभागी"
}
}
},
"en" => %{
description: "Creating safe learning environments _(Safety)_",
option_b_videos: %{
"s1" => %{
code: :s1,
title: "More reflective discussion on the strategy"
},
"s2" => %{
code: :s2,
title: "Participants' get improvement focused feedback"
},
"s3" => %{
code: :s3,
title: "Space for practising the strategy"
},
"s4" => %{
code: :s4,
title: "Participants referring to data"
}
}
},
"ta" => %{
description: "பாதுகாப்பான கற்றல் சூழல்களை உருவாக்குவது_(பாதுகாப்பு)_",
option_b_videos: %{
"s1" => %{
code: :s1,
title: "மூலோபாயத்தில் அதிக பிரதிபலிப்பு விவாதம்"
},
"s2" => %{
code: :s2,
title: "பங்கேற்பாளர்களின் முன்னேற்றத்தை மையமாகக் கொண்ட கருத்து கிடைக்கும்"
},
"s3" => %{
code: :s3,
title: "மூலோபாயத்தை பயிற்சி செய்வதற்கான இடம்"
},
"s4" => %{
code: :s4,
title: "பங்கேற்பாளர்கள் தரவைக் குறிப்பிடுகின்றனர்"
}
}
},
"kn" => %{
description: "ಸುರಕ್ಷಿತ ಕಲಿಕೆಯ ವಾತಾವರಣವನ್ನು ಸೃಷ್ಟಿಸುವುದು _(ಸುರಕ್ಷತೆ)_",
option_b_videos: %{
"s1" => %{
code: :s1,
title: "ತಂತ್ರದ ಬಗ್ಗೆ ಹೆಚ್ಚು ಪ್ರತಿಫಲಿತ ಚರ್ಚೆ"
},
"s2" => %{
code: :s2,
title: "ಭಾಗವಹಿಸುವವರು ಸುಧಾರಿತ ಕೇಂದ್ರಿತ ಪ್ರತಿಕ್ರಿಯೆಯನ್ನು ಪಡೆಯುತ್ತಾರೆ"
},
"s3" => %{
code: :s3,
title: "ತಂತ್ರವನ್ನು ಅಭ್ಯಾಸ ಮಾಡಲು ಸ್ಥಳಾವಕಾಶ"
},
"s4" => %{
code: :s4,
title: "ಭಾಗವಹಿಸುವವರು ಡೇಟಾವನ್ನು ಉಲ್ಲೇಖಿಸುತ್ತಾರೆ"
}
}
}
}
}
},
{
"engagement",
%{
description: "Dedication and engagement _(Engagement)_",
keyword: "2",
tdc_survery_flow: "06247ce0-d5b7-4a42-b1e9-166dc85e9a74",
option_b_videos: %{
"s1" => %{
code: :e1,
title: "Proactive participation"
},
"s2" => %{
code: :e2,
title: "Developing action plans"
},
"s3" => %{
code: :e3,
title: "Asking questions"
}
},
translations: %{
"hi" => %{
description: "समर्पण और इंगेजमेन्ट (इंगेजमेन्ट)",
option_b_videos: %{
"s1" => %{
code: :e1,
title: "सक्रिय भागीदारी"
},
"s2" => %{
code: :e2,
title: "कार्य योजनाओं का विकास"
},
"s3" => %{
code: :e3,
title: "सवाल पूछे जा रहे है"
}
}
},
"en" => %{
description: "Dedication and engagement _(Engagement)_",
option_b_videos: %{
"s1" => %{
code: :e1,
title: "Proactive participation"
},
"s2" => %{
code: :e2,
title: "Developing action plans"
},
"s3" => %{
code: :e3,
title: "Asking questions"
}
}
},
"ta" => %{
description: "அர்ப்பணிப்பு மற்றும் ஈடுபாடு_ (ஈடுபாடு)_",
option_b_videos: %{
"s1" => %{
code: :e1,
title: "செயலில் பங்கேற்பு"
},
"s2" => %{
code: :e2,
title: "செயல் திட்டங்களை உருவாக்குதல்"
},
"s3" => %{
code: :e3,
title: "கேள்விகளை வினாவுதல்"
}
}
},
"kn" => %{
description: "ಸಮರ್ಪಣೆ ಮತ್ತು ತೊಡಗಿಸಿಕೊಳ್ಳುವಿಕೆ_(ತೊಡಗಿಸಿಕೊಳ್ಳುವಿಕೆ)_",
option_b_videos: %{
"s1" => %{
code: :e1,
title: "ಪೂರ್ವಭಾವಿಯಾಗಿ ಭಾಗವಹಿಸುವಿಕೆ"
},
"s2" => %{
code: :e2,
title: "ಕ್ರಿಯಾ ಯೋಜನೆಗಳನ್ನು ಅಭಿವೃದ್ಧಿಪಡಿಸುವುದು"
},
"s3" => %{
code: :e3,
title: "ಪ್ರಶ್ನೆಗಳನ್ನು ಕೇಳುತ್ತಿದ್ದಾರೆ"
}
}
}
}
}
},
{
"c&ct",
%{
description: "Promoting an improvement-focused culture _(Curiosity & Critical Thinking)_",
keyword: "3",
tdc_survery_flow: "27839001-d31c-4b53-9209-fe25615a655f",
option_b_videos: %{
"s1" => %{
code: :c1,
title: "Reflect on the content link to purpose"
},
"s2" => %{
code: :c2,
title: "Asking 'how' questions"
},
"s3" => %{
code: :c3,
title: "Asking 'why' questions"
}
},
translations: %{
"hi" => %{
description: "सुधार पर केंद्रित संस्कृति को बढ़ावा देना(जिज्ञासा और आलोचनात्मक सोच)",
option_b_videos: %{
"s1" => %{
code: :c1,
title: "उद्देश्य के लिए सामग्री लिंक पर चिंतन करें"
},
"s2" => %{
code: :c2,
title: "'कैसे' सवाल पूछना"
},
"s3" => %{
code: :c3,
title: "'क्यों' सवाल पूछना"
}
}
},
"en" => %{
description:
"Promoting an improvement-focused culture _(Curiosity & Critical Thinking)_",
option_b_videos: %{
"s1" => %{
code: :c1,
title: "Reflect on the content link to purpose"
},
"s2" => %{
code: :c2,
title: "Asking 'how' questions"
},
"s3" => %{
code: :c3,
title: "Asking 'why' questions"
}
}
},
"ta" => %{
description: "மேம்படுத்துதலை மையமாக கொண்ட கலாச்சாரத்தை வளர்ப்பது_(ஆர்வம் மற்றும் திறனாய்வு சிந்தனை)_",
option_b_videos: %{
"s1" => %{
code: :c1,
title: "நோக்கத்திற்கான உள்ளடக்க இணைப்பைப் பிரதிபலிக்கவும்"
},
"s2" => %{
code: :c2,
title: "'எப்படி' கேள்விகள் கேட்பது"
},
"s3" => %{
code: :c3,
title: "'ஏன்' கேள்விகள் கேட்பது"
}
}
},
"kn" => %{
description: "ಸುಧಾರಣೆ-ಕೇಂದ್ರಿತ ಸಂಸ್ಕೃತಿಯನ್ನು ಉತ್ತೇಜಿಸುವುದು _(ಕುತೂಹಲ ಮತ್ತು ವಿಮರ್ಶಾತ್ಮಕ ಚಿಂತನೆ)_",
option_b_videos: %{
"s1" => %{
code: :c1,
title: "ಉದ್ದೇಶಕ್ಕಾಗಿ ವಿಷಯ ಲಿಂಕ್ ಅನ್ನು ಪ್ರತಿಬಿಂಬಿಸಿ"
},
"s2" => %{
code: :c2,
title: "'ಹೇಗೆ' ಪ್ರಶ್ನೆಗಳನ್ನು ಕೇಳುವುದು"
},
"s3" => %{
code: :c3,
title: "'ಏಕೆ' ಪ್ರಶ್ನೆಗಳನ್ನು ಕೇಳುವುದು"
}
}
}
}
}
},
{
"selfesteem",
%{
description:
"Improving learning self-esteem (collaborate, recognise achievements/celebrate, and ask for support) _(Self-Esteem)_",
keyword: "4",
tdc_survery_flow: "3f38afbe-cb97-427e-85c0-d1a455952d4c",
option_b_videos: %{
"s1" => %{
code: :se1,
title: "Collaborating with peers"
},
"s2" => %{
code: :se2,
title: "Asking for support"
},
"s3" => %{
code: :se3,
title: "Achievements recognized"
}
},
translations: %{
"hi" => %{
description: "सीखने की आत्म-प्रतिष्ठा में सुधार करना (आत्मसम्मान)",
option_b_videos: %{
"s1" => %{
code: :se1,
title: "साथियों के साथ सहयोग"
},
"s2" => %{
code: :se2,
title: "समर्थन मांगना"
},
"s3" => %{
code: :se3,
title: "उपलब्धियां पहचानी गईं"
}
}
},
"en" => %{
description:
"Improving learning self-esteem (collaborate, recognise achievements/celebrate, and ask for support) _(Self-Esteem)_",
option_b_videos: %{
"s1" => %{
code: :se1,
title: "Collaborating with peers"
},
"s2" => %{
code: :se2,
title: "Asking for support"
},
"s3" => %{
code: :se3,
title: "Achievements recognized"
}
}
},
"ta" => %{
description: "சுயமரியாதையை கற்றுக்கொள்வதை மேம்படுத்துவது_(சுயமரியாதை)_",
option_b_videos: %{
"s1" => %{
code: :se1,
title: "சகாக்களுடன் ஒத்துழைத்தல்"
},
"s2" => %{
code: :se2,
title: "ஆதரவு கேட்கிறது"
},
"s3" => %{
code: :se3,
title: "சாதனைகள் அங்கீகரிக்கப்பட்டன"
}
}
},
"kn" => %{
description: "ಸ್ವಾಭಿಮಾನ ಕಲಿಕೆಯನ್ನು ಸುಧಾರಿಸುವುದು _(ಸ್ವಾಭಿಮಾನ)_",
option_b_videos: %{
"s1" => %{
code: :se1,
title: "ಗೆಳೆಯರೊಂದಿಗೆ ಸಹಕರಿಸುವುದು"
},
"s2" => %{
code: :se2,
title: "ಬೆಂಬಲಕ್ಕಾಗಿ ಕೇಳುವುದು"
},
"s3" => %{
code: :se3,
title: "ಸಾಧನೆಗಳನ್ನು ಗುರುತಿಸಲಾಗಿದೆ"
}
}
}
}
}
}
]
@intentional_coach_survey_titles %{
"question_1" => %{
"question" => "Providing inputs without excessive prompting",
translations: %{
"hi" => %{
"question" => "अत्यधिक प्रोत्साहन के बिना इनपुट प्रदान करना"
},
"en" => %{
"question" => "Providing inputs without excessive prompting"
},
"ta" => %{
"question" => "அதிக தூண்டுதல் இல்லாமல் உள்ளீடுகளை வழங்குதல்"
},
"ka" => %{
"question" => "ಅತಿಯಾದ ಪ್ರಚೋದನೆಯಿಲ್ಲದೆ ಒಳಹರಿವು ಒದಗಿಸುವುದು"
}
}
},
"question_2" => %{
"question" => "Linking actions to wider purposee",
translations: %{
"hi" => %{
"question" => "कार्यों को व्यापक उद्देश्य से जोड़ना"
},
"en" => %{
"question" => "Linking actions to wider purposee"
},
"ta" => %{
"question" => "செயல்களை பரந்த நோக்கத்துடன் இணைத்தல்"
},
"ka" => %{
"question" => "ಕ್ರಿಯೆಗಳನ್ನು ವಿಶಾಲ ಉದ್ದೇಶಕ್ಕೆ ಲಿಂಕ್ ಮಾಡುವುದು"
}
}
},
"question_3" => %{
"question" => "List action points to take forward",
translations: %{
"hi" => %{
"question" => "आगे बढ़ने के लिए कार्रवाई बिंदुओं की सूची बनाएं"
},
"en" => %{
"question" => "List action points to take forward"
},
"ta" => %{
"question" => "முன்னெடுத்துச் செல்ல நடவடிக்கை புள்ளிகளை பட்டியலிடுங்கள்"
},
"ka" => %{
"question" => "ಮುಂದೆ ತೆಗೆದುಕೊಳ್ಳಲು ಕ್ರಿಯಾ ಅಂಶಗಳನ್ನು ಪಟ್ಟಿ ಮಾಡಿ"
}
}
},
"question_4" => %{
"question" => "Problem solving and discussion",
translations: %{
"hi" => %{
"question" => "समस्या समाधान और चर्चा"
},
"en" => %{
"question" => "Problem solving and discussion"
},
"ta" => %{
"question" => "சிக்கல் தீர்வு மற்றும் விவாதம்"
},
"ka" => %{
"question" => "ಸಮಸ್ಯೆ ಪರಿಹಾರ ಮತ್ತು ಚರ್ಚೆ"
}
}
},
"question_5" => %{
"question" => "Asking 'why' and 'how' questions",
translations: %{
"hi" => %{
"question" => "'क्यों' और 'कैसे' प्रश्न पूछना"
},
"en" => %{
"question" => "Asking 'why' and 'how' questions"
},
"ta" => %{
"question" => "'ஏன்' மற்றும் 'எப்படி' கேள்விகள் கேட்பது"
},
"ka" => %{
"question" => "'ಏಕೆ' ಮತ್ತು 'ಹೇಗೆ' ಪ್ರಶ್ನೆಗಳನ್ನು ಕೇಳುವುದು"
}
}
}
}
@reminders %{
pending_registration: %{days: 7, group: "pending_registration"},
inactive_after_registration: %{days: 7, group: "inactive_after_registration"},
submit_reflection: %{days: 30, group: "submit_reflection"}
}
@active_report_days [1, 2, 3, 4, 5, 6]
@doc false
@spec webhook(String.t(), map()) :: map()
def webhook("previous_videos", fields) do
{:ok, contact_id} = Glific.parse_maybe_integer(fields["contact_id"])
{:ok, organization_id} = Glific.parse_maybe_integer(fields["organization_id"])
start_date = DateTime.utc_now() |> Timex.shift(months: -1) |> Timex.beginning_of_month()
end_date = DateTime.utc_now() |> Timex.shift(months: -1) |> Timex.end_of_month()
urls =
Message
|> where([m], m.type == ^"video")
|> where([m], m.contact_id == ^contact_id)
|> where([m], m.flow_id in [1541, 1557, 1700])
|> where([m], m.organization_id == ^organization_id)
|> where([m], m.inserted_at >= ^start_date and m.inserted_at <= ^end_date)
|> join(:inner, [m], mm in MessageMedia, on: m.media_id == mm.id)
|> select([m, mm], %{url: mm.url})
|> Repo.all()
|> Enum.with_index(1)
|> Enum.reduce("", fn {video, index}, acc -> "#{acc} \n *Video #{index}:* #{video.url}" end)
Map.put(%{}, :urls, urls)
end
def webhook("move_mt_to_district_group", fields) do
{:ok, contact_id} = Glific.parse_maybe_integer(fields["contact_id"])
{:ok, organization_id} = Glific.parse_maybe_integer(fields["organization_id"])
group_label = district_group(fields["district"], :mt)
{:ok, group} = Groups.get_or_create_group_by_label(group_label, organization_id)
Groups.create_contact_group(%{
contact_id: contact_id,
group_id: group.id,
organization_id: organization_id
})
%{group: group.label, is_moved: true}
end
def webhook("fetch_mt_list", fields) do
# {:ok, contact_id} = Glific.parse_maybe_integer(fields["contact_id"])
{:ok, organization_id} = Glific.parse_maybe_integer(fields["organization_id"])
mt_list = get_mt_list(fields["district"], organization_id)
{index_map, message_list} =
Enum.reduce(mt_list, {%{}, []}, fn {contact, index}, {index_map, message_list} ->
contact_name = get_in(contact.fields, ["name", "value"]) || contact.name
{
Map.put(index_map, index, contact.id),
message_list ++ ["Type *#{index}* for #{contact_name}"]
}
end)
%{mt_list_message: Enum.join(message_list, "\n"), index_map: Jason.encode!(index_map)}
end
def webhook("fetch_remaining_priorities", fields) do
priority_map = Enum.into(@priorities_list, %{})
first_priority = fields["first_priority"] |> String.downcase()
second_priority = fields["second_priority"] |> String.downcase()
[remaining_priority_first, remaining_priority_second | _] =
priority_map
|> Map.delete(first_priority)
|> Map.delete(second_priority)
|> Map.keys()
%{
remaining_priority_first: remaining_priority_first,
remaining_priority_second: remaining_priority_second
}
end
def webhook("check_coach_response", fields) do
{:ok, contact_id} = Glific.parse_maybe_integer(fields["contact_id"])
language = get_language(contact_id)
filtered_list =
fields["response"]
|> Enum.reject(fn {_question_no, response} -> clean_string(response) != "no" end)
coach_survey_state =
cond do
length(filtered_list) == 1 -> "one_no"
length(filtered_list) > 1 -> "more_than_one_no"
true -> "all_yes"
end
coach_survey_titles = get_coach_survey_titles(coach_survey_state, filtered_list, language)
%{coach_survey_state: coach_survey_state, coach_survey_titles: coach_survey_titles}
end
def webhook("get_coach_preferred_video", fields) do
{index_map, _message_list} =
fields["coach_survey_titles"]
|> String.split("\n")
|> Enum.with_index(1)
|> Enum.reduce({%{}, []}, fn {data, index}, {index_map, message_list} ->
{
Map.put(index_map, index, data),
message_list ++ [data]
}
end)
{:ok, preference} = Glific.parse_maybe_integer(fields["preference"])
%{response: Map.get(index_map, preference, "invalid response")}
end
def webhook("set_mt_for_tdc", fields) do
{:ok, contact_id} = Glific.parse_maybe_integer(fields["contact_id"])
{:ok, organization_id} = Glific.parse_maybe_integer(fields["organization_id"])
index_map = Jason.decode!(fields["index_map"])
{:ok, mt_contact_id} =
Map.get(index_map, fields["mt_contact_id"], 0)
|> Glific.parse_maybe_integer()
if mt_contact_id == 0 do
%{is_valid: false, mt_contact_id: mt_contact_id}
else
tdc = Contacts.get_contact!(contact_id)
{mt, _index} =
get_mt_list(fields["district"], organization_id)
|> Enum.find(fn {contact, _index} -> mt_contact_id == contact.id end)
## this is not the best way to update the contact variables we will fix that after this assignments.
tdc
|> ContactField.do_add_contact_field("mt_name", "mt_name", mt.name, "string")
|> ContactField.do_add_contact_field("mt_contact_id", "mt_contact_id", mt.id, "string")
%{is_valid: true, selected_mt: mt.name}
end
end
def webhook("get_priority_message", fields) do
exculde = clean_string(fields["exclude"])
{:ok, contact_id} = Glific.parse_maybe_integer(fields["contact_id"])
language = get_language(contact_id)
priorities =
if exculde in [""],
do: @priorities_list,
else: Enum.reject(@priorities_list, fn {priority, _} -> exculde == priority end)
priority_message =
priorities
|> Enum.map_join("\n", fn {_priority, obj} ->
description = get_in(obj, [:translations, language.locale, :description])
"*#{obj.keyword}*. #{description}"
end)
priority_map = Enum.into(@priorities_list, %{})
%{
message: priority_message,
exculde: get_in(priority_map, [exculde, :translations, language.locale])
}
end
def webhook("get_priority_descriptions", fields) do
priority_map = Enum.into(@priorities_list, %{})
{:ok, contact_id} = Glific.parse_maybe_integer(fields["contact_id"])
language = get_language(contact_id)
{first_priority, second_priority} =
get_in(fields, ["contact", "fields"])
|> cleaned_contact_priority()
first_priority_map = Map.get(priority_map, first_priority, %{})
second_priority_map = Map.get(priority_map, second_priority, %{})
%{
first_priority_description:
get_in(first_priority_map, [:translations, language.locale, :description]) || "NA",
second_priority_description:
get_in(second_priority_map, [:translations, language.locale, :description]) || "NA"
}
end
def webhook("contact_updated_the_priorities", fields) do
{:ok, contact_id} = Glific.parse_maybe_integer(fields["contact_id"])
{first_priority, second_priority} =
get_in(fields, ["contact", "fields"])
|> cleaned_contact_priority()
contact = Contacts.get_contact!(contact_id)
priority_versions = get_priority_versions(fields)["versions"] || []
versions =
priority_versions ++
[
%{
first_priority: first_priority,
second_priority: second_priority,
updated_at: Date.to_string(Timex.today())
}
]
priority_change_map = %{
last_priority_change: Date.to_string(Timex.today()),
versions: versions
}
priority_versions_as_string = Jason.encode!(priority_change_map)
ContactField.do_add_contact_field(
contact,
"priority_versions",
"priority_versions",
priority_versions_as_string,
"json"
)
%{update: true}
end
def webhook("priority_selection_frequency", fields) do
last_priority_change = get_priority_versions(fields)["last_priority_change"]
frequency =
if is_nil(last_priority_change) do
1
else
last_updated_date =
Timex.parse!(last_priority_change, "{YYYY}-{0M}-{D}")
|> Timex.to_date()
if Timex.diff(Timex.today(), last_updated_date, :days) <= 30, do: 2, else: 1
end
%{frequency: frequency}
end
def webhook("priority_based_survery_flows", fields) do
{:ok, mt_contact_id} =
get_in(fields, ["contact", "fields", "mt_contact_id", "value"])
|> Glific.parse_maybe_integer()
if mt_contact_id <= 0 do
%{
first_priority: "NA",
second_priority: "NA",
first_priority_flow: "NA",
second_priority_flow: "NA"
}
else
contact = Contacts.get_contact!(mt_contact_id)
{first_priority, second_priority} =
contact.fields
|> cleaned_contact_priority()
priority_map = Enum.into(@priorities_list, %{})
first_priority_map = priority_map[first_priority] || %{}
second_priority_map = priority_map[second_priority] || %{}
%{
first_priority: first_priority,
second_priority: second_priority,
first_priority_flow: first_priority_map[:tdc_survery_flow],
second_priority_flow: second_priority_map[:tdc_survery_flow]
}
end
end
def webhook("mt_and_diet_priority", fields) do
{:ok, organization_id} = Glific.parse_maybe_integer(fields["organization_id"])
mt_district = fields["district"] |> clean_string()
{first_priority, second_priority} =
get_in(fields, ["contact", "fields"])
|> cleaned_contact_priority()
result = %{
district: mt_district,
mt_first_priority: first_priority,
mt_second_priority: second_priority,
diet_first_priority: "NA",
diet_second_priority: "NA"
}
get_diet_list(fields["diet_group"], organization_id)
|> Enum.find(fn {district, _contact} -> district == mt_district end)
|> case do
{_district, diet} ->
{first_priority, second_priority} =
diet.fields
|> cleaned_contact_priority()
Map.merge(
result,
%{
diet_first_priority: first_priority,
diet_second_priority: second_priority
}
)
_ ->
result
end
end
def webhook("reset_contact_fields", fields) do
{:ok, _organization_id} = Glific.parse_maybe_integer(fields["organization_id"])
{:ok, contact_id} = Glific.parse_maybe_integer(fields["contact_id"])
Contacts.get_contact!(contact_id)
|> Contacts.update_contact(%{fields: %{}})
%{status: true}
end
def webhook("save_survey_answer_as_participant", fields) do
{:ok, contact_id} = Glific.parse_maybe_integer(fields["contact_id"])
mt_contact = Contacts.get_contact!(contact_id)
save_survey_results(mt_contact, fields, mt_type(fields), false)
end
def webhook("save_survey_answer", fields) do
{:ok, contact_id} = Glific.parse_maybe_integer(fields["contact_id"])
contact = Contacts.get_contact!(contact_id)
if remaining_priority?(fields["priority"], contact),
do: save_survey_results(contact, fields, mt_type(fields), false),
else: save_survey_results(contact, fields, mt_type(fields), true)
end
def webhook("get_survey_results", fields),
do: get_survey_results(fields, mt_type(fields))
def webhook("get_option_b_video_data", fields) do
index_map = Jason.decode!(fields["index_map"])
index = fields["index"]
if Map.has_key?(index_map, index) do
Map.get(index_map, index)
|> Map.put("is_valid", true)
else
%{"is_valid" => false}
end
end
def webhook("set_reminders", fields) do
{:ok, contact_id} = Glific.parse_maybe_integer(fields["contact_id"])
contact = Contacts.get_contact!(contact_id)
set_contact_reminder(contact, fields)
end
def webhook("compute_survey_score", %{results: results}),
do: compute_survey_score(results)
def webhook("monthly_reports", _fields) do
today = Timex.today("Asia/Kolkata")
response = if today.day in @active_report_days, do: "available", else: "not available"
%{response: response}
end
def webhook(_, fields), do: fields
@doc """
Get a GCS file name for specific user
"""
@spec gcs_file_name(map()) :: String.t()
def gcs_file_name(media),
do: media["remote_name"]
# Get MT type if it's A or B and perform the action based on that.
@spec mt_type(map()) :: atom()
defp mt_type(fields) do
contact_state =
get_in(fields, ["contact", "fields", "state", "value"])
|> clean_string()
if contact_state in ["karnataka", "tamilnadu", "tamil nadu"],
do: :TYPE_B,
else: :TYPE_A
end
@spec remaining_priority?(String.t(), Contacts.Contact.t()) :: boolean()
defp remaining_priority?(priority, contact) when is_binary(priority) == true do
{first_priority, second_priority} =
contact.fields
|> cleaned_contact_priority()
clean_string(priority) not in [first_priority, second_priority]
end
defp remaining_priority?(_priority, _contact), do: true
@spec save_survey_results(Contacts.Contact.t(), map(), atom(), boolean()) :: map()
defp save_survey_results(contact, fields, :TYPE_A, update_option_data?) do
priority = clean_string(fields["priority"])
answer = clean_string(fields["answer"])
[most_ranked, mid_ranked, least_rank] = get_ranked_response(fields["answer"])
option_a_data = get_option_a_data(fields)
## reset the value if the survey has been field eariler
option_a_data = if Map.keys(option_a_data) |> length > 1, do: %{}, else: option_a_data
# fetching contact priorities to fetch remaining_priorities for reports
contact_priorities = get_contact_priority(fields)
fields =
fields
|> Map.put("first_priority", contact_priorities.first)
|> Map.put("second_priority", contact_priorities.second)
remaining_priorities = webhook("fetch_remaining_priorities", fields)
priority_item = %{
"priority" => priority,
"answer" => answer,
"least_rank" => least_rank,
"mid_rank" => mid_ranked,
"most_rank" => most_ranked,
"diet_activity" => fields["contact"]["fields"]["activity"]["value"],
# passing remaining_priorities in the result for reports will improve it as we move forward
"remaining_priority_first" => remaining_priorities.remaining_priority_first,
"remaining_priority_second" => remaining_priorities.remaining_priority_second
}
option_a_data = Map.put(option_a_data, priority, priority_item)
update_option_data(contact, "option_a_data", option_a_data, update_option_data?)
end
defp save_survey_results(contact, fields, :TYPE_B, update_option_data?) do
priority = clean_string(fields["priority"])
answer_s1 = clean_string(fields["answers"]["s1"])
answer_s2 = clean_string(fields["answers"]["s2"])
answer_s3 = clean_string(fields["answers"]["s3"])
option_b_data = get_option_b_data(fields)
## reset the value if the survey has been field eariler
option_b_data = if Map.keys(option_b_data) |> length > 1, do: %{}, else: option_b_data
priority_item = %{
"priority" => priority,
"diet_activity" => fields["contact"]["fields"]["activity"]["value"],
"answers" => %{
s1: answer_s1,
s2: answer_s2,
s3: answer_s3
}
}
option_b_data = Map.put(option_b_data, priority, priority_item)
update_option_data(contact, "option_b_data", option_b_data, update_option_data?)
end
@spec update_option_data(Contacts.Contact.t(), String.t(), map(), boolean()) :: map()
defp update_option_data(contact, option_name, option_data, true) do
contact
|> ContactField.do_add_contact_field(
option_name,
option_name,
Jason.encode!(option_data),
"json"
)
option_data
end
defp update_option_data(_contact, _option_name, option_data, false), do: option_data
@spec get_coach_survey_titles(String.t(), list(), map()) :: String.t()
defp get_coach_survey_titles("all_yes", _response, language) do
@intentional_coach_survey_titles
|> Enum.reduce("", fn {question_no, question_data}, acc ->
question = get_in(question_data, [:translations, language.locale, "question"])
"#{acc} *Video #{String.replace(question_no, "question_", "")}* - #{question} \n"
end)
end
defp get_coach_survey_titles("more_than_one_no", response, language) do
response
|> Enum.with_index(1)
|> Enum.reduce("", fn {{question_no, _answer}, index}, acc ->
question =
get_in(@intentional_coach_survey_titles, [
question_no,
:translations,
language.locale,
"question"
])
"#{acc} *Video #{index}* #{question} \n"
end)
end
defp get_coach_survey_titles("one_no", response, language) do
[{question_no, _answer}] = response
get_in(@intentional_coach_survey_titles, [
question_no,
:translations,
language.locale,
"question"
])
end
@spec get_survey_results(map(), atom()) :: map()
defp get_survey_results(fields, :TYPE_A) do
option_a_data = get_option_a_data(fields)
{first_priority, second_priority} =
get_in(fields, ["contact", "fields"])
|> cleaned_contact_priority()
%{
option_a_data: option_a_data,
first_priority: first_priority,
second_priority: second_priority,
first_priority_rank: option_a_data[first_priority]["least_rank"],
second_priority_rank: option_a_data[second_priority]["least_rank"]
}
end
defp get_survey_results(fields, :TYPE_B) do
{first_priority, second_priority} =
get_in(fields, ["contact", "fields"])
|> cleaned_contact_priority()
option_b_data = get_option_b_data(fields)
p1_answers = option_b_data[first_priority]["answers"]
p2_answers = option_b_data[second_priority]["answers"]
answer_state = option_b_answer_state(p1_answers, p2_answers)
list_p1 = option_b_video_data(first_priority, p1_answers, answer_state, fields)
list_p2 = option_b_video_data(second_priority, p2_answers, answer_state, fields)
{index_map, message_list} =
(list_p1 ++ list_p2)
|> Enum.with_index(1)
|> Enum.reduce({%{}, []}, fn {data, index}, {index_map, message_list} ->
{
Map.put(index_map, index, data),
message_list ++ ["Video *#{index}* for #{data.title}"]
}
end)
%{
index_map: Jason.encode!(index_map),
video_message: Enum.join(message_list, "\n"),
answer_state: option_b_answer_state(p1_answers, p2_answers),
first_priority: first_priority,
second_priority: second_priority,
first_priority_answers: p1_answers,
second_priority_answers: p2_answers,
one_video_data: hd(list_p1 ++ list_p2)
}
end
@spec option_b_video_data(String.t(), map(), String.t(), map()) :: list()
defp option_b_video_data(priority, answers, answer_state, fields) do
priority_map = Enum.into(@priorities_list, %{})
{:ok, contact_id} = Glific.parse_maybe_integer(fields["contact_id"])
language = get_language(contact_id)
Enum.reduce(answers, [], fn {key, value}, acc ->
if answer_state == "all_true" || value not in ["67-100"] do
key =
if is_dam_dmpc_activity?(priority, fields) and key in ["s3", "s4"],
do: "s4",
else: key
item =
get_in(priority_map, [priority, :option_b_videos, key])
|> Map.put(:priority, priority)
get_in(priority_map, [priority, :translations, language.locale, :option_b_videos, key])
[item] ++ acc
else
acc
end
end)
end
defp is_dam_dmpc_activity?("safety", fields) do
activty =
get_in(fields, ["contact", "fields", "activity", "value"])
|> clean_string()
String.contains?(activty, ["dam", "dmpc"])
end
defp is_dam_dmpc_activity?(_priority, _activity), do: false
@spec option_b_answer_state(map(), map()) :: String.t()
defp option_b_answer_state(p1_answers, p2_answers) do
filtered_list =
(Map.values(p1_answers) ++ Map.values(p2_answers))
|> Enum.reject(fn answer -> answer == "67-100" end)
cond do
length(filtered_list) == 1 -> "one_true"
length(filtered_list) > 1 -> "more_then_one_true"
true -> "all_true"
end
end
@spec get_ranked_response(String.t()) :: list()
defp get_ranked_response(answer) do
clean_string(answer)
|> String.split([",", " "], trim: true)
end
@spec get_priority_versions(map()) :: map()
defp get_priority_versions(fields) do
priority_version_field = get_in(fields, ["contact", "fields", "priority_versions", "value"])
priority_version_field =
if priority_version_field in ["", nil], do: "{}", else: priority_version_field
Jason.decode!(priority_version_field)
end
@spec get_option_a_data(map()) :: map()
defp get_option_a_data(fields),
do: do_get_option(fields, "option_a_data")
## We will merge these two functions once we know that there is no other requirnment.
@spec get_option_b_data(map()) :: map()
defp get_option_b_data(fields),
do: do_get_option(fields, "option_b_data")
@spec do_get_option(map(), String.t()) :: map()
defp do_get_option(fields, key) do
option_data = get_in(fields, ["contact", "fields", key, "value"])
option_data = if option_data in ["", nil], do: "{}", else: option_data
Jason.decode!(option_data)
end
@spec clean_string(String.t()) :: String.t()
defp clean_string(str) do
String.downcase(str || "")
|> String.trim()
end
@spec get_contact_priority(map()) :: map()
defp get_contact_priority(fields) do
first_priority_map = Map.get(fields, "first_priority", %{})
second_priority_map = Map.get(fields, "second_priority", %{})
%{
first: first_priority_map["value"] || "NA",
second: second_priority_map["value"] || "NA"
}
end
@spec get_diet_list(String.t(), non_neg_integer()) :: list()
defp get_diet_list(diet_group_label, organization_id) do
{:ok, diet_group} =
Repo.fetch_by(Group, %{label: diet_group_label, organization_id: organization_id})
Contacts.list_contacts(%{
filter: %{include_groups: [diet_group.id]},
opts: %{"order" => "ASC"}
})
|> Enum.map(fn contact ->
district =
contact.fields["district"]["value"]
|> clean_string()
{district, contact}
end)
end
@spec get_mt_list(String.t(), non_neg_integer()) :: list()
defp get_mt_list(district, organization_id) do
group_label = district_group(district, :mt)
Repo.fetch_by(Group, %{label: group_label, organization_id: organization_id})
|> case do
{:ok, group} ->
Contacts.list_contacts(%{filter: %{include_groups: [group.id]}, opts: %{"order" => "ASC"}})
|> Enum.with_index(1)
_ ->
[]
end
end
@spec district_group(String.t(), atom()) :: String.t()
defp district_group(district, :mt) when is_binary(district) do
district =
String.trim(district)
|> String.downcase()
"MT-#{district}"
end
defp district_group(_, _), do: nil
@spec cleaned_contact_priority(map()) :: tuple()
defp cleaned_contact_priority(fields) do
contact_priorities = get_contact_priority(fields)
first_priority =
contact_priorities.first
|> clean_string()
second_priority =
contact_priorities.second
|> clean_string()
{first_priority, second_priority}
end
@spec set_contact_reminder(Contacts.Contact.t(), map()) :: map()
defp set_contact_reminder(contact, _fields) do
with {:reminder_not_set, attrs} <-
pending_registration_reminder(%{reminder_set: false}, contact, :pending_registration),
{:reminder_not_set, attrs} <-
being_inactive_after_registration_reminder(
attrs,
contact,
:inactive_after_registration
),
{:reminder_not_set, attrs} <-
submit_reflection_reminder(attrs, contact, :submit_reflection) do
attrs
else
{_, attrs} -> attrs
_ -> %{error: :no_response}
end
end
@spec pending_registration_reminder(map(), Contacts.Contact.t(), atom()) :: tuple()
defp pending_registration_reminder(results, contact, type) do
with {:error, _} <- has_a_date(contact.fields, "registration_completed_at"),
{:ok, registration_started_at} <- has_a_date(contact.fields, "registration_started_at"),
true <-
Timex.diff(Timex.today(), registration_started_at, :days) |> is_reminder_day?(type) do
{:reminder_set, set_reminder(contact, type)}
else
_ -> {:reminder_not_set, results}
end
end
@spec being_inactive_after_registration_reminder(map(), Contacts.Contact.t(), atom()) ::
tuple()
defp being_inactive_after_registration_reminder(results, contact, type) do
with {:ok, _registration_completed_at} <-
has_a_date(contact.fields, "registration_completed_at"),
true <-
Timex.diff(Timex.today(), contact.last_message_at, :days)
|> is_reminder_day?(type) do
{:reminder_set, set_reminder(contact, type)}
else
_ -> {:reminder_not_set, results}
end
end
@spec submit_reflection_reminder(map(), Contacts.Contact.t(), atom()) :: tuple()
defp submit_reflection_reminder(results, contact, type) do
case has_a_date(contact.fields, "registration_completed_at") do
{:ok, _registration_completed_at} ->
has_a_date(contact.fields, "last_survey_submission_at")
date = submission_check_date(contact.fields)
if Timex.diff(Timex.today(), date, :days) |> is_reminder_day?(type) do
{:reminder_set, set_reminder(contact, type)}
else
{:reminder_not_set, results}
end
_ ->
{:reminder_not_set, results}
end
end
@spec submission_check_date(map()) :: Date.t()
defp submission_check_date(contact_fields) do
case has_a_date(contact_fields, "last_survey_submission_at") do
{:ok, last_survey_submission_at} ->
last_survey_submission_at
_ ->
{:ok, registration_completed_at} = has_a_date(contact_fields, "registration_completed_at")
registration_completed_at
end
end
@spec has_a_date(map(), atom()) :: {:ok, Date.t()} | {:error, atom()}
defp has_a_date(contact_fields, key) do
if Map.has_key?(contact_fields, key) do
date =
get_in(contact_fields, [key, "value"])
|> parse_string_to_date()
{:ok, date}
else
{:error, :invalid_date}
end
end
@spec is_reminder_day?(integer(), atom()) :: boolean()
defp is_reminder_day?(0, _type), do: false
defp is_reminder_day?(days, type),
do: rem(days, @reminders[type][:days]) == 0
@spec set_reminder(Contacts.Contact.t(), atom()) :: map()
defp set_reminder(contact, type) do
{:ok, group} =
@reminders[type][:group]
|> Groups.get_or_create_group_by_label(contact.organization_id)
Groups.create_contact_group(%{
contact_id: contact.id,
group_id: group.id,
organization_id: contact.organization_id
})
%{reminder_set: true, reminder_type: type, last_reminder_at: Timex.now()}
end
@spec parse_string_to_date(String.t()) :: Date.t() | nil
defp parse_string_to_date(nil), do: nil
defp parse_string_to_date(date) when is_binary(date) == true do
date
|> String.trim()
|> Timex.parse!("{YYYY}-{0M}-{D}")
|> Timex.to_date()
end
defp get_language(contact_id) do
contact =
contact_id
|> Contacts.get_contact!()
|> Repo.preload([:language])
contact.language
end
@doc false
@spec blocked?(String.t()) :: boolean
def blocked?(phone) do
pattern = ["91", "1", "44", "256"]
if String.starts_with?(phone, pattern),
do: false,
else: true
end
@spec get_value(String.t(), map()) :: integer()
defp get_value(k, v) do
k = String.downcase(k)
input =
if is_binary(v["input"]),
do: String.downcase(v["input"]),
else: ""
if input == "y" do
case k do
"a1" -> 1
"a2" -> 2
"a3" -> 4
"a4" -> 8
"a5" -> 16
_ -> 0
end
else
0
end
end
@spec get_art_content(String.t(), map()) :: String.t()
defp get_art_content(k, v) do
k = String.downcase(k)
if(is_binary(v["input"]), do: String.downcase(v["input"]), else: "")
|> process(k)
end
@spec process(String.t(), String.t()) :: String.t()
defp process("n", "a1"), do: " *1*. More reflective discussion on the teaching strategy \n"
defp process("n", "a2"), do: " *2*. Space for practicing a classroom strategy \n"
defp process("n", "a3"), do: " *3*. Teachers get improvement focused feedback \n"
defp process("n", "a4"), do: " *4*. Teachers participation \n"
defp process("n", "a5"), do: " *5*. Developing concrete action plans \n"
defp process("n", "a6"), do: " *6*. Teachers asking question \n"
defp process(_, _), do: ""
@doc """
Return art content
"""
@spec compute_art_content(map()) :: String.t()
def compute_art_content(results) do
results
|> Enum.reduce(" ", fn {k, v}, acc ->
"#{acc} #{get_art_content(k, v)}"
end)
end
@doc """
Return integer depending on number of n as response in messages
"""
@spec compute_art_results(map()) :: non_neg_integer()
def compute_art_results(results) do
answers =
results
|> Enum.map(fn {_k, v} ->
if is_binary(v["input"]),
do: String.downcase(v["input"]),
else: ""
end)
|> Enum.reduce(%{}, fn x, acc -> Map.update(acc, x, 1, &(&1 + 1)) end)
cond do
is_nil(Map.get(answers, "n")) -> 3
Map.get(answers, "n") == 1 -> 1
Map.get(answers, "n") > 1 -> 2
true -> 3
end
end
@doc """
Return total score
"""
@spec compute_survey_score(map()) :: map()
def compute_survey_score(results) do
results
|> Enum.reduce(
0,
fn {k, v}, acc -> acc + get_value(k, v) end
)
|> get_content()
end
@spec get_content(integer()) :: map()
defp get_content(score) do
{status, content} =
cond do
rem(score, 7) == 0 -> {1, "Your score: #{score} is divisible by 7"}
rem(score, 5) == 0 -> {2, "Your score: #{score} is divisible by 5"}
rem(score, 3) == 0 -> {3, "Your score: #{score} is divisible by 3"}
rem(score, 2) == 0 -> {4, "Your score: #{score} is divisible by 2"}
true -> {5, "Your score: #{score} is not divisible by 2, 3, 5 or 7"}
end
%{
status: to_string(status),
content: content,
score: to_string(score)
}
end
end