feat: vested/upcoming RSU grouping with vest-date valuation#204
Merged
Conversation
…stings
GET /api/stock-price/{symbol} accepts optional on_date and returns the
close on that date (nearest prior trading day within a week for
weekends/holidays) plus an as_of field. RsuVesting schema gains optional
price_at_vest so vested income can be locked at the vest-date price.
Vesting tables split into Vested (date <= today, dimmed) and Upcoming sections, sorted chronologically; rows re-sort on date-input blur and pre-existing grants are normalized on load. Summary line splits into vested vs upcoming shares/value. New lib/rsuVesting.ts is the single source of truth for isVested / sortVestings / vestingPrice / splitRsuTotals, shared by the settings UI, projectionCalculator (vested rows use locked vest-date price, never appreciated) and tdsScheduleCalculator. useRsuGrants hook extracts grant state handling from SalaryStructureSection and auto-fetches the historical close once when a vesting passes, storing price_at_vest; editing a vested date clears the lock so it re-fetches.
CHANGELOG gains 2.22.0 (this work), 2.21.0 (PR #203, was missing) and renames the stale Unreleased heading to 2.20.0 (PR #202, merged 2026-07-04). Versions aligned across root/frontend package.json, backend pyproject + uv.lock, and APP_VERSION (health endpoint reported a hardcoded 1.0.0). HANDBOOK RSU section updated for vested/upcoming grouping.
pytest.approx for float assertions, single throwing call in the exception test, Annotated Query param, and extract applyVestPrices to cut function nesting below 4 levels.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



What
RSU vesting schedules in Settings were rendered in raw insertion order (a 2025 vesting added last sat below 2028 rows) and every vesting -- past or future -- was valued at the grant's current stock price. This PR fixes both.
price_at_vest, so realized RSU income stops drifting with the current price. Falls back to current price when the lookup fails; editing a vested row's date clears the lock so it re-fetches.GET /api/stock-price/{symbol}?on_date=YYYY-MM-DDreturns the close on that date (nearest prior trading day within a week covers weekends/market holidays) plus anas_offield.RsuVestingschema gains optionalprice_at_vest(Decimal, > 0).getRsuVestingsByFYtreats vested rows as realized income -- locked vest-date price, no stock-appreciation applied. Upcoming vestings keep the projection behavior. Same for the TDS schedule'srsuExtrasByFyMonth.lib/rsuVesting.tsis the single source of truth forisVested/sortVestings/vestingPrice/splitRsuTotals, shared by settings UI, tax projection, and TDS schedule. Grant state handlers extracted into auseRsuGrantshook; vesting table intoVestingTable.tsx.APP_VERSION(the health endpoint was reporting a hardcoded 1.0.0).Testing
price_at_vestschema.rsuVesting.test.tssuite (vested/sort/price/split-totals) and projection tests pinned to a fixed "today" proving vested rows never get appreciation.pnpm run type-check,eslint,ruff check,ruff format --check,mypy, and production build all clean.