Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UserTaskMatcher gets tasks with most overlapping skills fixes #736 #752

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions lib/code_corps/user_task_matcher.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
defmodule CodeCorps.UserTaskMatcher do
@moduledoc """
Find the top tasks most matching a User's skills
"""

alias CodeCorps.{Repo, Task, User, TaskSkill}
import Ecto.Query

@spec match_user(User.t, number) :: [Task.t]
def match_user(%CodeCorps.User{} = user, tasks_count) do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would write some inline documentation here and rename to tasks_limit or something more intention revealing.

query = from t in TaskSkill,
join: skill in assoc(t, :skill),
join: user_skill in assoc(skill, :user_skills),
join: user in assoc(user_skill, :user),
where: user.id == ^user.id,
group_by: t.task_id,
order_by: count(t.task_id),
limit: ^tasks_count,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually kind of wonder if we shouldn't do the limit chained elsewhere to keep this function more multipurpose. I.e. Without it we could do a count.

select: t.task_id
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you been able to run an EXPLAIN on this query by chance to see how well it performs?


matches = query |> Repo.all

Task |> where([t], t.id in ^matches) |> Repo.all
end
end
32 changes: 32 additions & 0 deletions test/lib/code_corps/user_task_matcher_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
defmodule CodeCorps.UserTaskMatcherTest do
use CodeCorps.ModelCase

import CodeCorps.UserTaskMatcher

test "can find top x tasks for a user's skill" do
coding = insert(:skill, title: "coding")
design = insert(:skill, title: "design")

account_page = insert(:task)
settings_page = insert(:task)
photoshop = insert(:task)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These could maybe be rethought a little to tell a clearer story. Like one could be front_end_task and one back_end_task and one design_task. You'd have coding_skill and design_skill and then match accordingly. Your final test would be 1 and 3 for length instead, using a limit of 1 and 3.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment probably conflicts slightly with the suggestion to chain, but with the right ordering could be resolvable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what i was going for with skills that were implementing an entire page (thus needing coding and design) versus one that was just PhotoShop.

I did start to wonder sometimes about using just the most overlapping tasks as the sort priority. I could see someone like me selecting 'design' as a tag (even though that's a stretch). Then I'd get a list of results back that were design heavy, even though I'd be better suited for a task that simply overlaps my strongest skill, coding.

(Seems like this would be more clear when we saw real data instead of speculating what the data would be.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think this is simply the most naive approach right now. I'd prefer some sort of recommender system generally but we're a ways off from that.


insert(:task)

insert(:task_skill, task: account_page, skill: design)
insert(:task_skill, task: account_page, skill: coding)
insert(:task_skill, task: settings_page, skill: design)
insert(:task_skill, task: settings_page, skill: coding)
insert(:task_skill, task: photoshop, skill: coding)

user = insert(:user)

insert(:user_skill, user: user, skill: design)
insert(:user_skill, user: user, skill: coding)

tasks = match_user(user, 2)

assert(length(tasks) == 2)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might be able to write this as assert tasks |> length() == 2 instead.

assert(length(match_user(user, 3)) == 3)
end
end
1 change: 1 addition & 0 deletions web/models/skill.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ defmodule CodeCorps.Skill do
has_many :roles, through: [:role_skills, :role]

has_many :project_skills, CodeCorps.ProjectSkill
has_many :user_skills, CodeCorps.UserSkill
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this go last to keep alpha order?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should roles and role_skills be in alpha order too?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes!

has_many :projects, through: [:project_skills, :project]

timestamps()
Expand Down