Skip to content

Commit f64e329

Browse files
committed
feat: implement match anonymization for non-subscribed orgs
- Add anonymized parameter to match_card component - Replace avatar with gray circle for anonymized matches - Replace name/handle with 'Anonymous user' + blur effect - Hide provider meta information when anonymized - Preserve country flag display for anonymized matches - Control anonymization based on hiring_subscription status
1 parent 919b51f commit f64e329

File tree

2 files changed

+324
-24
lines changed

2 files changed

+324
-24
lines changed

job_matches_enhancement_prd.md

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
# Job Matches Enhancement PRD
2+
3+
## Overview
4+
5+
Enhance the job matching system in `lib/algora_web/live/org/job_live.ex` to provide better candidate privacy, accurate match counting, improved matching criteria, and visual enhancement with GitHub contribution heatmaps.
6+
7+
## Background
8+
9+
Currently, the job matching system in `lib/algora_web/live/org/job_live.ex` has several limitations:
10+
1. Matches show full user information (avatar, name, handle) which may not be appropriate for privacy
11+
2. Match counts are artificially limited (15-50 matches) rather than showing actual match numbers
12+
3. Matching criteria doesn't properly validate user email availability
13+
4. Missing visual contribution heatmaps that provide valuable candidate assessment data
14+
15+
## Goals
16+
17+
1. **Anonymize Matches**: Protect candidate privacy by anonymizing user information in match cards
18+
2. **Accurate Match Counting**: Display real match counts instead of arbitrary limits
19+
3. **Enhanced Matching Criteria**: Improve matching logic to include email validation
20+
4. **Visual Enhancement**: Add GitHub contribution heatmaps for better candidate evaluation
21+
22+
## Requirements
23+
24+
### 1. Anonymized Match Display
25+
26+
**Current State:**
27+
- Matches display full user avatar, name, and GitHub handle
28+
- All user information is visible in match cards
29+
30+
**Required Changes:**
31+
- Replace user avatar with a gray circle (`bg-muted` background)
32+
- Replace user name and handle with "Anonymous user" text
33+
- Apply `blur-sm` CSS class to anonymized text for visual indication
34+
- Maintain country flag display if available
35+
- Keep contribution data and tech stack information visible
36+
37+
**Technical Implementation:**
38+
```heex
39+
<!-- Current -->
40+
<.avatar class="h-12 w-12 rounded-full">
41+
<.avatar_image src={@user.avatar_url} alt={@user.name} />
42+
</.avatar>
43+
<span class="font-semibold">{@user.name}</span>
44+
45+
<!-- Anonymized -->
46+
<div class="h-12 w-12 rounded-full bg-muted"></div>
47+
<span class="font-semibold blur-sm">Anonymous user</span>
48+
```
49+
50+
### 2. Accurate Match Counting
51+
52+
**Current State:**
53+
- `Algora.Cloud.truncate_matches/2` limits matches to 15 for non-subscribed orgs
54+
- `Algora.Cloud.count_matches/1` returns fixed limits (50-100)
55+
- Display shows truncated count, not actual available matches
56+
57+
**Required Changes:**
58+
- Modify `get_job_matches/1` in `Algora.Settings` to count all available matches
59+
- Update match counting logic to return actual numbers instead of arbitrary limits
60+
- Display format: "X matches found" where X is the real count
61+
- Maintain existing subscription-based access controls for viewing matches
62+
63+
**Technical Implementation:**
64+
```elixir
65+
# In Algora.Settings.get_job_matches/1
66+
def get_job_matches(job) do
67+
total_matches = count_all_matches(job) # New function
68+
available_matches = get_subscription_limited_matches(job) # Existing logic
69+
70+
%{
71+
total_count: total_matches,
72+
matches: available_matches
73+
}
74+
end
75+
76+
# New helper function
77+
defp count_all_matches(job) do
78+
[
79+
tech_stack: job.tech_stack,
80+
email_required: true, # New criteria
81+
count_only: true
82+
]
83+
|> Algora.Cloud.list_top_matches()
84+
|> length()
85+
end
86+
```
87+
88+
### 3. Enhanced Matching Criteria
89+
90+
**Current State:**
91+
- Matching logic in `Algora.Cloud.list_top_matches/1` doesn't validate email availability
92+
- Users without email addresses may be included in matches
93+
94+
**Required Changes:**
95+
- Add email validation to matching criteria
96+
- A user should be considered a match if they have either:
97+
- `user.email` field populated, OR
98+
- `user.provider_meta["email"]` field populated
99+
- Update matching query to include this email check
100+
- Maintain existing tech stack matching logic
101+
102+
**Technical Implementation:**
103+
```elixir
104+
# In Algora.Cloud.list_top_matches/1
105+
def list_top_matches(opts) do
106+
# ... existing query setup ...
107+
108+
query =
109+
query
110+
|> where([u], not is_nil(u.email) or fragment("? IS NOT NULL", u.provider_meta["email"]))
111+
# ... rest of existing filters ...
112+
end
113+
```
114+
115+
### 4. GitHub Contribution Heatmaps
116+
117+
**Current State:**
118+
- `lib/algora_cloud/live/candidates_live.ex` has heatmap implementation
119+
- Heatmaps are stored in `user_heatmaps` table
120+
- Async sync functionality exists via `AlgoraCloud.Profiles.sync_heatmap_by/1`
121+
122+
**Required Changes:**
123+
- Add heatmap display to match cards in `job_live.ex`
124+
- Implement similar heatmap component as in `candidates_live.ex`
125+
- Add async heatmap syncing when socket connects for users without heatmap data
126+
- Display 17-week contribution grid with color-coded activity levels
127+
128+
**Technical Implementation:**
129+
130+
1. **Update socket assigns in `assign_applicants/1`:**
131+
```elixir
132+
defp assign_applicants(socket) do
133+
# ... existing code ...
134+
135+
# Fetch heatmaps for all developers
136+
heatmaps_map =
137+
developers
138+
|> Enum.map(& &1.id)
139+
|> AlgoraCloud.Profiles.list_heatmaps()
140+
|> Map.new(fn heatmap -> {heatmap.user_id, heatmap.data} end)
141+
142+
# Trigger async sync for missing heatmaps
143+
missing_heatmap_users =
144+
developers
145+
|> Enum.reject(&Map.has_key?(heatmaps_map, &1.id))
146+
147+
if connected?(socket) and length(missing_heatmap_users) > 0 do
148+
enqueue_heatmap_sync(missing_heatmap_users)
149+
end
150+
151+
socket
152+
|> assign(:heatmaps_map, heatmaps_map)
153+
# ... rest of existing assigns ...
154+
end
155+
156+
defp enqueue_heatmap_sync(users) do
157+
Task.start(fn ->
158+
for user <- users do
159+
AlgoraCloud.Profiles.sync_heatmap_by(id: user.id)
160+
end
161+
end)
162+
end
163+
```
164+
165+
2. **Add heatmap component to match cards:**
166+
```heex
167+
<!-- In match_card function -->
168+
<div :if={@heatmaps_map[@user.id]} class="mt-4">
169+
<.heatmap_display user_id={@user.id} heatmap_data={@heatmaps_map[@user.id]} />
170+
</div>
171+
```
172+
173+
3. **Implement heatmap component (based on candidates_live.ex):**
174+
```heex
175+
defp heatmap_display(assigns) do
176+
~H"""
177+
<div class="mt-4">
178+
<div class="flex items-center justify-between mb-2">
179+
<div class="text-xs text-muted-foreground uppercase font-semibold">
180+
{get_in(@heatmap_data, ["totalContributions"])} contributions in the last year
181+
</div>
182+
</div>
183+
<div class="grid grid-cols-[repeat(17,1fr)] gap-1">
184+
<%= for week <- get_in(@heatmap_data, ["weeks"]) |> Enum.take(-17) do %>
185+
<div class="grid grid-rows-7 gap-1">
186+
<%= for day <- week["contributionDays"] do %>
187+
<div
188+
class={"h-3 w-3 rounded-sm #{get_contribution_color(day["contributionCount"])}"}
189+
title={"#{day["contributionCount"]} contributions on #{format_date(day["date"])}"}
190+
>
191+
</div>
192+
<% end %>
193+
</div>
194+
<% end %>
195+
</div>
196+
</div>
197+
"""
198+
end
199+
200+
defp get_contribution_color(count) do
201+
cond do
202+
count == 0 -> "bg-muted/50"
203+
count in 1..5 -> "bg-success-400/40"
204+
count in 6..10 -> "bg-success-400/50"
205+
count in 11..15 -> "bg-success-400/70"
206+
count in 16..20 -> "bg-success-400/90"
207+
true -> "bg-success-400"
208+
end
209+
end
210+
```
211+
212+
## Implementation Plan
213+
214+
### Phase 1: Anonymization and Match Counting
215+
1. Update `match_card/1` function to display anonymized user information
216+
2. Modify `Algora.Settings.get_job_matches/1` to return actual match counts
217+
3. Update UI to display real match numbers
218+
4. Test anonymization with different user types and subscription levels
219+
220+
### Phase 2: Enhanced Matching Criteria
221+
1. Update `Algora.Cloud.list_top_matches/1` to include email validation
222+
2. Add database query filters for email requirements
223+
3. Update matching logic to check both `user.email` and `user.provider_meta["email"]`
224+
4. Test matching accuracy with email filtering
225+
226+
### Phase 3: Heatmap Integration
227+
1. Add heatmap data fetching to `assign_applicants/1`
228+
2. Implement async heatmap syncing for missing data
229+
3. Add heatmap component to match cards
230+
4. Test heatmap display and async syncing functionality
231+
232+
### Phase 4: Testing and Optimization
233+
1. Performance testing with large match sets
234+
2. UI/UX testing for anonymized cards
235+
3. Async operation monitoring for heatmap syncing
236+
4. Cross-browser compatibility testing
237+
238+
## Success Metrics
239+
240+
1. **Privacy Protection**: All match cards show anonymized information for non-subscribed viewers
241+
2. **Accurate Counts**: Match counts reflect actual available candidates, not artificial limits
242+
3. **Better Matching**: Only users with email addresses are included in matches
243+
4. **Enhanced UX**: Heatmaps provide visual contribution data for better candidate assessment
244+
5. **Performance**: Async heatmap syncing doesn't impact page load times
245+
246+
## Technical Considerations
247+
248+
### Database Impact
249+
- Heatmap syncing may increase database load
250+
- Consider implementing rate limiting for async sync operations
251+
- Monitor performance of email filtering queries
252+
253+
### Caching Strategy
254+
- Cache heatmap data to reduce GitHub API calls
255+
- Implement TTL for heatmap data (suggested: 24 hours)
256+
- Cache match counts to improve performance
257+
258+
### Error Handling
259+
- Handle GitHub API rate limits gracefully
260+
- Fallback display when heatmap data is unavailable
261+
- Graceful degradation if anonymization fails
262+
263+
### Security Considerations
264+
- Ensure anonymization cannot be bypassed client-side
265+
- Validate subscription status server-side for match access
266+
- Protect GitHub API tokens used for heatmap syncing
267+
268+
## Files to Modify
269+
270+
1. **`lib/algora_web/live/org/job_live.ex`** - Main implementation file
271+
2. **`lib/algora/settings/settings.ex`** - Match counting logic
272+
3. **`lib/algora_cloud/algora_cloud.ex`** - Matching criteria enhancement
273+
4. **`lib/algora_cloud/profiles.ex`** - Heatmap syncing (if needed)
274+
275+
## Dependencies
276+
277+
- Existing `AlgoraCloud.Profiles` module for heatmap functionality
278+
- `user_heatmaps` database table
279+
- GitHub GraphQL API access for contribution data
280+
- Existing subscription and authentication systems
281+
282+
## Rollout Strategy
283+
284+
1. **Development**: Implement features in feature branch
285+
2. **Staging**: Test with sample job postings and user data
286+
3. **Gradual Rollout**: Deploy to subset of organizations initially
287+
4. **Full Deployment**: Roll out to all organizations after validation
288+
5. **Monitoring**: Track performance metrics and user feedback
289+
290+
## Future Enhancements
291+
292+
1. **Advanced Anonymization**: Different levels of anonymization based on subscription tiers
293+
2. **Enhanced Heatmaps**: Additional metrics like PR review activity, issue contributions
294+
3. **Real-time Updates**: Live updating of match counts and heatmap data
295+
4. **Export Functionality**: Allow exporting of match data for subscribed organizations

0 commit comments

Comments
 (0)