def post_note_scoring(
  scorers: Dict[Scorers, List[Scorer]],
  scoredNotes: pd.DataFrame,
  auxiliaryNoteInfo: pd.DataFrame,
  ratings: pd.DataFrame,
  noteStatusHistory: pd.DataFrame,
  noteSubsetsAndMaxFlipRates: List[c.NoteSubset],
  enabledScorers: Optional[Set[Scorers]] = None,
  strictColumns: bool = True,
  checkFlips: bool = True,
  enableNmrDueToMinStableCrhTime: bool = True,
):
  """
  Apply individual scoring models and obtained merged result.
  """
  postScoringStartTime = time.time()
  # Augment scoredNotes and auxiliaryNoteInfo with additional attributes for each note
  # which are computed over the corpus of notes / ratings as a whole and are independent
  # of any particular model.

  with c.time_block("Post-scorers: Compute note stats"):
    scoredNotesCols, auxiliaryNoteInfoCols = _compute_note_stats(ratings, noteStatusHistory)
    scoredNotes = scoredNotes.merge(scoredNotesCols, on=c.noteIdKey)
    auxiliaryNoteInfo = auxiliaryNoteInfo.merge(auxiliaryNoteInfoCols, on=c.noteIdKey)

  # Assign final status to notes based on individual model scores and note attributes.
  with c.time_block("Post-scorers: Meta score"):
    scoredNotesCols, auxiliaryNoteInfoCols = meta_score(
      scorers,
      scoredNotes,
      auxiliaryNoteInfo,
      noteStatusHistory[
        [
          c.noteIdKey,
          c.lockedStatusKey,
          c.timestampMillisOfNmrDueToMinStableCrhTimeKey,
          c.firstNonNMRLabelKey,
        ]
      ],
      enabledScorers,
      enableNmrDueToMinStableCrhTime,
    )

  with c.time_block("Post-scorers: Join scored notes"):
    scoredNotes = scoredNotes.merge(scoredNotesCols, on=c.noteIdKey)
    scoredNotes[c.timestampMillisOfNoteCurrentLabelKey] = c.epochMillis
    auxiliaryNoteInfo = auxiliaryNoteInfo.merge(auxiliaryNoteInfoCols, on=c.noteIdKey)

    # Validate that no notes were dropped or duplicated.
    assert len(scoredNotes) == len(
      noteStatusHistory
    ), "noteStatusHistory should be complete, and all notes should be scored."
    assert len(auxiliaryNoteInfo) == len(
      noteStatusHistory
    ), "noteStatusHistory should be complete, and all notes should be scored."

  # Merge scoring results into noteStatusHistory, check flip rates, and set rescoringActiveRules.
  with c.time_block("Post-scorers: Update note status history"):
    mergedNoteStatuses = note_status_history.merge_old_and_new_note_statuses(
      noteStatusHistory, scoredNotes
    )
    # Not needed anymore, has been merged into note_status_history.
    scoredNotes = scoredNotes.drop(columns=[c.updatedTimestampMillisOfNmrDueToMinStableCrhTimeKey])

    scoredNotes[c.rescoringActiveRulesKey] = ""
    failedCheckFlips = False
    failureDescription = ""
    for noteSubset in noteSubsetsAndMaxFlipRates:
      if checkFlips:
        failed, description = note_status_history.check_flips(
          mergedNoteStatuses, noteSubset=noteSubset
        )
        failedCheckFlips = failedCheckFlips or failed
        failureDescription = failureDescription + "\n" + description
      if noteSubset.noteSet is not None:
        noteInSetMask = scoredNotes[c.noteIdKey].isin(noteSubset.noteSet)
      else:
        noteInSetMask = scoredNotes[c.noteIdKey].notnull()  # All notes by default.
      scoredNotes.loc[noteInSetMask, c.rescoringActiveRulesKey] = scoredNotes.loc[
        noteInSetMask, c.rescoringActiveRulesKey
      ].apply(
        lambda rescoringActiveRules: rescoringActiveRules + noteSubset.description.name
        if len(rescoringActiveRules) == 0
        else f"{rescoringActiveRules},{noteSubset.description.name}"
      )
    assert failedCheckFlips == False, f"Failed at least one flip check: {failureDescription}"
    newNoteStatusHistory = note_status_history.update_note_status_history(mergedNoteStatuses)
    assert len(newNoteStatusHistory) == len(
      noteStatusHistory
    ), "noteStatusHistory should contain all notes after preprocessing"

  # Skip validation and selection out output columns if the set of scorers is overridden.
  with c.time_block("Post-scorers: finalize output columns"):
    scoredNotes[c.timestampMillisOfNmrDueToMinStableCrhTimeKey] = newNoteStatusHistory[
      c.timestampMillisOfNmrDueToMinStableCrhTimeKey
    ]
    scoredNotes = _add_deprecated_columns(scoredNotes)
    scoredNotes = scoredNotes.drop(columns=PFLIP_LABEL)
    if strictColumns:
      (
        scoredNotes,
        newNoteStatusHistory,
        auxiliaryNoteInfo,
      ) = _validate_note_scoring_output(scoredNotes, newNoteStatusHistory, auxiliaryNoteInfo)

  logger.info(
    f"Meta scoring elapsed time: {((time.time() - postScoringStartTime)/60.0):.2f} minutes."
  )
  return scoredNotes, newNoteStatusHistory, auxiliaryNoteInfo

  Apply individual scoring models and obtained merged result.
  
sourcecode/scoring/run_scoring.py