From 4417ae0ff03b0508d44e64f3361cd1e88c50cc4c Mon Sep 17 00:00:00 2001 From: goon-foss Date: Mon, 3 Aug 2026 09:19:50 +0200 Subject: [PATCH] feat(tags): scal duplikaty liczby mnogiej + prewencja + skrocone liczniki 1) Duplikaty tagow wrocily, ale INNYM wzorcem niz w lipcu: alnum-klucz z natury nie lapie liczby mnogiej ('blowjob' != 'blowjobs'). Audyt: 1944 pary, m.in. Blowjob/Blowjobs (390728 vs 791), Teen/Teens (53096 vs 41294), Cumshot/Cumshots. Scalone oba kierunki (kanoniczny = wiekszy scene_count): 1938 + 2063 tagow, przeniesione 251251 + 26142 przypisan scen i 118522 + 392 filmow. Zostaje 581 par (lancuchy i remisy) - celowo pominiete, wymagaja recznej decyzji. 2) Prewencja w _resolve_by_altkey: gdy alnum-klucz nie trafi, probujemy obu form (+s / -s). Tylko proste 's' - form nieregularnych (panty/panties) NIE ruszamy, bo zbyt latwo o falszywy zlew (glass/glasses, bra/bras). Zweryfikowane na prodzie: Blowjobs->Blowjob, Teens->Teen, Cumshots->Cumshot, Lesbians->Lesbian. 3) UI: formatCount() skraca duze liczniki (181764 -> 181k, 1200 -> 1,2k) w tagach, performerach, ulubionych i filtrach filmow - user-request, chipy przestaja puchnac. --- app/resolve/tag_resolver.py | 29 ++++++++++++++++----- mobile/src/changelog.ts | 8 ++++++ mobile/src/components/MovieFiltersSheet.tsx | 7 ++--- mobile/src/lib/formatCount.ts | 20 ++++++++++++++ mobile/src/screens/FavoritesScreen.tsx | 5 ++-- mobile/src/screens/PerformersScreen.tsx | 3 ++- mobile/src/screens/TagsScreen.tsx | 3 ++- 7 files changed, 62 insertions(+), 13 deletions(-) create mode 100644 mobile/src/lib/formatCount.ts diff --git a/app/resolve/tag_resolver.py b/app/resolve/tag_resolver.py index 1646a0c..641912c 100644 --- a/app/resolve/tag_resolver.py +++ b/app/resolve/tag_resolver.py @@ -31,15 +31,32 @@ def _resolve_by_altkey(session: Session, name: str) -> Tag | None: key = _altkey(name) if not key: return None - return ( - session.execute( - select(Tag) - .where(func.regexp_replace(func.lower(func.btrim(Tag.name)), "[^a-z0-9]", "", "g") == key) - .order_by(Tag.scene_count.desc()) - ) + expr = func.regexp_replace(func.lower(func.btrim(Tag.name)), "[^a-z0-9]", "", "g") + hit = ( + session.execute(select(Tag).where(expr == key).order_by(Tag.scene_count.desc())) .scalars() .first() ) + if hit is not None: + return hit + # Liczba mnoga. Alnum-klucz z natury jej NIE łapie ("blowjob" != "blowjobs"), więc + # duplikaty odrastały mimo tamtej prewencji (audyt 2026-08-02: 1944 pary, m.in. + # Blowjob/Blowjobs, Teen/Teens, Cumshot/Cumshots). Próbujemy obu kierunków: nowe + # "Blowjobs" trafia w istniejące "Blowjob", a nowe "Blowjob" w istniejące "Blowjobs". + # Tylko proste `+s` — form nieregularnych (panty/panties) świadomie nie ruszamy, bo + # zbyt łatwo o fałszywy zlew (glass/glasses, bra/bras). + alts = [key + "s"] if not key.endswith("s") else [key[:-1]] + for alt in alts: + if len(alt) < 3: + continue + hit = ( + session.execute(select(Tag).where(expr == alt).order_by(Tag.scene_count.desc())) + .scalars() + .first() + ) + if hit is not None: + return hit + return None def _canonical_dup2_slug(session: Session, slug: str) -> str: diff --git a/mobile/src/changelog.ts b/mobile/src/changelog.ts index f0cdfbe..ead482c 100644 --- a/mobile/src/changelog.ts +++ b/mobile/src/changelog.ts @@ -16,6 +16,14 @@ export type ChangelogEntry = { }; export const CHANGELOG: ChangelogEntry[] = [ + { + id: '2026-08-02', + date: 'August 2026', + items: [ + 'Tag and performer counts are now shortened (181k instead of 181 764), so the chips stay readable.', + 'Merged duplicate tags that differed only by a plural, so "Blowjob" and "Blowjobs" are one tag again.', + ], + }, { id: '2026-07-28b', date: 'July 2026', diff --git a/mobile/src/components/MovieFiltersSheet.tsx b/mobile/src/components/MovieFiltersSheet.tsx index 9b8ef64..1804b07 100644 --- a/mobile/src/components/MovieFiltersSheet.tsx +++ b/mobile/src/components/MovieFiltersSheet.tsx @@ -7,6 +7,7 @@ * przez chipy; backend wspiera comma-separated. */ import { useQuery } from '@tanstack/react-query'; +import { formatCount } from '../lib/formatCount'; import React, { useState } from 'react'; import { ActivityIndicator, @@ -257,7 +258,7 @@ export function MovieFiltersSheet({ visible, value, onChange, onClose }: Props) > {t.name} - {t.scene_count > 0 ? ` · ${t.scene_count}` : ''} + {t.scene_count > 0 ? ` · ${formatCount(t.scene_count)}` : ''} ); @@ -284,7 +285,7 @@ export function MovieFiltersSheet({ visible, value, onChange, onClose }: Props) > {s.name} - {s.scene_count > 0 ? ` · ${s.scene_count}` : ''} + {s.scene_count > 0 ? ` · ${formatCount(s.scene_count)}` : ''} ); @@ -336,7 +337,7 @@ export function MovieFiltersSheet({ visible, value, onChange, onClose }: Props) > {p.canonical_name} - {p.scene_count > 0 ? ` · ${p.scene_count}` : ''} + {p.scene_count > 0 ? ` · ${formatCount(p.scene_count)}` : ''} ))} diff --git a/mobile/src/lib/formatCount.ts b/mobile/src/lib/formatCount.ts new file mode 100644 index 0000000..330f4f2 --- /dev/null +++ b/mobile/src/lib/formatCount.ts @@ -0,0 +1,20 @@ +/** + * Skracanie dużych liczników (user-request 2026-08-02: "jeśli jest 181 764, to napisać + * 181k"). Popularne tagi mają setki tysięcy scen, a pełna liczba rozpycha chipy i nic + * nie wnosi — przy tej skali liczy się rząd wielkości, nie dokładność. + * + * Do 999 pokazujemy dokładnie. Powyżej: 1,2k / 12k / 181k / 1,2M. Jedno miejsce po + * przecinku tylko gdy liczba jest jednocyfrowa w swojej skali (1,2k ale już 12k), + * żeby chip nie puchł. Separator dziesiętny przecinkiem, bo apka jest angielska tylko + * z nazwy, a i tak czytają to ludzie z całego świata — kropka myliłaby się z tysiącami. + */ +export function formatCount(n: number | null | undefined): string { + const v = typeof n === 'number' && isFinite(n) ? Math.max(0, Math.trunc(n)) : 0; + if (v < 1000) return String(v); + if (v < 1_000_000) { + const k = v / 1000; + return (k < 10 ? k.toFixed(1).replace(/\.0$/, '').replace('.', ',') : String(Math.round(k))) + 'k'; + } + const m = v / 1_000_000; + return (m < 10 ? m.toFixed(1).replace(/\.0$/, '').replace('.', ',') : String(Math.round(m))) + 'M'; +} diff --git a/mobile/src/screens/FavoritesScreen.tsx b/mobile/src/screens/FavoritesScreen.tsx index 4cca111..eb68f69 100644 --- a/mobile/src/screens/FavoritesScreen.tsx +++ b/mobile/src/screens/FavoritesScreen.tsx @@ -4,6 +4,7 @@ // żeby PerformerScenesScreen / StudioScenesScreen mógł pokazać NEW badge na świeżych // scenach. import { useNavigation } from '@react-navigation/native'; +import { formatCount } from '../lib/formatCount'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import React from 'react'; @@ -411,7 +412,7 @@ function PerformerRow({ {fav.canonical_name} - {fav.scene_count} {fav.scene_count === 1 ? 'scene' : 'scenes'} + {formatCount(fav.scene_count)} {fav.scene_count === 1 ? 'scene' : 'scenes'} {fav.new_count > 0 ? ( @@ -486,7 +487,7 @@ function StudioRow({ {fav.name} - {fav.scene_count} {fav.scene_count === 1 ? 'scene' : 'scenes'} + {formatCount(fav.scene_count)} {fav.scene_count === 1 ? 'scene' : 'scenes'} {fav.network ? ` · ${fav.network}` : ''} diff --git a/mobile/src/screens/PerformersScreen.tsx b/mobile/src/screens/PerformersScreen.tsx index 10302a8..ad81f6a 100644 --- a/mobile/src/screens/PerformersScreen.tsx +++ b/mobile/src/screens/PerformersScreen.tsx @@ -1,6 +1,7 @@ // Lista performerów + tap → PerformerScenes. // Domyślny order: scene_count desc (najpierw popularni); search po name_normalized. import { useNavigation } from '@react-navigation/native'; +import { formatCount } from '../lib/formatCount'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { useQuery } from '@tanstack/react-query'; import React, { useState } from 'react'; @@ -153,7 +154,7 @@ function PerformerRow({ - {performer.scene_count} {performer.scene_count === 1 ? 'scene' : 'scenes'} + {formatCount(performer.scene_count)} {performer.scene_count === 1 ? 'scene' : 'scenes'} {performer.gender ? ( diff --git a/mobile/src/screens/TagsScreen.tsx b/mobile/src/screens/TagsScreen.tsx index df32df4..3528b46 100644 --- a/mobile/src/screens/TagsScreen.tsx +++ b/mobile/src/screens/TagsScreen.tsx @@ -1,6 +1,7 @@ // Lista tagów + tap → TagScenes. Domyślny order: popular (po liczbie scen desc). // Layout: chip-grid, bo nazwy tagów są krótkie i upacking ich w listę marnuje miejsce. import { useNavigation } from '@react-navigation/native'; +import { formatCount } from '../lib/formatCount'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { useQuery } from '@tanstack/react-query'; import React, { useState } from 'react'; @@ -131,7 +132,7 @@ function TagChip({ tag, onPress }: { tag: TagCount; onPress: () => void }) { {tag.name} - {tag.scene_count} + {formatCount(tag.scene_count)} );