This guide is part of the Indirect Cost Rate Automation section, and it solves one narrow problem: when a cognizant agency has fixed your indirect cost rate, base, and effective periods in a signed agreement, how do you apply exactly the right rate to each cost — even when a multi-year award crosses two or three rate periods — and reject any cost that falls outside every covered segment?
A Negotiated Indirect Cost Rate Agreement (NICRA) is not a single number. It is a schedule of rate segments, each with an effective start and end date, a rate type (provisional, predetermined, fixed, or final), and a named base definition against which the rate is charged. The common mistake is to store one float — 0.34 — and multiply every direct cost by it. That over- or under-recovers indirect the moment an award spans a rate boundary, applies the rate to the wrong base, and leaves nothing to reconcile when a provisional rate is later finalized. This guide models the agreement faithfully, selects the segment by the cost’s incurred date, applies the rate to the Modified Total Direct Cost base, and handles the provisional-to-final true-up.
When to Use This Approach
Reach for this segment-aware applier when all three conditions hold:
- The organization has a signed NICRA, not the de minimis rate. A NICRA is negotiated with, and issued by, the entity’s cognizant agency for indirect costs under 2 CFR §200.414. If instead the entity is electing the flat 10% rate available to those without a negotiated agreement, that is a different calculation with no rate segments and no true-up — it lives in Automating the 10% De Minimis Indirect Cost Rate.
- The award spans more than one rate period. A single-year charge against a single provisional rate does not need this machinery. The moment a multi-year award has costs that straddle a rate boundary — say a September and an October charge on either side of a fiscal-year turn — a scalar rate is wrong and date-based segment selection is mandatory.
- The base is defined, and it excludes items. The agreement names a base, most often Modified Total Direct Cost, which excludes equipment, capital expenditures, rent, participant support, and the portion of each subaward beyond the first $50,000. You must apply the negotiated rate to that computed base, not to raw total direct cost.
Computing the MTDC base itself — the exclusion rules and the subaward cap — is treated here as a reusable dependency rather than re-derived; this page focuses on segment modelling and rate selection. Rate negotiation, cost-pool allocation, and the assembly of the final indirect cost proposal are out of scope; how grantor-specific rate rules are encoded belongs to Grantor-Specific Rule Taxonomies.
Step-by-Step Implementation
The reference implementation targets Python 3.10+ and uses only the standard library — dataclasses, datetime, decimal, enum, and logging. Money is Decimal throughout; a rate applied to a budget in floating point drifts by cents that an auditor will find.
python >= 3.10 # standard library only — no third-party packages
Step 1: Model the agreement as dated rate segments
Represent each row of the NICRA as an immutable RateSegment: the effective window, the rate type, the rate as a Decimal, and the name of the base it charges against. A RateType enum makes the provisional-versus-final distinction explicit, because only provisional rates are subject to a later true-up.
import logging
from dataclasses import dataclass
from datetime import date
from decimal import Decimal
from enum import Enum
logger = logging.getLogger("nicra.apply")
class RateType(Enum):
PROVISIONAL = "provisional"
PREDETERMINED = "predetermined"
FIXED = "fixed"
FINAL = "final"
@dataclass(frozen=True)
class RateSegment:
"""One row of a Negotiated Indirect Cost Rate Agreement."""
effective_start: date
effective_end: date # inclusive last covered day
rate: Decimal
rate_type: RateType
base: str # e.g. "MTDC" — the applicable base definition
def covers(self, incurred: date) -> bool:
return self.effective_start <= incurred <= self.effective_end
The effective_end is treated as an inclusive last covered day, so a cost incurred on the segment’s final date still matches. Freezing the dataclass guarantees a segment cannot be mutated after the agreement is loaded — the schedule is a fact, not a working value.
Step 2: Select the correct segment by the cost’s incurred date
Selection is the heart of the applier. Scan the agreement for the single segment whose window contains the cost’s incurred date. Zero matches is an out-of-coverage rejection, not a fallback to the nearest rate; more than one match means the agreement itself has overlapping periods and is malformed, which must fail loudly at load time.
class CoverageError(Exception):
"""Raised when no rate segment covers a cost's incurred date."""
class AgreementError(Exception):
"""Raised when the agreement's segments overlap or are inconsistent."""
def select_segment(segments: list[RateSegment], incurred: date) -> RateSegment:
matches = [seg for seg in segments if seg.covers(incurred)]
if len(matches) > 1:
raise AgreementError(f"Overlapping segments cover {incurred.isoformat()}")
if not matches:
logger.warning("no_segment_covers date=%s", incurred.isoformat())
raise CoverageError(f"No negotiated rate covers {incurred.isoformat()}")
return matches[0]
Returning the matched segment rather than its bare rate keeps the base name and rate type attached, so the caller applies the right rate to the right base and knows whether a true-up will follow.
Step 3: Apply the segment’s rate to its specified base
Multiply the negotiated rate by the base amount the agreement names — here the pre-computed MTDC figure. The MTDC base itself, with its §200.1 exclusions and the $50,000 subaward cap, is produced by a reusable base computation; this function consumes that result and quantizes the product to cents.
from decimal import ROUND_HALF_UP
CENTS = Decimal("0.01")
def apply_rate(segment: RateSegment, mtdc_base: Decimal, incurred: date) -> Decimal:
if segment.base != "MTDC":
raise AgreementError(f"Unsupported base {segment.base!r} on segment")
if mtdc_base < 0:
raise ValueError(f"MTDC base cannot be negative: {mtdc_base}")
recovery = (segment.rate * mtdc_base).quantize(CENTS, rounding=ROUND_HALF_UP)
logger.info(
"indirect_applied date=%s type=%s rate=%s base=%s recovery=%s",
incurred.isoformat(), segment.rate_type.value, segment.rate, mtdc_base, recovery,
)
return recovery
Guarding on segment.base means a NICRA that names a base this code does not implement (salaries-and-wages, for instance) fails explicitly rather than silently applying the rate to an MTDC figure it was never meant for.
Step 4: True up a provisional rate to its final rate
A provisional rate is a billing estimate. When the cognizant agency later issues the final rate for that period, every provisional recovery must be recomputed at the final rate and the difference settled. Compute the delta per cost; a positive delta is additional recovery owed to the grantee, a negative delta is an amount to return.
@dataclass(frozen=True)
class TrueUp:
incurred: date
provisional_recovery: Decimal
final_recovery: Decimal
delta: Decimal # final minus provisional; positive = owed to grantee
def true_up(segment: RateSegment, final_rate: Decimal,
mtdc_base: Decimal, incurred: date) -> TrueUp:
if segment.rate_type is not RateType.PROVISIONAL:
raise AgreementError("Only provisional segments are subject to true-up")
provisional = (segment.rate * mtdc_base).quantize(CENTS, rounding=ROUND_HALF_UP)
final = (final_rate * mtdc_base).quantize(CENTS, rounding=ROUND_HALF_UP)
delta = final - provisional
logger.info("true_up date=%s delta=%s", incurred.isoformat(), delta)
return TrueUp(incurred, provisional, final, delta)
Only PROVISIONAL segments are eligible: predetermined, fixed, and final rates are not retroactively adjusted, so calling true_up on them raises rather than fabricating a phantom settlement.
Step 5: Reject costs that fall outside every segment
Wire the pieces into one entry point. A cost whose date no segment covers propagates the CoverageError — it is never charged at a default rate. This is the compliance-critical behaviour: unallowable indirect recovery on an uncovered date is a finding under 2 CFR §200.414.
def recover_indirect(segments: list[RateSegment],
incurred: date, mtdc_base: Decimal) -> Decimal:
segment = select_segment(segments, incurred) # raises CoverageError if uncovered
return apply_rate(segment, mtdc_base, incurred)
Because select_segment raises before apply_rate is reached, there is no code path that returns a recovery amount for an out-of-coverage cost — the rejection is structural, not a runtime check the caller might forget.
Verification
Confirm the applier behaves correctly with four checks:
- The incurred date selects the right segment. With three consecutive segments, a cost dated inside the middle window recovers at the middle rate, and a cost dated one day into the next window recovers at the next rate — proving the boundary is handled inclusively and selection is date-driven, not order-driven.
- An uncovered date is rejected. A cost dated after the last segment’s
effective_endraisesCoverageErrorand produces no recovery figure, satisfying the rule that no default rate is ever applied. - The true-up settles the delta. A provisional recovery re-run at a lower final rate yields a negative delta equal to
(final_rate - provisional_rate) * mtdc_base, quantized to cents. - A malformed agreement fails loudly. Two segments whose windows overlap raise
AgreementErroron selection rather than silently returning the first match.
segments = [
RateSegment(date(2023, 10, 1), date(2024, 9, 30), Decimal("0.34"),
RateType.PREDETERMINED, "MTDC"),
RateSegment(date(2024, 10, 1), date(2025, 9, 30), Decimal("0.36"),
RateType.PROVISIONAL, "MTDC"),
RateSegment(date(2025, 10, 1), date(2026, 9, 30), Decimal("0.35"),
RateType.FIXED, "MTDC"),
]
assert recover_indirect(segments, date(2025, 3, 14), Decimal("12000")) == Decimal("4320.00")
try:
recover_indirect(segments, date(2026, 11, 2), Decimal("5000"))
raise AssertionError("expected CoverageError")
except CoverageError:
pass
Common Errors & Fixes
| Error | Cause | Fix |
|---|---|---|
| Indirect over- or under-recovers on a multi-year award | One scalar rate applied across a rate boundary | Model each period as a RateSegment and call select_segment on the cost’s incurred date. |
CoverageError on a valid-looking cost |
Incurred date falls in a gap between segments, or before the first effective_start |
Confirm the agreement has no gaps for the award period; if the date is truly uncovered, the cost is unallowable indirect — do not default a rate. |
| Recovery is a few cents off the funder’s figure | Rate applied to a float budget, or rounding not fixed |
Use Decimal end to end and quantize(Decimal("0.01"), ROUND_HALF_UP). |
AgreementError: Overlapping segments |
Two rows in the NICRA share a covered day | Correct the boundary so one segment’s effective_end is the day before the next effective_start. |
| True-up produces a settlement on a fixed rate | true_up called on a non-provisional segment |
Gate on rate_type is RateType.PROVISIONAL; predetermined, fixed, and final rates are not adjusted. |
Related
- Parent section: Indirect Cost Rate Automation
- The flat-rate alternative for entities without a NICRA: Automating the 10% De Minimis Indirect Cost Rate
- Where computed indirect figures land in the 990 return: IRS 990 Data Schema Mapping