{"record":{"id":"e36bdd7282e2fa99","repo":"HKUDS/Vibe-Trading","slug":"tier-self-name-r-does-not-conserve-cash-lp-sel","errorCode":null,"errorMessage":"tier {self.name!r} does not conserve cash: lp {self.lp_amount!r} + gp {self.gp_amount!r} != {self.amount!r}","messagePattern":"tier (.+?) does not conserve cash: lp (.+?) \\+ gp (.+?) != (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agent/src/quantlib/fundmath.py","lineNumber":1035,"sourceCode":"                if any figure is negative. A waterfall that does not conserve\n                cash is a bug, never a rounding artefact worth tolerating.\n        \"\"\"\n        for label, value in (\n            (\"amount\", self.amount),\n            (\"lp_amount\", self.lp_amount),\n            (\"gp_amount\", self.gp_amount),\n        ):\n            if value < -_ALLOCATION_TOLERANCE:\n                raise ValueError(\n                    f\"tier {self.name!r} has a negative {label} of {value!r}\"\n                )\n        if not math.isclose(\n            self.lp_amount + self.gp_amount,\n            self.amount,\n            rel_tol=_ALLOCATION_TOLERANCE,\n            abs_tol=_ALLOCATION_TOLERANCE,\n        ):\n            raise ValueError(\n                f\"tier {self.name!r} does not conserve cash: lp {self.lp_amount!r} \"\n                f\"+ gp {self.gp_amount!r} != {self.amount!r}\"\n            )\n\n\n@dataclass(frozen=True)\nclass WaterfallResult:\n    \"\"\"Outcome of a European whole-of-fund distribution waterfall.\n\n    Attributes:\n        distributable: Cash entering the waterfall.\n        contributed_capital: LP capital that must be returned first.\n        preferred_amount: Preferred return owed before the GP earns anything.\n        tiers: The tiers in payment order.\n        lp_total: Everything allocated to the limited partners.\n        gp_total: Everything allocated to the general partner.\n        unreturned_capital: Contributed capital still outstanding, positive when\n            the distributable amount did not cover the return of capital.","sourceCodeStart":1017,"sourceCodeEnd":1053,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/quantlib/fundmath.py#L1017-L1053","documentation":"A waterfall tier must conserve cash: lp_amount + gp_amount must equal amount within _ALLOCATION_TOLERANCE. This invariant catches split computations that leak or create dollars between the LP and GP sleeves.","triggerScenarios":"Constructing a tier with amount=100.0, lp_amount=80.0, gp_amount=15.0 (sums to 95, not 100), typically from independent rounding of lp/gp shares.","commonSituations":"Rounding each sleeve to cents separately; carry split computed from a rate applied to a different base than amount; assembling tiers from separate data sources.","solutions":["Derive one sleeve from the other: gp_amount = round(amount - lp_amount, 2)","Round amount and both sleeves consistently to the same precision","Recompute the tier from a single split rate (gp_amount = amount * carry_rate)"],"exampleFix":"# before\ntier = Tier(name=\"carry\", amount=100.0, lp_amount=80.0, gp_amount=15.0)\n\n# after\namount = 100.0\nlp = round(amount * 0.8, 2)\ntier = Tier(name=\"carry\", amount=amount, lp_amount=lp, gp_amount=round(amount - lp, 2))","handlingStrategy":"validation","validationCode":"import math\nassert math.isclose(lp + gp, amount, rel_tol=1e-9, abs_tol=1e-9), (lp, gp, amount)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never round lp and gp independently; derive one from amount minus the other","Use one rounding precision throughout the tier pipeline"],"tags":["fund-math","waterfall","conservation-invariant","rounding"],"backgroundTag":"data-invariant-violation","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}