timgremore's picture
chore: Rename app to Medicode
3f219b5
raw
history blame
3.91 kB
defmodule Medicode.Feedback do
@moduledoc """
Context for managing user feedback on transcribed text and codes.
"""
import Ecto.Query
import Pgvector.Ecto.Query, only: [cosine_distance: 2]
alias Ecto.Multi
alias Medicode.Feedback.CodeFeedback
alias Medicode.Repo
def track_response(params) do
# TODO: Move this changeset into Multi and read errors from the result within the case statement
changeset =
CodeFeedback.changeset(%CodeFeedback{}, %{
code_vector_id: params["code_vector_id"],
response: params["response"],
text: params["text"],
text_vector: params["text_vector"],
user_id: params["user_id"]
})
multi =
Multi.new()
|> Multi.exists?(:existing_feedback, fn _changes ->
code_feedback_for_user_and_code_vector_query(
Ecto.Changeset.fetch_change!(changeset, :text),
Ecto.Changeset.fetch_change!(changeset, :user_id),
Ecto.Changeset.fetch_change!(changeset, :code_vector_id)
)
|> where([f], f.response == ^Ecto.Changeset.fetch_change!(changeset, :response))
end)
|> Multi.delete_all(:delete_previous_feedback, fn _changes ->
code_feedback_for_user_and_code_vector_query(
Ecto.Changeset.fetch_change!(changeset, :text),
Ecto.Changeset.fetch_change!(changeset, :user_id),
Ecto.Changeset.fetch_change!(changeset, :code_vector_id)
)
end)
|> Multi.merge(fn %{existing_feedback: existing_feedback} ->
if existing_feedback do
Multi.new()
else
Multi.new()
|> Multi.insert(:new_feedback, changeset)
end
end)
case Repo.transaction(multi) do
{:ok, _code_feedback} ->
{:ok, message_for_response(params)}
{:error, _failed_operation, _failed_value, _changes_so_far} ->
{:error, Repo.collect_errors(changeset)}
end
end
def insert_and_return(params) do
changeset = CodeFeedback.changeset(%CodeFeedback{}, params)
case Repo.insert(changeset) do
{:ok, code_feedback} -> code_feedback
{:error, _changeset} -> nil
end
end
def delete(id) do
case Repo.delete(id) do
{:ok, _code_feedback} -> {:ok, "Deleted code feedback."}
{:error, changeset} -> {:error, Repo.collect_errors(changeset)}
end
end
def code_feedback_for_user_and_code_vector_query(text, user_id, code_vector_id) do
from(f in CodeFeedback,
inner_join: u in assoc(f, :user),
inner_join: v in assoc(f, :code_vector),
where:
f.user_id == ^user_id and f.code_vector_id == ^code_vector_id and
f.text == ^text
)
end
def code_feedback_for_user_and_code_vector(text, user_id, code_vector_id) do
text
|> code_feedback_for_user_and_code_vector_query(user_id, code_vector_id)
|> Repo.one()
end
defp message_for_response(params) do
action = if params["response"] == "true", do: "upvoted", else: "downvoted"
"Successfully #{action} code"
end
@doc """
Given a portion of transcribed text, finds the most-relevant previous feedback for that transcribed text.
The response from this previous feedback can then be used to modify the weight for the associated code when
classifying this `text`.
"""
def find_related_feedback(search_vector, opts \\ []) do
k = Keyword.get(opts, :num_results, 5)
similarity_threshold = Keyword.get(opts, :similarity_threshold, 0.80)
query =
from(f in CodeFeedback,
order_by: cosine_distance(f.text_vector, ^search_vector),
where: 1 - cosine_distance(f.text_vector, ^search_vector) > ^similarity_threshold,
limit: ^k,
select: %{
code_vector_id: f.code_vector_id,
response: f.response,
cosine_similarity: 1 - cosine_distance(f.text_vector, ^search_vector)
}
)
Repo.all(query)
end
end