Packages
ami
0.3.7
0.3.9
0.3.8
retired
0.3.7
retired
0.3.6
retired
0.3.5
retired
0.3.4
retired
0.3.3
retired
0.3.2
retired
0.3.1
retired
0.3.0
retired
0.2.9
retired
0.2.8
retired
0.2.7
retired
0.2.6
retired
0.2.5
retired
0.2.3
retired
0.2.2
retired
0.2.1
retired
0.2.0
retired
0.1.9
retired
0.1.8
retired
0.1.7
retired
0.1.6
retired
0.1.5
retired
0.1.4
retired
Package containing all AMI Models shared between micro-services.
Retired package: Security issue - retiring
Current section
Files
Jump to
Current section
Files
lib/ami/enrollment.ex
defmodule Ami.Enrollment do
use Ecto.Schema
use Timex
import Ecto.{
Query,
Changeset
}
alias Ami.{
CoursePeriod,
Repo,
Score,
PackagePeriod,
Course,
Unit,
Quiz,
Buddyship,
UserAnswer,
User,
Resource,
Enrollment,
CommunityUser,
Assignment,
Lesson
}
schema "enrollments" do
@timestamps_opts [type: :naive_datetime_usec]
field(:type, :string)
field(:enrollment_date, :utc_datetime_usec)
field(:created_at, :utc_datetime_usec)
field(:updated_at, :utc_datetime_usec)
field(:assignment_status, :string)
field(:active, :boolean)
field(:passed_on, :utc_datetime_usec)
field(:status, :integer)
field(:course_started, :boolean)
field(:course_completed, :boolean)
field(:assignment_score, :integer)
field(:assignment_sent_on, :utc_datetime_usec)
field(:assignment_reviewed_on, :date)
field(:progress, :binary)
field(:is_archived, :boolean)
belongs_to(:user, User)
belongs_to(:course_period, CoursePeriod)
belongs_to(:package_period, PackagePeriod)
belongs_to(:buddyship, Buddyship)
# user_id buddyship_id course_period_id
has_one(:buddy, through: [:buddyship, :buddy])
has_many(:assignments, Assignment, on_delete: :delete_all)
has_many(:scores, Score, on_delete: :delete_all)
end
def update_progress(enrollment) do
progress = calculate_progress(enrollment)
Ecto.Changeset.change(enrollment, %{progress: :erlang.term_to_binary(progress)})
|> Repo.update()
end
@doc """
Builds a changeset for saving assignment rating (aka assignment-score)
"""
def assignment_rating_changeset(struct, params \\ %{}) do
struct
|> cast(params, [:assignment_score, :updated_at, :created_at])
end
@doc """
Builds a changeset creating a course enrollment
- validates presence of course_period_id and user_id foreign keys
- validates with course_period_id uniqueness scoped by user_id
- check if user belongs to course period's community when it's a community course period
"""
def course_period_changeset(enrollment_struct, params \\ %{}, %CoursePeriod{} = course_period) do
enrollment_struct
|> cast(params, [
:created_at,
:updated_at,
:user_id,
:course_period_id,
:enrollment_date,
:package_period_id,
:status,
:active
])
|> validate_required([:course_period_id, :user_id])
|> check_community_belonging(course_period)
|> unique_constraint(
:course_period_id,
name: :index_enrollments_on_course_period_id_and_user_id,
message: "already enrolled in this course"
)
end
defp check_community_belonging(changeset, %CoursePeriod{community_id: nil}) do
changeset
end
defp check_community_belonging(changeset, course_period) do
query =
from(
cu in CommunityUser,
where: cu.user_id == ^changeset.changes.user_id,
where: cu.community_id == ^course_period.community_id,
where: cu.status == 1,
select: count(cu.id)
)
# or Communities.Repo.one(query) but failing test, not sure how it impact performance though
count = Repo.one(query)
cond do
count > 0 ->
changeset
count == 0 ->
add_error(changeset, :community, "can't enroll, you don't belong to the community")
end
end
@doc """
Builds a changeset for creating a package enrollment with package_period_id uniqueness validation scoped by user_id
"""
def package_period_changeset(%Enrollment{} = enrollment, params \\ %{}) do
enrollment
|> cast(params, [
:created_at,
:updated_at,
:user_id,
:package_period_id,
:status,
:enrollment_date
])
|> validate_required([:package_period_id, :user_id])
|> unique_constraint(
:package_period_id,
name: :index_enrollments_on_package_period_id_and_user_id,
message: "already enrolled in this package"
)
end
@doc """
Pattern to fetch resources as lesson.resources with resourceable_type == "Enrollment"
"""
def preload_resources(struct), do: Resource.preload_resources(struct, "Enrollment")
def get_current_enrollments_for(user_struct) do
community_enrollments = get_community_enrollments_for(user_struct)
public_enrollments =
if user_struct.status in [0, 1], do: get_public_enrollments_for(user_struct), else: []
package_enrollments = get_package_enrollments_for(user_struct)
learninglab_enrollments = get_learninglab_enrollments_for(user_struct)
%{
community_enrollments: community_enrollments,
public_enrollments: public_enrollments,
package_enrollments: package_enrollments,
learninglab_enrollments: learninglab_enrollments
}
end
def get_community_enrollments_for(user_struct) do
Repo.all(
from(
e in __MODULE__,
join: cp in assoc(e, :course_period),
join: c in assoc(cp, :course),
where: cp.starts_on <= ^Date.utc_today() and cp.ends_on >= ^Date.utc_today(),
where: e.user_id == ^user_struct.id,
where: e.active == true,
where: e.status in [0, 1],
where: e.is_archived == false,
where: not is_nil(cp.community_id),
where: cp.is_learning_lab == false,
# distinct: true,
order_by: [asc: cp.ends_on, asc: c.title],
preload: [
course_period: {cp, course: {c, [:thumb_image_attachments, instructors: :profile]}}
],
preload: [:scores, :buddyship, :buddy]
)
)
|> Enum.map(fn e -> Enrollment.progress_for(e) end)
end
def get_public_enrollments_for(user_struct) do
Repo.all(
from(
e in __MODULE__,
join: cp in assoc(e, :course_period),
join: c in assoc(cp, :course),
where: cp.starts_on <= ^Date.utc_today() and cp.ends_on >= ^Date.utc_today(),
where: e.user_id == ^user_struct.id,
where: e.active == true,
where: e.status in [0, 1],
where: e.is_archived == false,
where: is_nil(cp.community_id),
where: is_nil(cp.package_period_id),
where: cp.is_learning_lab == false,
# distinct: true,
order_by: [asc: cp.ends_on, asc: c.title],
preload: [
course_period: {cp, course: {c, [:thumb_image_attachments, instructors: :profile]}}
],
preload: [:scores, :buddyship, :buddy]
)
)
|> Enum.map(fn e -> Enrollment.progress_for(e) end)
end
def get_learninglab_enrollments_for(user_struct) do
Repo.all(
from(
e in __MODULE__,
join: cp in assoc(e, :course_period),
join: c in assoc(cp, :course),
where: cp.starts_on <= ^Date.utc_today() and cp.ends_on >= ^Date.utc_today(),
where: e.user_id == ^user_struct.id,
where: e.active == true,
where: e.status in [0, 1],
where: e.is_archived == false,
where: is_nil(cp.community_id),
where: cp.is_learning_lab == true,
# distinct: true,
order_by: [asc: cp.ends_on, asc: c.title],
preload: [
course_period: {cp, course: {c, [:thumb_image_attachments, instructors: :profile]}}
],
preload: [:scores, :buddyship, :buddy]
)
)
|> Enum.map(fn e -> Enrollment.progress_for(e) end)
end
def get_package_enrollments_for(user_struct) do
Repo.all(
from(
e in __MODULE__,
join: cp in assoc(e, :course_period),
join: c in assoc(cp, :course),
where: cp.starts_on <= ^Date.utc_today() and cp.ends_on >= ^Date.utc_today(),
where: e.user_id == ^user_struct.id,
where: e.active == true,
where: e.status in [0, 1],
where: e.is_archived == false,
where: not is_nil(e.package_period_id),
where: not is_nil(e.course_period_id),
where: is_nil(cp.community_id),
where: cp.is_learning_lab == false,
where: not is_nil(cp.package_period_id),
order_by: [asc: cp.ends_on, asc: c.title],
preload: [
course_period: {cp, course: {c, [:thumb_image_attachments, instructors: :profile]}}
],
preload: [:scores, :buddyship, :buddy]
)
)
|> Enum.map(fn e -> Enrollment.progress_for(e) end)
end
def get_notcompleted_community_enrollments_for(user_struct) do
Repo.all(
from(
e in __MODULE__,
join: cp in assoc(e, :course_period),
join: c in assoc(cp, :course),
where: is_nil(e.passed_on) and e.course_completed == false,
where: cp.starts_on <= ^Date.utc_today() and cp.ends_on >= ^Date.utc_today(),
where: e.user_id == ^user_struct.id,
where: e.active == true,
where: e.status in [0, 1],
where: e.is_archived == false,
where: not is_nil(cp.community_id),
where: cp.is_learning_lab == false,
order_by: [asc: cp.ends_on, asc: c.title],
preload: [
course_period: {cp, course: {c, [:thumb_image_attachments, instructors: :profile]}}
],
preload: [:scores, :buddyship, :buddy]
)
)
|> Enum.map(fn e -> Enrollment.progress_for(e) end)
end
def get_notcompleted_public_enrollments_for(user_struct) do
Repo.all(
from(
e in __MODULE__,
join: cp in assoc(e, :course_period),
join: c in assoc(cp, :course),
where: is_nil(e.passed_on) and e.course_completed == false,
where: is_nil(cp.community_id),
where: is_nil(cp.package_period_id),
where: cp.is_learning_lab == false,
where: cp.starts_on <= ^Date.utc_today() and cp.ends_on >= ^Date.utc_today(),
where: e.user_id == ^user_struct.id,
where: e.active == true,
where: e.status in [0, 1],
where: e.is_archived == false,
order_by: [asc: cp.ends_on, asc: c.title],
preload: [
course_period: {cp, course: {c, [:thumb_image_attachments, instructors: :profile]}}
],
preload: [:scores, :buddyship, :buddy]
)
)
|> Enum.map(fn e -> Enrollment.progress_for(e) end)
end
def get_notarchived_completed_enrollments_for(user_struct) do
Repo.all(
from(
e in __MODULE__,
join: cp in assoc(e, :course_period),
join: c in assoc(cp, :course),
where: e.is_archived == false,
where: e.course_completed == true,
where: e.user_id == ^user_struct.id,
where: e.active == true,
where: e.status in [0, 1],
order_by: [asc: cp.ends_on, asc: c.title],
preload: [
course_period: {cp, course: {c, [:thumb_image_attachments, instructors: :profile]}}
],
preload: [:scores, :buddyship, :buddy],
limit: 5
)
)
|> Enum.map(fn e -> Enrollment.progress_for(e) end)
end
@doc """
Builds a map containing all enrollments archive for a given user
Keys are: :community_enrollments, :public_enrollments, :learninglab_enrollments
"""
def get_archive_enrollments_for(user_struct) do
%{}
|> get_community_enrollment_archive_for(user_struct)
|> get_public_enrollment_archive_for(user_struct)
|> get_package_enrollment_archive_for(user_struct)
|> get_learninglab_enrollment_archive_for(user_struct)
end
def get_community_enrollment_archive_for(map, user_struct) do
enrollments =
Repo.all(
from(
e in __MODULE__,
join: cp in assoc(e, :course_period),
join: c in assoc(cp, :course),
where: e.user_id == ^user_struct.id,
where: e.active == true,
where: e.status in [0, 1],
where: e.updated_at <= ^Timex.shift(DateTime.utc_now(), days: -29),
where: cp.is_learning_lab == false,
where: not is_nil(cp.community_id),
where: cp.ends_on < ^Timex.now(),
order_by: [desc: cp.ends_on, asc: c.title],
preload: [
course_period: {cp, course: {c, [:thumb_image_attachments, instructors: :profile]}}
],
preload: :scores,
preload: :buddyship,
preload: :buddy
)
)
|> Enum.map(fn e -> Ami.Enrollment.progress_for(e) end)
|> Enum.map(fn e -> Ami.Enrollment.mark_as_archived(e) end)
Map.put_new(map, :community_enrollments, enrollments)
end
def get_public_enrollment_archive_for(map, user_struct) do
if user_struct.status in [0, 1] do
enrollments =
Repo.all(
from(
e in __MODULE__,
join: cp in assoc(e, :course_period),
join: c in assoc(cp, :course),
where: e.user_id == ^user_struct.id,
where: e.active == true,
where: e.status in [0, 1],
where: e.updated_at <= ^Timex.shift(DateTime.utc_now(), days: -29),
where: cp.is_learning_lab == false,
where: is_nil(cp.community_id),
where: cp.ends_on < ^Timex.now(),
order_by: [desc: cp.ends_on, asc: c.title],
preload: [
course_period: {cp, course: {c, [:thumb_image_attachments, instructors: :profile]}}
],
preload: [:scores, buddyship: :buddy]
)
)
|> Enum.map(fn e -> Enrollment.progress_for(e) end)
|> Enum.map(fn e -> Enrollment.mark_as_archived(e) end)
Map.put_new(map, :public_enrollments, enrollments)
else
Map.put_new(map, :public_enrollments, [])
end
end
def get_learninglab_enrollment_archive_for(map, user_struct) do
enrollments =
Repo.all(
from(
e in __MODULE__,
join: cp in assoc(e, :course_period),
join: c in assoc(cp, :course),
where: e.user_id == ^user_struct.id,
where: e.active == true,
where: e.status in [0, 1],
where: e.updated_at <= ^Timex.shift(DateTime.utc_now(), days: -29),
where: cp.is_learning_lab == true,
where: is_nil(cp.community_id),
where: cp.ends_on < ^Timex.now(),
order_by: [desc: cp.ends_on, asc: c.title],
preload: [
course_period: {cp, course: {c, [:thumb_image_attachments, instructors: :profile]}}
],
preload: [:scores, buddyship: :buddy]
)
)
|> Enum.map(fn e -> Enrollment.progress_for(e) end)
|> Enum.map(fn e -> Enrollment.mark_as_archived(e) end)
Map.put_new(map, :learninglab_enrollments, enrollments)
end
def get_package_enrollment_archive_for(map, user_struct) do
enrollments =
Repo.all(
from(
e in __MODULE__,
join: cp in assoc(e, :course_period),
join: c in assoc(cp, :course),
where: e.user_id == ^user_struct.id,
where: e.active == true,
where: e.updated_at <= ^Timex.shift(DateTime.utc_now(), days: -29),
where: e.status in [0, 1],
where: not is_nil(e.package_period_id),
where: not is_nil(e.course_period_id),
where: is_nil(cp.community_id),
where: cp.is_learning_lab == false,
where: not is_nil(cp.package_period_id),
where: cp.ends_on < ^Timex.now(),
order_by: [desc: cp.ends_on, asc: c.title],
preload: [
course_period: {cp, course: {c, [:thumb_image_attachments, instructors: :profile]}}
],
preload: :scores,
preload: :buddyship,
preload: :buddy
)
)
|> Enum.map(fn e -> Enrollment.progress_for(e) end)
|> Enum.map(fn e -> Enrollment.mark_as_archived(e) end)
Map.put_new(map, :package_enrollments, enrollments)
end
@doc """
Progress overview for a course : calculate progress percentage based on how many unit exams has been done along with the final exam. Provide a keyword list of all graded exams status for helping in setting stars status on progress bar.
{:pcentage, :completed_unit_test, :passed_final_exam, :accepted_final_exam, :exam_status, :unit_test_count}
"""
def calculate_progress(enrollment) do
%{pcentage: 0}
|> get_unit_exam_count(enrollment)
|> done_unit_test_count(enrollment)
|> has_passed_final_exam?(enrollment)
|> final_assignment_reviewed?(enrollment)
|> finished_course?(enrollment)
|> unit_tests_attempt_status(enrollment)
|> calculate_progress_percentage
end
def progress_for(enrollment) do
# empty_progress = :erlang.term_to_binary(%{})
progress =
case enrollment.progress do
<<131, 116, 0, 0, 0, 0>> ->
{:ok, enrollment} = update_progress(enrollment)
:erlang.binary_to_term(enrollment.progress)
nil ->
{:ok, enrollment} = update_progress(enrollment)
:erlang.binary_to_term(enrollment.progress)
_ ->
{:ok, enrollment} = update_progress(enrollment)
:erlang.binary_to_term(enrollment.progress)
end
%Enrollment{enrollment | progress: progress}
end
def mark_as_archived(enrollment) do
{:ok, enrollment} =
case enrollment.is_archived do
false ->
Enrollment.update(enrollment, %{is_archived: true})
true ->
{:ok, enrollment}
end
enrollment
end
def update_is_archived(enrollment) do
Ecto.Changeset.change(enrollment, %{is_archived: true}) |> Repo.update()
end
@doc """
Query responsible for fetching a given course unit tests
Bindings are : u - Unit / l - Lesson / q - Quiz
"""
def fetch_unit_tests_query(course) do
from(
u in Unit,
where: u.course_id == ^course.id,
join: l in assoc(u, :lessons),
join: q in assoc(l, :quiz),
where: q.is_graded == true and q.is_final == false
)
end
@doc """
Query responsible for fetching a given course final unit
"""
def fetch_final_unit_query(course) do
from(
u in Unit,
where: u.course_id == ^course.id,
join: l in assoc(u, :lessons),
join: q in assoc(l, :quiz),
where: q.is_final == true
)
end
@doc """
Count the enrollment's course unit tests and add it to the progress map under the key :unit_test_count
"""
def get_unit_exam_count(progress_map, enrollment) do
query = fetch_unit_tests_query(enrollment.course_period.course)
count = Repo.one(from([u, l, q] in query, select: count(q.id)))
Map.put_new(progress_map, :unit_test_count, count)
end
@doc """
Count the enrollment's done unit tests and add it to the progress map under the key :completed_unit_test
"""
def done_unit_test_count(progress_map, enrollment) do
query = fetch_unit_tests_query(enrollment.course_period.course)
count =
Repo.all(from([u, l, q] in query, select: q.id))
|> Enum.filter(fn quiz_id ->
Enum.find(enrollment.scores, fn s -> s.quiz_id == quiz_id end)
end)
|> length
Map.put_new(progress_map, :completed_unit_test, count)
end
@doc """
Query responsible for fetching course final quiz
Bindings are : q - Quiz / l - Lesson / u - Unit
"""
def fetch_final_exam_query(course) do
from(
q in Quiz,
join: l in assoc(q, :lesson),
join: ul in assoc(l, :unit_lessons),
join: u in assoc(ul, :unit),
where: u.course_id == ^course.id,
where: q.is_final == true
)
end
def fetch_final_exam_query_with_grade_value(course) do
from(
q in Quiz,
join: l in assoc(q, :lesson),
join: ul in assoc(l, :unit_lessons),
join: u in assoc(ul, :unit),
where: u.course_id == ^course.id,
where: q.is_final == true,
select: {q, u.grade_value}
)
end
@doc """
Query fetching the preassessment quiz of a given course
Bindings are : [q,l,u]
"""
def preassessment_quiz_query(course) do
from(
q in Quiz,
where: q.id == ^course.pre_assessment_quiz_id,
where: q.is_pre == true
)
end
@doc """
Query responsible for fetching the final quiz score for a given enrollment
Bindings are : s - Score
"""
def fetch_finalexam_score_query(final_exam_id, enrollment) do
from(
s in Score,
where: s.quiz_id == ^final_exam_id,
where: s.course_period_id == ^enrollment.course_period_id,
where: s.enrollment_id == ^enrollment.id,
where: s.user_id == ^enrollment.user_id
)
end
@doc """
Check if user has taken the course final exam or not, and add the check result to the progress_map under key :passed_final_exam
"""
def has_passed_final_exam?(progress_map, enrollment) do
query = fetch_final_exam_query(enrollment.course_period.course)
final_exam_id = Repo.one(from([q, l, u] in query, select: q.id))
case final_exam_id do
nil ->
Map.put_new(progress_map, :passed_final_exam, false)
_ ->
did_finalexam =
Enum.any?(enrollment.scores, fn score -> score.quiz_id == final_exam_id end)
if did_finalexam do
Map.put_new(progress_map, :passed_final_exam, true)
else
Map.put_new(progress_map, :passed_final_exam, false)
end
end
end
def final_assignment_reviewed?(progress_map, enrollment) do
Map.put_new(
progress_map,
:assignment_reviewed,
enrollment.assignment_status == "accepted" || enrollment.assignment_status == "rejected"
)
end
@doc """
Check wether or not the user succeeded this enrollment final exam and add the result to the progress map under key :accepted_final_exam
"""
def finished_course?(progress_map, enrollment) do
Map.put_new(progress_map, :completed_course, Enrollment.course_is_completed?(enrollment))
end
@doc """
Calculate the enrollment progress percentage following this rule/formula
1 - calculate the unit test pcentage value :
50.0 / unit tests count
50.0 if unit test count = 0
2 - add 25 if user has passed final exam
3 - add 25 if user has succeeded final exam
Add the calculated percentage to the progress map under key :pcentage
"""
def calculate_progress_percentage(progress_map) do
nb_of_unit_exams =
cond do
progress_map[:unit_test_count] == 0 -> 1
progress_map[:unit_test_count] > 0 -> progress_map[:unit_test_count]
end
utest_pcent_value = 50.0 / nb_of_unit_exams
progress_map = %{
progress_map
| pcentage: progress_map[:completed_unit_test] * utest_pcent_value
}
progress_map =
if progress_map.passed_final_exam,
do: Map.update!(progress_map, :pcentage, &(&1 + 25)),
else: progress_map
if progress_map.completed_course,
do: Map.update!(progress_map, :pcentage, &(&1 + 25)),
else: progress_map
end
@doc """
Return an ordered by unit_id list tuples in the form [{ unit_id, 1(true)/0(false)}, idx }, {{},idx2}, …] where 1/0 stands for wether the user has done the unit test
"""
def unit_tests_attempt_status(progress_map, enrollment) do
keyword_list = []
# get the unit_exam ids in descending order bc we'll prepend element in final keywords list
unit_test_query = fetch_unit_tests_query(enrollment.course_period.course)
unit_ids = Repo.all(from([u, l, q] in unit_test_query, order_by: [desc: u.id], select: u.id))
final_exam_query = fetch_final_exam_query(enrollment.course_period.course)
final_quiz = Repo.one(from([q, l, u] in final_exam_query))
# NOTE : this is ugly !!! We couldn't create a course with a final exam defined
# NOTE from Sadan: This is the old implementation which is causing compilation issue in docker.
# Please look at the fix below
# if !is_nil(final_quiz) do
# final_quiz_id = final_quiz.id
# final_quiz_title = final_quiz.title
# final_quiz_lesson_id = final_quiz.lesson_id
# final_quiz_score = fetch_score_for(final_quiz.id, enrollment)
# else
# final_quiz_id = nil
# final_quiz_title = ""
# final_quiz_lesson_id = nil
# final_quiz_score = nil
# end
{final_quiz_id, final_quiz_title, final_quiz_lesson_id, final_quiz_score} =
if !is_nil(final_quiz) do
{final_quiz.id, final_quiz.title, final_quiz.lesson_id,
fetch_score_for(final_quiz.id, enrollment)}
else
{nil, "", nil, nil}
end
conclusion_unit_id = Repo.one(from([q, l, u] in final_exam_query, select: u.id))
# associate them with their corresponding score presence
# starting with final assignment status
keyword_list =
case progress_map.assignment_reviewed do
true -> [{"final assignment", 1, nil, "Final assignment", "approved"} | keyword_list]
false -> [{"final assignment", 0, nil, "Final assignment", "pending"} | keyword_list]
end
# then with course conlusion unit test
keyword_list =
case progress_map.passed_final_exam do
true ->
[
{
conclusion_unit_id,
1,
final_quiz_id,
final_quiz_title,
final_quiz_lesson_id,
final_quiz_score.value
}
| keyword_list
]
false ->
[
{
conclusion_unit_id,
0,
final_quiz_id,
final_quiz_title,
final_quiz_lesson_id,
"not taken yet"
}
| keyword_list
]
end
# then the other unit tests
tuples_list =
Stream.map(unit_ids, fn unit_id ->
# TODO : check admin/moderator does not create multiple graded quizzes for a same Unit ?
get_unit_graded_quiz(unit_id)
|> create_base_tuple(unit_id)
|> add_lesson_title_and_id
|> add_score_value(enrollment)
|> add_took_exam_flag
end)
|> Enum.take(length(unit_ids))
|> prepend_to_list(keyword_list)
|> Enum.with_index()
Map.put_new(progress_map, :exam_status, tuples_list)
end
def get_unit_graded_quiz(unit_id) do
query =
from(
q in Quiz,
join: l in assoc(q, :lesson),
join: ul in assoc(l, :unit_lessons),
join: u in assoc(ul, :unit),
where: u.id == ^unit_id,
where: q.is_graded == true and q.is_final == false
)
Repo.one(from([q, l, u] in query, preload: [lesson: l]))
end
@doc """
Helper functions pipeline building enrollment quizzes status stored in enrollment.progress
"""
def create_base_tuple(quiz, unit_id), do: {unit_id, quiz}
def add_lesson_title_and_id({unit_id, quiz}),
do: {unit_id, quiz.id, quiz.lesson.title, quiz.lesson_id}
def add_score_value({unit_id, quiz_id, lesson_title, lesson_id}, enrollment) do
score = fetch_score_for(quiz_id, enrollment)
case score do
nil -> {unit_id, quiz_id, lesson_title, lesson_id, nil}
_ -> {unit_id, quiz_id, lesson_title, lesson_id, score.value}
end
end
def add_took_exam_flag({unit_id, quiz_id, lesson_title, lesson_id, score_value}) do
case score_value do
nil -> {unit_id, 0, quiz_id, lesson_title, lesson_id, "not taken yet"}
_ -> {unit_id, 1, quiz_id, lesson_title, lesson_id, score_value}
end
end
def fetch_score_for(quiz_id, enrollment) do
if quiz_id do
query =
from(
s in Score,
where: s.quiz_id == ^quiz_id,
where: s.enrollment_id == ^enrollment.id and s.user_id == ^enrollment.user_id,
where: s.course_period_id == ^enrollment.course_period_id,
select: [:id, :quiz_id, :enrollment_id, :user_id, :course_period_id, :value],
order_by: [asc: :value]
)
try do
Repo.one(query)
rescue
Ecto.MultipleResultsError ->
Repo.all(query) |> List.last()
end
end
end
def prepend_to_list([], list), do: list
def prepend_to_list([head | tail], list) do
prepend_to_list(tail, [head | list])
end
def update(enrollment, params \\ %{}) do
Ecto.Changeset.change(enrollment, params) |> Repo.update()
end
def potential_buddy_ids_for(course_period, enrollment) do
# get users ids with no buddyship for this course period
Repo.all(
from(
e in Enrollment,
where: e.course_period_id == ^course_period.id,
where: e.user_id != ^enrollment.user_id,
where: is_nil(e.buddyship_id),
select: e.user_id
)
)
|> Enum.sort()
end
def potential_buddy_ids_for(users_list, :additional, course_period, enrollment) do
if length(users_list) > 0 do
users_list
else
potential_buddy_ids_for(:additional, course_period, enrollment)
end
end
def potential_buddy_ids_for(:additional, course_period, enrollment) do
# scope :with_count_spot_to_become_buddy, ->(course_period, count) { joins("INNER JOIN buddyships on buddyships.buddy_id = users.id AND buddyships.course_period_id= #{course_period.id}").group('users.id').having("count(buddyships.id) = ?", count)}
# query responsible for getting user ids who strictly have 1 buddyships for this course period
from(
u in User,
join: b in assoc(u, :assigned_buddyships),
where: b.course_period_id == ^course_period.id,
where: b.buddy_id != ^enrollment.user_id and b.user_id != ^enrollment.user_id,
group_by: u.id,
having: count(b.id) == 1,
select: {u.id, count(b.id)}
)
|> Repo.all()
|> Enum.map(fn {user_id, _} -> user_id end)
end
def potential_buddy_ids_for(course_period, enrollment, excluded_userid) do
query =
from(
b in Buddyship,
where: b.course_period_id == ^course_period.id,
where: b.buddy_id != ^enrollment.user_id and b.user_id != ^enrollment.user_id,
where: b.buddy_id != ^excluded_userid and b.user_id != ^excluded_userid,
group_by: b.buddy_id,
having: count(b.id) == 1,
select: {b.buddy_id, count(b.id)}
)
virgin_user_ids =
Repo.all(
from(
e in Enrollment,
where: e.course_period_id == ^course_period.id,
where: e.user_id != ^enrollment.user_id,
where: e.user_id != ^excluded_userid,
where: is_nil(e.buddyship_id),
select: e.user_id
)
)
Repo.all(query)
|> Enum.map(fn {user_id, _} -> user_id end)
|> Enum.concat(virgin_user_ids)
|> Enum.sort()
end
def type_of(%Enrollment{} = enrollment) do
cond do
not is_nil(enrollment.course_period_id) && not is_nil(enrollment.course_period.community_id) ->
"community"
not is_nil(enrollment.course_period_id) && enrollment.course_period.is_learning_lab == true ->
"learning_lab"
not is_nil(enrollment.course_period_id) && is_nil(enrollment.course_period.community_id) &&
enrollment.course_period.is_learning_lab == false && is_nil(enrollment.package_period_id) ->
"public"
is_nil(enrollment.course_period_id) && not is_nil(enrollment.package_period_id) ->
"package"
not is_nil(enrollment.package_period_id) && not is_nil(enrollment.course_period_id) ->
"package"
end
end
def course_passed?(%Enrollment{} = enrollment) do
enrollment = Repo.preload(enrollment, [:scores, course_period: :course], force: true)
calculate_global_result(enrollment) >= enrollment.course_period.course.acceptance_score &&
course_is_completed?(enrollment) && assignment_accepted?(enrollment)
end
@doc """
Calculate an enrollment's final grade, according to following rules :
- 40% for accepted assignment
- 40% of final exam score
- 20% of unit test scores average
"""
def calculate_global_result(%Enrollment{} = enrollment) do
assignment_part =
if assignment_accepted?(enrollment) do
assignment_grade_value =
from(
l in Lesson,
join: u in assoc(l, :units),
join: la in assoc(l, :lesson_activity),
where: la.is_final_assignment == true,
where: u.course_id == ^enrollment.course_period.course_id,
distinct: true,
select: la.grade_value
)
|> Repo.one()
case assignment_grade_value do
n when is_integer(n) -> n
nil -> 40
_ -> 40
end
else
0
end
final_exam_part =
case final_exam_result(enrollment) do
{value, grade_value} when grade_value != nil ->
value * (grade_value / 100)
{value, grade_value} when is_nil(grade_value) ->
value * 0.4
end
test_part =
length(Course.unit_exams(enrollment.course_period.course))
|> case do
n when n > 0 ->
query =
from(
s in Score,
join: q in assoc(s, :quiz),
join: l in assoc(q, :lesson),
join: u in assoc(l, :units),
where: s.enrollment_id == ^enrollment.id,
where: s.course_period_id == ^enrollment.course_period_id,
where: s.user_id == ^enrollment.user_id,
where: q.is_graded == true,
where: q.is_final == false,
where: u.course_id == ^enrollment.course_period.course_id,
select: {s.value, u.grade_value}
)
unit_tests_total =
Repo.all(from(s in query))
|> Enum.map(fn {s, g} ->
case g do
# apply old grade value
nil -> {s, 20}
# apply recorded grade value
_ -> {s, g}
end
end)
|> Enum.reduce(0, fn {s, g}, acc -> s * (g / 100) + acc end)
unit_tests_total / n
_ ->
0
end
(test_part + final_exam_part + assignment_part) |> Float.round(2)
# tests_avg =
# length(Course.unit_exams(enrollment.course_period.course))
# |> case do
# n when n > 0 -> unit_tests_total_score(enrollment) / n
# _ -> 0
# end
# (tests_avg * 0.2 + final_exam_part + assignment_part) |> Float.round(2)
end
@doc """
Update enrollment's course flags (started, completed, passed_on) based enrollment scores and assignment status
"""
def update_course_flags(%Enrollment{} = enrollment) do
{:ok, enrollment} =
if !enrollment.course_started do
__MODULE__.update(enrollment, %{course_started: true})
else
{:ok, enrollment}
end
{:ok, enrollment} =
if __MODULE__.course_is_completed?(enrollment) do
__MODULE__.update(enrollment, %{course_completed: true})
else
{:ok, enrollment}
end
{:ok, enrollment} =
if __MODULE__.course_passed?(enrollment) do
__MODULE__.update(enrollment, %{passed_on: Timex.now()})
else
{:ok, enrollment}
end
end
defp final_exam_result(enrollment) do
final_exam_query = fetch_final_exam_query_with_grade_value(enrollment.course_period.course)
{final_quiz, gv} = Repo.one(from([q, l, u] in final_exam_query))
value =
Enum.find(enrollment.scores, %Score{value: 0}, fn s -> s.quiz_id == final_quiz.id end).value
{value, gv}
end
defp unit_tests_total_score(enrollment) do
query = test_results_query(enrollment)
Repo.all(from(s in query, select: s.value))
|> Enum.reduce(0, fn s, acc -> s + acc end)
end
defp assignment_accepted?(enrollment) do
enrollment.assignment_status == "accepted"
end
defp assignment_rejected?(enrollment) do
enrollment.assignment_status == "rejected"
end
def test_results_query(enrollment) do
from(
s in Score,
join: q in assoc(s, :quiz),
where: s.enrollment_id == ^enrollment.id,
where: s.course_period_id == ^enrollment.course_period_id,
where: s.user_id == ^enrollment.user_id,
where: q.is_graded == true,
where: q.is_final == false
)
end
def course_is_completed?(enrollment) do
graded_exam_ids =
Course.graded_exams(enrollment.course_period.course)
|> Enum.map(fn e -> e.id end)
graded_scores = Enum.filter(enrollment.scores, fn s -> s.quiz_id in graded_exam_ids end)
(assignment_accepted?(enrollment) || assignment_rejected?(enrollment)) &&
length(graded_exam_ids) == length(graded_scores)
end
def delete_associated_datas_of(enrollment) do
from(
ua in UserAnswer,
where: ua.user_id == ^enrollment.user_id,
where: ua.course_period_id == ^enrollment.course_period_id
)
|> Repo.delete_all()
from(
s in Score,
where: s.enrollment_id == ^enrollment.id,
where: s.user_id == ^enrollment.user_id
)
|> Repo.delete_all()
unless is_nil(enrollment.buddyship_id) do
Repo.get(Buddyship, enrollment.buddyship_id) |> Repo.delete()
end
from(
r in Resource,
where: r.resourceable_type == "Enrollment",
where: r.resourceable_id == ^enrollment.id
)
|> Repo.delete_all()
end
def into_learning_lab?(user_struct) do
enrollments =
Ami.Repo.all(
from(
e in Ami.Enrollment,
join: cp in assoc(e, :course_period),
where: e.user_id == ^user_struct.id,
where: cp.is_learning_lab == true,
where: cp.starts_on <= ^Date.utc_today() and cp.ends_on >= ^Date.utc_today()
)
)
length(enrollments) > 0
end
def into_package?(user_struct) do
Ami.Repo.one(
from(
e in __MODULE__,
join: cp in assoc(e, :course_period),
where: e.user_id == ^user_struct.id,
where: not is_nil(e.package_period_id),
where: not is_nil(e.course_period_id),
where: is_nil(cp.community_id),
where: cp.is_learning_lab == false,
where: not is_nil(cp.package_period_id),
where: cp.starts_on <= ^Date.utc_today() and cp.ends_on >= ^Date.utc_today(),
select: count(e.id)
)
) > 0
end
def into_community_cp?(user_struct) do
enrollments =
Ami.Repo.all(
from(
e in __MODULE__,
join: cp in assoc(e, :course_period),
where: e.user_id == ^user_struct.id,
where: not is_nil(cp.community_id),
where: cp.starts_on <= ^Date.utc_today() and cp.ends_on >= ^Date.utc_today()
)
)
length(enrollments) > 0
end
def days_from_ending?(enrollment, countdown) do
enrollment = Ami.Repo.preload(enrollment, :course_period)
interval =
Interval.new(
from: Date.utc_today(),
until: enrollment.course_period.ends_on
)
|> Interval.duration(:days)
interval == countdown
end
defp other_cp_students_query(course_period, user_id) do
from(
u in Ami.User,
join: e in assoc(u, :enrollments),
left_join: pr in assoc(u, :profile),
where: u.id != ^user_id,
where: e.course_period_id == ^course_period.id,
where: e.active == true,
preload: [profile: pr],
select: [:id, :email, :name, :uuid, profile: [:id, :first_name, :last_name, :image]]
)
end
def other_cp_students(course_period, user) do
other_cp_students_query(course_period, user.id) |> Repo.all()
end
def remaining_assessments(enrollment) do
course_id = enrollment.course_period.course_id
taken_quiz_ids = Enum.map(enrollment.scores, fn score -> score.quiz_id end)
Ami.Repo.one(
from(
q in Ami.Quiz,
join: l in assoc(q, :lesson),
join: u in assoc(l, :unit),
join: c in assoc(u, :course),
where: c.id == ^course_id,
where: q.is_graded == true or q.is_final == true,
where: not (q.id in ^taken_quiz_ids),
select: count(q.id)
)
)
end
end