def _get_recent_rating_stats(
    self, scoredNotes: pd.DataFrame, ratings: pd.DataFrame, prepareForTraining: bool
  ):
    """Generate counts of ratings within the last 1/5/15/20 minutes.

    Note that this process must work differently during training and in production, since
    during training future ratings will be available.  We solve this by treating the
    _SCORING_CUTOFF_MTS as the effective time at which scoring occurred, and include ratings
    based on that timestamp.

    Args:
      scoredNotes: pd.DataFrame specifying scoring cutoff timestamps
      ratings: pd.DataFrame containing local ratings.
      prepareForTraining: bool specifying whether to prune ratings.
    """
    # Define notion of effective present for each rating
    initialRatings = len(ratings)
    ratings = ratings[[c.noteIdKey, c.createdAtMillisKey]].copy()
    if prepareForTraining:
      ratings = ratings.merge(
        scoredNotes[[c.noteIdKey, _SCORING_CUTOFF_MTS]].rename(
          columns={_SCORING_CUTOFF_MTS: "effectivePresent"}
        )
      )
    else:
      ratings["effectivePresent"] = ratings[c.createdAtMillisKey].max()
    assert len(ratings) == initialRatings
    assert (ratings[c.createdAtMillisKey] > ratings["effectivePresent"]).sum() == 0
    assert ratings[c.createdAtMillisKey].isna().sum() == 0
    assert ratings["effectivePresent"].isna().sum() == 0
    # Develop counts and ratios of recent ratings in specific time ranges
    ratingTotals = (
      ratings[[c.noteIdKey]]
      .value_counts()
      .to_frame()
      .reset_index(drop=False)
      .rename(columns={"count": "total"})
    )
    ratingTotals = scoredNotes[[c.noteIdKey]].merge(ratingTotals, how="left")
    ratingTotals = ratingTotals.fillna({"total": 0}).astype(pd.Int64Dtype())
    for cutoff in _RATING_TIME_BUCKETS:
      afterCutoff = ratings[
        ratings[c.createdAtMillisKey] > (ratings["effectivePresent"] - (1000 * 60 * cutoff))
      ]
      cutoffCount = (
        afterCutoff[[c.noteIdKey]]
        .value_counts()
        .to_frame()
        .reset_index(drop=False)
        .rename(columns={"count": f"RECENT_{cutoff}_TOTAL"})
      )
      ratingTotals = ratingTotals.merge(cutoffCount, how="left").fillna(0)
    ratingTotals = ratingTotals.astype(pd.Int64Dtype())
    for cutoff in _RATING_TIME_BUCKETS:
      ratingTotals[f"RECENT_{cutoff}_RATIO"] = ratingTotals[f"RECENT_{cutoff}_TOTAL"] / (
        ratingTotals["total"].clip(lower=1)
      )
    return ratingTotals[[c.noteIdKey] + _RECENT_RATING_COLS]
Generate counts of ratings within the last 1/5/15/20 minutes.

    Note that this process must work differently during training and in production, since
    during training future ratings will be available.  We solve this by treating the
    _SCORING_CUTOFF_MTS as the effective time at which scoring occurred, and include ratings
    based on that timestamp.

    Args:
      scoredNotes: pd.DataFrame specifying scoring cutoff timestamps
      ratings: pd.DataFrame containing local ratings.
      prepareForTraining: bool specifying whether to prune ratings.
    
sourcecode/scoring/pflip_plus_model.py