66 */
77import type { Id } from "../_generated/dataModel" ;
88import type { QueryCtx } from "../_generated/server" ;
9- import { notDeleted , isUserLoggedActivity } from "./activityFilters" ;
9+ import { notDeleted , PR_ELIGIBLE_KINDS } from "./activityFilters" ;
1010import { formatDateOnlyFromUtcMs } from "./dateOnly" ;
1111
1212// ─── Types ───────────────────────────────────────────────────────────────────
@@ -419,29 +419,51 @@ export async function previewPrWeekEnd(
419419
420420// ─── Shared Helpers ──────────────────────────────────────────────────────────
421421
422+ /**
423+ * Build a set of activity type IDs whose kind is eligible for PR day calculations.
424+ * Only core, special, and penalty activities count — bonus activities (mindfulness,
425+ * mini-game bonuses, category leader, skiing, etc.) are excluded.
426+ */
427+ async function getPrEligibleTypeIds (
428+ ctx : ReadCtx ,
429+ challengeId : Id < "challenges" > ,
430+ ) : Promise < Set < Id < "activityTypes" > > > {
431+ const types = await ctx . db
432+ . query ( "activityTypes" )
433+ . withIndex ( "challengeId" , ( q ) => q . eq ( "challengeId" , challengeId ) )
434+ . collect ( ) ;
435+
436+ return new Set (
437+ types . filter ( ( t ) => PR_ELIGIBLE_KINDS . has ( t . kind ?? "" ) ) . map ( ( t ) => t . _id ) ,
438+ ) ;
439+ }
440+
422441/**
423442 * Get a user's max single-day points total from all days before `beforeDate`.
424- * Excludes system-generated bonus activities (mini_game, category_leader, etc. ).
443+ * Only includes core, special, and penalty activities (excludes bonus kind ).
425444 */
426445export async function calculateMaxDailyPoints (
427446 ctx : ReadCtx ,
428447 userId : Id < "users" > ,
429448 challengeId : Id < "challenges" > ,
430449 beforeDate : number ,
431450) : Promise < number > {
432- const activities = await ctx . db
433- . query ( "activities" )
434- . withIndex ( "by_user_challenge_date" , ( q ) =>
435- q . eq ( "userId" , userId ) . eq ( "challengeId" , challengeId ) ,
436- )
437- . filter ( notDeleted )
438- . collect ( ) ;
451+ const [ activities , eligibleTypeIds ] = await Promise . all ( [
452+ ctx . db
453+ . query ( "activities" )
454+ . withIndex ( "by_user_challenge_date" , ( q ) =>
455+ q . eq ( "userId" , userId ) . eq ( "challengeId" , challengeId ) ,
456+ )
457+ . filter ( notDeleted )
458+ . collect ( ) ,
459+ getPrEligibleTypeIds ( ctx , challengeId ) ,
460+ ] ) ;
439461
440462 const dailyPoints : Record < string , number > = { } ;
441463
442464 for ( const activity of activities ) {
443465 if ( activity . loggedDate >= beforeDate ) continue ;
444- if ( ! isUserLoggedActivity ( activity ) ) continue ;
466+ if ( ! eligibleTypeIds . has ( activity . activityTypeId ) ) continue ;
445467
446468 const dateStr = formatDateOnlyFromUtcMs ( activity . loggedDate ) ;
447469 dailyPoints [ dateStr ] = ( dailyPoints [ dateStr ] || 0 ) + activity . pointsEarned ;
@@ -452,8 +474,8 @@ export async function calculateMaxDailyPoints(
452474}
453475
454476/**
455- * Get total non-bonus points earned by a user during a time period.
456- * Excludes system-generated bonus activities to prevent circular scoring .
477+ * Get total points earned by a user during a time period.
478+ * Only includes core, special, and penalty activities (excludes bonus kind) .
457479 */
458480export async function getPointsInPeriod (
459481 ctx : ReadCtx ,
@@ -462,27 +484,30 @@ export async function getPointsInPeriod(
462484 startDate : number ,
463485 endDate : number ,
464486) : Promise < number > {
465- const activities = await ctx . db
466- . query ( "activities" )
467- . withIndex ( "by_user_challenge_date" , ( q ) =>
468- q . eq ( "userId" , userId ) . eq ( "challengeId" , challengeId ) ,
469- )
470- . filter ( notDeleted )
471- . collect ( ) ;
487+ const [ activities , eligibleTypeIds ] = await Promise . all ( [
488+ ctx . db
489+ . query ( "activities" )
490+ . withIndex ( "by_user_challenge_date" , ( q ) =>
491+ q . eq ( "userId" , userId ) . eq ( "challengeId" , challengeId ) ,
492+ )
493+ . filter ( notDeleted )
494+ . collect ( ) ,
495+ getPrEligibleTypeIds ( ctx , challengeId ) ,
496+ ] ) ;
472497
473498 return activities
474499 . filter (
475500 ( a ) =>
476501 a . loggedDate >= startDate &&
477502 a . loggedDate <= endDate &&
478- isUserLoggedActivity ( a ) ,
503+ eligibleTypeIds . has ( a . activityTypeId ) ,
479504 )
480505 . reduce ( ( sum , a ) => sum + a . pointsEarned , 0 ) ;
481506}
482507
483508/**
484509 * Get the maximum single-day points total during a time period.
485- * Excludes system-generated bonus activities (mini_game, category_leader, etc. ).
510+ * Only includes core, special, and penalty activities (excludes bonus kind ).
486511 */
487512export async function getMaxDailyPointsInPeriod (
488513 ctx : ReadCtx ,
@@ -491,21 +516,24 @@ export async function getMaxDailyPointsInPeriod(
491516 startDate : number ,
492517 endDate : number ,
493518) : Promise < number > {
494- const activities = await ctx . db
495- . query ( "activities" )
496- . withIndex ( "by_user_challenge_date" , ( q ) =>
497- q . eq ( "userId" , userId ) . eq ( "challengeId" , challengeId ) ,
498- )
499- . filter ( notDeleted )
500- . collect ( ) ;
519+ const [ activities , eligibleTypeIds ] = await Promise . all ( [
520+ ctx . db
521+ . query ( "activities" )
522+ . withIndex ( "by_user_challenge_date" , ( q ) =>
523+ q . eq ( "userId" , userId ) . eq ( "challengeId" , challengeId ) ,
524+ )
525+ . filter ( notDeleted )
526+ . collect ( ) ,
527+ getPrEligibleTypeIds ( ctx , challengeId ) ,
528+ ] ) ;
501529
502530 const dailyPoints : Record < string , number > = { } ;
503531
504532 for ( const activity of activities ) {
505533 if (
506534 activity . loggedDate < startDate ||
507535 activity . loggedDate > endDate ||
508- ! isUserLoggedActivity ( activity )
536+ ! eligibleTypeIds . has ( activity . activityTypeId )
509537 )
510538 continue ;
511539
0 commit comments