philomena/lib/philomena/duplicate_reports.ex
2020-01-10 23:20:19 -05:00

198 lines
5.7 KiB
Elixir

defmodule Philomena.DuplicateReports do
@moduledoc """
The DuplicateReports context.
"""
import Ecto.Query, warn: false
alias Ecto.Multi
alias Philomena.Repo
alias Philomena.DuplicateReports.DuplicateReport
alias Philomena.ImageIntensities.ImageIntensity
alias Philomena.Images.Image
alias Philomena.Images
def generate_reports(source) do
source = Repo.preload(source, :intensity)
duplicates_of(source.intensity, source.image_aspect_ratio, 0.2, 0.05)
|> where([i, _it], i.id != ^source.id)
|> where([i, _it], i.duplication_checked != true)
|> Repo.all()
|> Enum.map(fn target ->
create_duplicate_report(source, target, %{}, %{
"reason" => "Automated Perceptual dedupe match"
})
end)
end
def duplicates_of(intensities, aspect_ratio, dist \\ 0.25, aspect_dist \\ 0.05) do
from i in Image,
inner_join: it in ImageIntensity,
on: it.image_id == i.id,
where: it.nw >= ^(intensities.nw - dist) and it.nw <= ^(intensities.nw + dist),
where: it.ne >= ^(intensities.ne - dist) and it.ne <= ^(intensities.ne + dist),
where: it.sw >= ^(intensities.sw - dist) and it.sw <= ^(intensities.sw + dist),
where: it.se >= ^(intensities.se - dist) and it.se <= ^(intensities.se + dist),
where:
i.image_aspect_ratio >= ^(aspect_ratio - aspect_dist) and
i.image_aspect_ratio <= ^(aspect_ratio + aspect_dist),
limit: 20
end
@doc """
Gets a single duplicate_report.
Raises `Ecto.NoResultsError` if the Duplicate report does not exist.
## Examples
iex> get_duplicate_report!(123)
%DuplicateReport{}
iex> get_duplicate_report!(456)
** (Ecto.NoResultsError)
"""
def get_duplicate_report!(id), do: Repo.get!(DuplicateReport, id)
@doc """
Creates a duplicate_report.
## Examples
iex> create_duplicate_report(%{field: value})
{:ok, %DuplicateReport{}}
iex> create_duplicate_report(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_duplicate_report(source, target, attribution, attrs \\ %{}) do
%DuplicateReport{image_id: source.id, duplicate_of_image_id: target.id}
|> DuplicateReport.creation_changeset(attrs, attribution)
|> Repo.insert()
end
# TODO: can we get this in a single transaction?
def accept_duplicate_report(%DuplicateReport{} = duplicate_report, user) do
changeset =
duplicate_report
|> DuplicateReport.accept_changeset(user)
Multi.new()
|> Multi.update(:duplicate_report, changeset)
|> Multi.run(:other_reports, fn repo, %{duplicate_report: duplicate_report} ->
{count, nil} =
DuplicateReport
|> where(
[dr],
(dr.image_id == ^duplicate_report.image_id and
dr.duplicate_of_image_id == ^duplicate_report.duplicate_of_image_id) or
(dr.image_id == ^duplicate_report.duplicate_of_image_id and
dr.duplicate_of_image_id == ^duplicate_report.image_id)
)
|> where([dr], dr.id != ^duplicate_report.id)
|> repo.update_all(set: [state: "rejected"])
{:ok, count}
end)
|> Repo.isolated_transaction(:serializable)
|> case do
{:ok, %{duplicate_report: duplicate_report}} ->
duplicate_report = Repo.preload(duplicate_report, [:image, :duplicate_of_image])
Images.merge_image(duplicate_report.image, duplicate_report.duplicate_of_image)
error ->
error
end
end
def accept_reverse_duplicate_report(%DuplicateReport{} = duplicate_report, user) do
{:ok, duplicate_report} = reject_duplicate_report(duplicate_report, user)
# Need a constraint for upsert, so have to do it the hard way
new_report =
DuplicateReport
|> where(duplicate_of_image_id: ^duplicate_report.image_id)
|> limit(1)
|> Repo.one()
new_report =
if new_report do
new_report
else
{:ok, duplicate_report} =
%DuplicateReport{
image_id: duplicate_report.duplicate_of_image_id,
duplicate_of_image_id: duplicate_report.image_id,
reason: Enum.join([duplicate_report.reason, "(Reverse accepted)"], "\n"),
user_id: user.id
}
|> DuplicateReport.changeset(%{})
|> Repo.insert()
duplicate_report
end
accept_duplicate_report(new_report, user)
end
def claim_duplicate_report(%DuplicateReport{} = duplicate_report, user) do
duplicate_report
|> DuplicateReport.claim_changeset(user)
|> Repo.update()
end
def unclaim_duplicate_report(%DuplicateReport{} = duplicate_report) do
duplicate_report
|> DuplicateReport.unclaim_changeset()
|> Repo.update()
end
def reject_duplicate_report(%DuplicateReport{} = duplicate_report, user) do
duplicate_report
|> DuplicateReport.reject_changeset(user)
|> Repo.update()
end
@doc """
Deletes a DuplicateReport.
## Examples
iex> delete_duplicate_report(duplicate_report)
{:ok, %DuplicateReport{}}
iex> delete_duplicate_report(duplicate_report)
{:error, %Ecto.Changeset{}}
"""
def delete_duplicate_report(%DuplicateReport{} = duplicate_report) do
Repo.delete(duplicate_report)
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking duplicate_report changes.
## Examples
iex> change_duplicate_report(duplicate_report)
%Ecto.Changeset{source: %DuplicateReport{}}
"""
def change_duplicate_report(%DuplicateReport{} = duplicate_report) do
DuplicateReport.changeset(duplicate_report, %{})
end
def count_duplicate_reports(user) do
if Canada.Can.can?(user, :index, DuplicateReport) do
DuplicateReport
|> where(state: "open")
|> Repo.aggregate(:count, :id)
else
nil
end
end
end