Skip to content

Commit 5348ba1

Browse files
authored
Merge pull request #99 from parker-server/insights-phase3
Adds the third Parker insights experience
2 parents d24e678 + adbf3b8 commit 5348ba1

7 files changed

Lines changed: 709 additions & 39 deletions

File tree

app/api/insights.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,3 +290,84 @@ async def get_character_collaborations(
290290
"limit": limit,
291291
**_serialize_visible_matrix(pairs, limit),
292292
}
293+
294+
295+
@router.get("/creator-character-collaborations", name="creator_character_collaborations")
296+
async def get_creator_character_collaborations(
297+
db: SessionDep,
298+
current_user: CurrentUser,
299+
role_a: CreatorRole = Query("writer"),
300+
library_id: Optional[int] = Query(None, ge=1),
301+
min_shared: int = Query(2, ge=1, le=100),
302+
limit: int = Query(12, ge=2, le=15, description="Maximum creators and characters shown per axis"),
303+
):
304+
"""
305+
Aggregates creator-to-character pairings for heatmap-style insights.
306+
"""
307+
if library_id is not None:
308+
_ensure_library_access(library_id, current_user)
309+
310+
credit_a = aliased(ComicCredit)
311+
person_a = aliased(Person)
312+
char_link_b = comic_characters.alias("char_link_b")
313+
character_b = aliased(Character)
314+
315+
shared_issue_count = func.count(func.distinct(Comic.id))
316+
shared_series_count = func.count(func.distinct(Series.id))
317+
318+
query = (
319+
db.query(
320+
person_a.id.label("person_a_id"),
321+
person_a.name.label("person_a"),
322+
character_b.id.label("person_b_id"),
323+
character_b.name.label("person_b"),
324+
shared_issue_count.label("shared_issues"),
325+
shared_series_count.label("shared_series"),
326+
func.min(Series.name).label("sample_series"),
327+
)
328+
.select_from(Comic)
329+
.join(Volume, Comic.volume_id == Volume.id)
330+
.join(Series, Volume.series_id == Series.id)
331+
.join(credit_a, credit_a.comic_id == Comic.id)
332+
.join(person_a, person_a.id == credit_a.person_id)
333+
.join(char_link_b, char_link_b.c.comic_id == Comic.id)
334+
.join(character_b, character_b.id == char_link_b.c.character_id)
335+
.filter(credit_a.role == role_a)
336+
)
337+
338+
if not current_user.is_superuser:
339+
allowed_ids = [lib.id for lib in current_user.accessible_libraries]
340+
query = query.filter(Series.library_id.in_(allowed_ids))
341+
342+
if library_id is not None:
343+
query = query.filter(Series.library_id == library_id)
344+
345+
age_filter = get_series_age_restriction(current_user)
346+
if age_filter is not None:
347+
query = query.filter(age_filter)
348+
349+
pair_cap = max(limit * limit * 2, 50)
350+
351+
pairs = (
352+
query.group_by(person_a.id, person_a.name, character_b.id, character_b.name)
353+
.having(shared_issue_count >= min_shared)
354+
.order_by(desc("shared_issues"), person_a.name.asc(), character_b.name.asc())
355+
.limit(pair_cap)
356+
.all()
357+
)
358+
359+
if not pairs:
360+
return _empty_matrix_payload(
361+
role_a=role_a,
362+
library_id=library_id,
363+
min_shared=min_shared,
364+
limit=limit,
365+
)
366+
367+
return {
368+
"role_a": role_a,
369+
"library_id": library_id,
370+
"min_shared": min_shared,
371+
"limit": limit,
372+
**_serialize_visible_matrix(pairs, limit),
373+
}

app/routers/pages.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ async def insights_character_chemistry(request: Request, user: CurrentUser):
4848
"""Character co-appearance insights page"""
4949
return templates.TemplateResponse(request=request, name="insights_character.html")
5050

51+
52+
@router.get("/insights/writer-character", response_class=HTMLResponse, name="insights_writer_character")
53+
async def insights_writer_character(request: Request, user: CurrentUser):
54+
"""Writer to character insights page"""
55+
return templates.TemplateResponse(request=request, name="insights_writer_character.html")
56+
5157
@router.get("/collections", response_class=HTMLResponse, name="collections")
5258
async def collections_view(request: Request, user: CurrentUser):
5359
"""Collections page"""

app/templates/insights.html

Lines changed: 23 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -14,56 +14,40 @@ <h1 class="mt-3 text-4xl md:text-5xl font-black text-white tracking-tight">Metad
1414
</div>
1515
</section>
1616

17-
<section class="grid grid-cols-1 xl:grid-cols-2 gap-6">
17+
<section class="grid grid-cols-1 xl:grid-cols-3 gap-6">
1818
<a href="{{ url('/insights/creator-chemistry') }}" class="group rounded-3xl border border-gray-700 bg-gray-800/80 hover:border-blue-500/60 hover:bg-gray-800 px-6 py-6 shadow-xl transition-all">
19-
<div class="flex items-start justify-between gap-4">
20-
<div>
21-
<div class="text-[10px] uppercase tracking-[0.25em] text-blue-300">Phase 1</div>
22-
<h2 class="mt-3 text-2xl font-black text-white">Creator Chemistry</h2>
23-
<p class="mt-3 text-sm leading-6 text-gray-400">
24-
See which writers, pencillers, inkers, colorists, and editors show up together most often across your library.
25-
</p>
26-
</div>
27-
<div class="rounded-full border border-blue-500/30 bg-blue-500/10 px-3 py-1 text-xs font-bold uppercase tracking-wider text-blue-200">
28-
Live
29-
</div>
30-
</div>
31-
32-
<div class="mt-6 grid grid-cols-1 md:grid-cols-3 gap-3 text-xs text-gray-400">
33-
<div class="rounded-xl border border-gray-700 bg-gray-900/50 px-4 py-3">Creator role pair filters</div>
34-
<div class="rounded-xl border border-gray-700 bg-gray-900/50 px-4 py-3">Matrix drill-down to Search</div>
35-
<div class="rounded-xl border border-gray-700 bg-gray-900/50 px-4 py-3">Visible collaboration totals</div>
36-
</div>
19+
<h2 class="text-2xl font-black text-white">Creator Chemistry</h2>
20+
<p class="mt-3 text-sm leading-6 text-gray-400">
21+
See which writers, pencillers, inkers, colorists, and editors show up together most often across your library.
22+
</p>
3723

3824
<div class="mt-6 inline-flex items-center gap-2 text-sm font-bold text-blue-300 group-hover:text-blue-200">
3925
Open Creator Chemistry
40-
<span aria-hidden="true"></span>
26+
<span aria-hidden="true">&rarr;</span>
4127
</div>
4228
</a>
4329

4430
<a href="{{ url('/insights/character-chemistry') }}" class="group rounded-3xl border border-gray-700 bg-gray-800/80 hover:border-emerald-500/60 hover:bg-gray-800 px-6 py-6 shadow-xl transition-all">
45-
<div class="flex items-start justify-between gap-4">
46-
<div>
47-
<div class="text-[10px] uppercase tracking-[0.25em] text-emerald-300">Phase 2</div>
48-
<h2 class="mt-3 text-2xl font-black text-white">Character Chemistry</h2>
49-
<p class="mt-3 text-sm leading-6 text-gray-400">
50-
Explore which characters co-appear most often, surface recurring team-ups, and jump into the matching issues in one click.
51-
</p>
52-
</div>
53-
<div class="rounded-full border border-emerald-500/30 bg-emerald-500/10 px-3 py-1 text-xs font-bold uppercase tracking-wider text-emerald-200">
54-
New
55-
</div>
56-
</div>
57-
58-
<div class="mt-6 grid grid-cols-1 md:grid-cols-3 gap-3 text-xs text-gray-400">
59-
<div class="rounded-xl border border-gray-700 bg-gray-900/50 px-4 py-3">Character co-appearance matrix</div>
60-
<div class="rounded-xl border border-gray-700 bg-gray-900/50 px-4 py-3">Top team-up sidebar</div>
61-
<div class="rounded-xl border border-gray-700 bg-gray-900/50 px-4 py-3">Search handoff with dual character rules</div>
62-
</div>
31+
<h2 class="text-2xl font-black text-white">Character Chemistry</h2>
32+
<p class="mt-3 text-sm leading-6 text-gray-400">
33+
Explore which characters co-appear most often, surface recurring team-ups, and jump into the matching issues in one click.
34+
</p>
6335

6436
<div class="mt-6 inline-flex items-center gap-2 text-sm font-bold text-emerald-300 group-hover:text-emerald-200">
6537
Open Character Chemistry
66-
<span aria-hidden="true"></span>
38+
<span aria-hidden="true">&rarr;</span>
39+
</div>
40+
</a>
41+
42+
<a href="{{ url('/insights/writer-character') }}" class="group rounded-3xl border border-gray-700 bg-gray-800/80 hover:border-amber-500/60 hover:bg-gray-800 px-6 py-6 shadow-xl transition-all">
43+
<h2 class="text-2xl font-black text-white">Writer to Character</h2>
44+
<p class="mt-3 text-sm leading-6 text-gray-400">
45+
See which writers are most closely associated with which characters, then jump straight into the matching books.
46+
</p>
47+
48+
<div class="mt-6 inline-flex items-center gap-2 text-sm font-bold text-amber-300 group-hover:text-amber-200">
49+
Open Writer to Character
50+
<span aria-hidden="true">&rarr;</span>
6751
</div>
6852
</a>
6953
</section>

app/templates/insights_character.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ <h1 class="mt-3 text-4xl font-black text-white tracking-tight">Character Chemist
2222
<span class="px-3 py-1.5 rounded-full border border-emerald-500/40 bg-emerald-500/10 text-xs font-bold uppercase tracking-wider text-emerald-200">
2323
Character Chemistry
2424
</span>
25+
<a href="{{ url('/insights/writer-character') }}" class="px-3 py-1.5 rounded-full border border-gray-700 text-xs font-bold uppercase tracking-wider text-gray-300 hover:text-white hover:border-amber-500/50 transition-colors">
26+
Writer to Character
27+
</a>
2528
</div>
2629
</div>
2730

app/templates/insights_creator.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ <h1 class="mt-3 text-4xl font-black text-white tracking-tight">Creator Chemistry
2222
<a href="{{ url('/insights/character-chemistry') }}" class="px-3 py-1.5 rounded-full border border-gray-700 text-xs font-bold uppercase tracking-wider text-gray-300 hover:text-white hover:border-emerald-500/50 transition-colors">
2323
Character Chemistry
2424
</a>
25+
<a href="{{ url('/insights/writer-character') }}" class="px-3 py-1.5 rounded-full border border-gray-700 text-xs font-bold uppercase tracking-wider text-gray-300 hover:text-white hover:border-amber-500/50 transition-colors">
26+
Writer to Character
27+
</a>
2528
</div>
2629
</div>
2730

0 commit comments

Comments
 (0)