{"record":{"id":"ab6a445c856d1cab","repo":"HKUDS/Vibe-Trading","slug":"fcffyear-year-must-be-1-got-self-year-r","errorCode":null,"errorMessage":"FCFFYear.year must be >= 1, got {self.year!r}","messagePattern":"FCFFYear\\.year must be >= 1, got (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agent/src/quantlib/valuation/dcf.py","lineNumber":557,"sourceCode":"\n    year: int\n    ebit: float\n    tax_rate: float\n    nopat: float\n    depreciation_amortization: float\n    capex: float\n    delta_nwc: float\n    fcff: float\n\n    def __post_init__(self) -> None:\n        \"\"\"Re-check the NOPAT and FCFF arithmetic.\n\n        Raises:\n            ValueError: If ``nopat`` or ``fcff`` do not match their formulas,\n                or ``year`` is not a positive integer.\n        \"\"\"\n        if self.year < 1:\n            raise ValueError(f\"FCFFYear.year must be >= 1, got {self.year!r}\")\n        expected_nopat = self.ebit * (1.0 - self.tax_rate)\n        if not math.isclose(\n            self.nopat, expected_nopat, rel_tol=_RECONCILIATION_TOLERANCE, abs_tol=1e-9\n        ):\n            raise ValueError(\n                f\"FCFFYear(year={self.year}).nopat={self.nopat!r} does not match \"\n                f\"ebit * (1 - tax_rate) = {expected_nopat!r}\"\n            )\n        expected_fcff = (\n            self.nopat + self.depreciation_amortization - self.capex - self.delta_nwc\n        )\n        if not math.isclose(\n            self.fcff, expected_fcff, rel_tol=_RECONCILIATION_TOLERANCE, abs_tol=1e-9\n        ):\n            raise ValueError(\n                f\"FCFFYear(year={self.year}).fcff={self.fcff!r} does not match the \"\n                f\"bridge total {expected_fcff!r}\"\n            )","sourceCodeStart":539,"sourceCodeEnd":575,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/quantlib/valuation/dcf.py#L539-L575","documentation":"FCFFYear.__post_init__ validates that year is a positive integer index (>= 1) for projection years. Year 0 or negative indices are rejected because discounting conventions assume years 1..N.","triggerScenarios":"Constructing FCFFYear(year=0, ...) or FCFFYear(year=-1, ...), typically from a 0-based loop index passed directly as year.","commonSituations":"enumerate() over forecasts starting at 0 and passing index as year; off-by-one when converting calendar years to relative projection years.","solutions":["Use enumerate(forecasts, start=1) so year starts at 1","Map calendar years to relative offsets (2026 -> year 1) explicitly","Prefer building FCFFYear via fcff_bridge(), which numbers years correctly"],"exampleFix":"# before\nfor i, e in enumerate(ebit):\n    FCFFYear(year=i, ...)\n\n# after\nfor i, e in enumerate(ebit, start=1):\n    FCFFYear(year=i, ...)","handlingStrategy":"validation","validationCode":"for i, row in enumerate(forecasts, start=1):\n    FCFFYear(year=i, ...)","typeGuard":"def is_projection_year(y) -> TypeGuard[int]:\n    return isinstance(y, int) and y >= 1","tryCatchPattern":"try:\n    FCFFYear(...)\nexcept ValueError as e:\n    if 'year' in str(e):\n        raise ValueError('projection years must be 1-based') from e\n    raise","preventionTips":["Use enumerate(..., start=1)","Prefer fcff_bridge() over manual FCFFYear construction","Convert calendar years to relative offsets explicitly"],"tags":["valuation","fcff","off-by-one","dataclass-invariants"],"backgroundTag":"off-by-one-index","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}