defmodule Medicode.Transcriptions do @moduledoc """ The Transcriptions context. """ import Ecto.Query, warn: false alias Medicode.Repo alias Medicode.Transcriptions.{ Transcription, TranscriptionChunk, TranscriptionChunkKeyword, TranscriptionChunkCodeVector } @doc """ Create transcription record and begin transcribing ## Examples iex> create_and_transcribe_audio("my-audio.mp3") %Transcription{audio_file: "my-audio.mp3"} """ def transcribe_audio(transcription) do Medicode.TranscriptionSupervisor.start_transcription(transcription) end @doc """ Returns the list of transcriptions. ## Examples iex> list_transcriptions() [%Transcription{}, ...] """ def list_transcriptions(user) do Transcription |> where([t], t.user_id == ^user.id) |> Repo.all() end @doc """ Gets a single transcription. Returns `nil` if the Transcription does not exist. ## Examples iex> get_transcription(123) %Transcription{} iex> get_transcription(456) nil """ def get_transcription(id, preload_transcription_chunks \\ false) do query = if preload_transcription_chunks do chunks_query = TranscriptionChunk |> preload([:keywords, :code_vectors]) |> order_by([c], asc: c.start_mark) Transcription |> preload(chunks: ^chunks_query) else Transcription end Repo.get(query, id) end @doc """ Gets a single transcription. Raises `Ecto.NoResultsError` if the Transcription does not exist. ## Examples iex> get_transcription!(123) %Transcription{} iex> get_transcription!(456) ** (Ecto.NoResultsError) """ def get_transcription!(id, preload_transcription_chunks \\ false) do query = if preload_transcription_chunks do Transcription |> preload(chunks: [:keywords, :code_vectors]) else Transcription end Repo.get!(query, id) end @doc """ Gets a single transcription chunk. Raises `Ecto.NoResultsError` if the TranscriptionChunk does not exist. ## Examples iex> get_transcription_chunk!(123) %TranscriptionChunk{} iex> get_transcription_chunk!(456) ** (Ecto.NoResultsError) """ def get_transcription_chunk!(id, preload_transcription_chunk_associations \\ false) do query = if preload_transcription_chunk_associations do TranscriptionChunk |> preload([:keywords, :code_vectors]) else TranscriptionChunk end Repo.get!(query, id) end @doc """ Lists code vectors for transcription chunk ID. Returns `[]` if none are found. ## Examples iex> list_transcription_chunk_code_vectors(123) %CodeVector{} iex> list_transcription_chunk_code_vectors(456) [] """ def list_transcription_chunk_code_vectors(chunk_id) do TranscriptionChunkCodeVector |> join(:inner, [chunk_vector], code_vector in assoc(chunk_vector, :code_vector)) |> where([chunk_vector], chunk_vector.transcription_chunk_id == ^chunk_id) |> order_by([chunk_vector], desc: chunk_vector.cosine_similarity) |> preload([:assigned_by_user, :code_vector]) |> Repo.all() end def delete_code_vector_by_chunk_id_and_assigned_by_user_id( chunk_id, code_vector_id, assigned_by_user_id ) do query = TranscriptionChunkCodeVector |> where( [v], v.transcription_chunk_id == ^chunk_id and v.code_vector_id == ^code_vector_id and v.assigned_by_user_id == ^assigned_by_user_id ) with %TranscriptionChunkCodeVector{} = chunk_vector <- Medicode.Repo.one(query), {:ok, _struct} <- Medicode.Repo.delete(chunk_vector) do {:ok, chunk_vector} else res -> res end end @doc """ List transcription chunks by transcription ID. Returns [] if none are found. ## Examples iex> list_transcription_chunks(123) [%TranscriptionChunk{}] iex> list_transcription_chunks(456) [] """ def list_transcription_chunks( transcription_chunk_ids, preload_transcription_chunk_associations \\ false ) do query = if preload_transcription_chunk_associations do TranscriptionChunk |> preload([:keywords, :code_vectors]) else TranscriptionChunk end query |> where([c], c.id in ^transcription_chunk_ids) |> select([c], {c.id, c}) |> Repo.all() end @doc """ Collect transcription keywords and order by score. """ def list_transcription_summary_keywords(transcription_id) do query = from( k in TranscriptionChunkKeyword, join: c in TranscriptionChunk, on: k.transcription_chunk_id == c.id, where: c.transcription_id == ^transcription_id, group_by: k.keyword, select: %{keyword: k.keyword, score: sum(k.score)}, order_by: [desc: sum(k.score)] ) Repo.all(query) end @doc """ Creates a transcription. ## Examples iex> create_transcription(%{field: value}) {:ok, %Transcription{}} iex> create_transcription(%{field: bad_value}) {:error, %Ecto.Changeset{}} """ def create_transcription(attrs \\ %{}) do %Transcription{} |> Transcription.changeset(attrs) |> Repo.insert() end @doc """ Creates a transcription chunk. ## Examples iex> create_chunk(%{field: value}) {:ok, %TranscriptionChunk{}} iex> create_chunk(%{field: bad_value}) {:error, %Ecto.Changeset{}} """ def create_chunk(attrs \\ %{}) do %TranscriptionChunk{} |> TranscriptionChunk.changeset(attrs) |> Repo.insert() end @doc """ Creates a transcription chunk keyword. ## Examples iex> create_keyword_for_chunk(%{transcription_chunk_id: 1, keyword: "healthy"}) {:ok, %TranscriptionChunkKeyword{}} iex> create_keyword_for_chunk(%{field: bad_value}) {:error, %Ecto.Changeset{}} """ def create_keyword_for_chunk(attrs \\ %{}) do %TranscriptionChunkKeyword{} |> TranscriptionChunkKeyword.changeset(attrs) |> Repo.insert() end @doc """ Creates a transcription chunk code vector record. ## Examples iex> replace_all_code_vectors_for_chunk(%TranscriptionChunk{}, [%{transcription_chunk_id: 1, code_vector_id: 1, cosine_similarity: 1, weight: "positive"}]) {:ok, [%{id: id}]} iex> replace_all_code_vectors_for_chunk(%TranscriptionChunk{}, [%{field: bad_value}]) {:error, %Ecto.Changeset{}} """ def replace_all_code_vectors_for_chunk(%TranscriptionChunk{id: transcription_chunk_id}, attrs) do timestamp = DateTime.utc_now() |> DateTime.truncate(:second) {_count, chunk_code_vectors} = TranscriptionChunkCodeVector |> Repo.insert_all( attrs, placeholders: %{timestamp: timestamp}, on_conflict: {:replace, [:cosine_similarity, :weighting, :updated_at]}, returning: [:id], conflict_target: [:transcription_chunk_id, :code_vector_id] ) chunk_code_vector_ids = Enum.map(chunk_code_vectors, & &1.id) # Clear previous code vectors except for those assigned by a user from(v in TranscriptionChunkCodeVector, where: v.transcription_chunk_id == ^transcription_chunk_id and v.id not in ^chunk_code_vector_ids and is_nil(v.assigned_by_user_id) ) |> Repo.delete_all() chunk_code_vectors end @doc """ Insert a transcription chunk code vector record. ## Examples iex> upsert_code_vector_for_transcription_chunk(%{user_id: user.id, chunk_id: chunk.id}) {:ok, %TranscriptionChunkCodeVector{}} iex> upsert_code_vector_for_transcription_chunk(%{field: bad_value}) {:error, %Ecto.Changeset{}} """ def upsert_code_vector_for_transcription_chunk(attrs) do %TranscriptionChunkCodeVector{} |> TranscriptionChunkCodeVector.changeset(attrs) |> Repo.insert( on_conflict: :nothing, conflict_target: [:transcription_chunk_id, :code_vector_id] ) end @doc """ Updates a transcription. ## Examples iex> update_transcription(transcription, %{field: new_value}) {:ok, %Transcription{}} iex> update_transcription(transcription, %{field: bad_value}) {:error, %Ecto.Changeset{}} """ def update_transcription(%Transcription{} = transcription, attrs) do transcription |> Transcription.changeset(attrs) |> Repo.update() end @doc """ Updates a transcription chunk. ## Examples iex> update_transcription_chunk(transcription_chunk, %{field: new_value}) {:ok, %TranscriptionChunk{}} iex> update_transcription_chunk(transcription_chunk, %{field: bad_value}) {:error, %Ecto.Changeset{}} """ def update_transcription_chunk(%TranscriptionChunk{} = chunk, attrs) do chunk |> TranscriptionChunk.changeset(attrs) |> Repo.update() end @doc """ Deletes a transcription. ## Examples iex> delete_transcription(transcription) {:ok, %Transcription{}} iex> delete_transcription(transcription) {:error, %Ecto.Changeset{}} """ def delete_transcription(%Transcription{} = transcription) do Repo.delete(transcription) end @doc """ Returns an `%Ecto.Changeset{}` for tracking transcription changes. ## Examples iex> change_transcription(transcription) %Ecto.Changeset{data: %Transcription{}} """ def change_transcription(%Transcription{} = transcription, attrs \\ %{}) do Transcription.changeset(transcription, attrs) end end