added anon metrics to recommendations

This commit is contained in:
nvrl committed 2026-07-26 17:23:07 +02:00
1 parent daeda71eca
commit 3757b784de
23 files changed
+1544 -66

No files matched your search

+7 -3
View File
@@ -59,8 +59,10 @@ export interface Recommendation {
predicted_score: number;
/** "N tasters like you" count (cf) or rating count (popular). */
contributing_neighbours: number;
/** "cf" | "popular" — frontend picks the reason copy. */
reason: 'cf' | 'popular';
/** Anonymous shop reviews (reviews.io) backing this flavor; 0 if none. */
external_reviews: number;
/** "cf" | "popular" | "shop" (no community ratings yet — shop reviews only). */
reason: 'cf' | 'popular' | 'shop';
}
export interface DashboardData {
@@ -81,8 +83,10 @@ export interface SimilarFlavor {
average_rating: number | null;
is_legacy: boolean;
is_available: boolean;
/** Adjusted-cosine similarity to the target flavor, in (0, 1]. */
/** Blended, confidence-discounted similarity. Ranking only — not a "% match". */
similarity: number;
/** Users who rated both this flavor and the target. */
co_raters: number;
/** Anonymous shop reviewers who liked both this flavor and the target. */
external_co_reviewers: number;
}
+8
View File
@@ -40,6 +40,7 @@ const resources = {
recHeading: 'Tasters like you loved',
recReasonCf: '{{count}} tasters like you',
recReasonPopular: 'Popular pick',
recReasonShop: '{{count}} shop buyers',
predictedForYou: 'Predicted for you',
myRatings: 'My Ratings',
missing: 'Missing',
@@ -179,6 +180,9 @@ const resources = {
similarHeading: 'People who liked this also liked',
similarCoRaters: '{{count}} rated both',
similarTooltip: 'Tasters who rated both this flavor and the one above',
similarShopCoReviewers: '{{count}} shop buyers liked both',
similarShopTooltip:
'Nobody here has rated both yet — this pairing comes from anonymous shop reviews',
rateThisFlavor: 'Rate this flavor',
scoreLabel: 'Your score, 1 to 10',
submitReview: 'Submit Review',
@@ -603,6 +607,7 @@ const resources = {
recHeading: 'Tester wie du mochten',
recReasonCf: '{{count}} Tester wie du',
recReasonPopular: 'Beliebte Wahl',
recReasonShop: '{{count}} Shop-Käufer',
predictedForYou: 'Für dich vorhergesagt',
myRatings: 'Meine Bewertungen',
missing: 'Fehlend',
@@ -742,6 +747,9 @@ const resources = {
similarHeading: 'Wer das mochte, mochte auch',
similarCoRaters: '{{count}} bewerteten beide',
similarTooltip: 'Tester, die diese Sorte und die obige bewertet haben',
similarShopCoReviewers: '{{count}} Shop-Käufer mochten beide',
similarShopTooltip:
'Hier hat noch niemand beide bewertet — diese Paarung stammt aus anonymen Shop-Bewertungen',
rateThisFlavor: 'Diese Sorte bewerten',
scoreLabel: 'Deine Bewertung, 1 bis 10',
submitReview: 'Bewertung abschicken',
@@ -15,7 +15,9 @@ export interface RecommendationCardProps {
/**
* A single recommended flavor. The reason chip is **count-only** (no neighbour
* names, per product decision): CF → "N tasters like you", popularity → "Popular".
* names, per product decision): CF → "N tasters like you", popularity →
* "Popular", shop → "N shop buyers" for flavors nobody here has rated yet, whose
* score comes from the anonymous reviews.io feed alone.
*/
const RecommendationCard: React.FC<RecommendationCardProps> = ({ rec }) => {
const { t } = useTranslation();
@@ -23,7 +25,9 @@ const RecommendationCard: React.FC<RecommendationCardProps> = ({ rec }) => {
const reasonLabel =
rec.reason === 'cf'
? t('dashboard.recReasonCf', { count: rec.contributing_neighbours })
: t('dashboard.recReasonPopular');
: rec.reason === 'shop'
? t('dashboard.recReasonShop', { count: rec.external_reviews })
: t('dashboard.recReasonPopular');
return (
<GlassCard
@@ -81,12 +81,26 @@ const SimilarCard: React.FC<{ item: SimilarFlavor }> = ({ item }) => {
<Typography variant="caption" color="text.secondary" display="block" sx={{ mb: 1 }}>
{t(`categories.${item.category_slug}`, { defaultValue: item.category_name })}
</Typography>
<Tooltip title={t('flavorDetail.similarTooltip')}>
{/* Pairs the community hasn't co-rated yet can still surface, backed by
anonymous shop reviewers — don't claim "0 rated both" for those. */}
<Tooltip
title={t(
item.co_raters > 0
? 'flavorDetail.similarTooltip'
: 'flavorDetail.similarShopTooltip',
)}
>
<Chip
size="small"
icon={<GroupsIcon />}
label={t('flavorDetail.similarCoRaters', { count: item.co_raters })}
color="primary"
label={
item.co_raters > 0
? t('flavorDetail.similarCoRaters', { count: item.co_raters })
: t('flavorDetail.similarShopCoReviewers', {
count: item.external_co_reviewers,
})
}
color={item.co_raters > 0 ? 'primary' : 'default'}
variant="outlined"
sx={{ fontWeight: 700, maxWidth: '100%' }}
/>