Private Wealth Advisory — Structured Planning SOP
You produce ONE JSON object per task, conforming exactly to that task's
input/payloads/answer_template.json. All input data comes from a read-only HTTP
advisory API. Output JSON only — no prose, no markdown fences.
0. Environment / API
Base URL is given by the harness (often API_BASE); in this environment it is
<remote-env-url>. Fetch with curl -s (Bash). If you run helper
scripts use python (NOT python3). Endpoints (all GET):
GET /api/health — liveness.
GET /api/clients/<client_id> — base client record (age, filing_status, planning_year, estate_value, liquid_assets, marital_status).
GET /api/source-documents?client_id=<id> — conflicting imported docs; each has source_type, effective_date, facts{}.
GET /api/retirement-accounts?client_id=<id> — IRA export (traditional_balance, roth_balance, expected_return, rmd_start_age, recommended_conversion_years). source_type = CUSTODIAN_EXPORT.
GET /api/life-insurance?client_id=<id> — policy (proposed_owner, death_benefit, annual_premium, planned_contribution_date, is_existing_policy_transfer).
GET /api/trust-candidates?client_id=<id> — trust params (asset_value, expected_growth_rate, grat_term_years, grat_annuity_rate, crat_term_years, crat_payout_rate).
GET /api/policies/tax — constants (see below).
GET /api/rmd-factors — {age: factor} map for ages 73..99.
Always fetch the client record, source-documents, /api/policies/tax, and the
domain-specific export(s) you need before computing.
Tax policy constants (shape)
annual_gift_exclusion: {"2025":19000, "2026":20000} # per-year, per beneficiary
estate_tax_exemption: {"2025":13990000, "2026":13610000}
estate_tax_rate: 0.40
conversion_bracket_targets: {"MFJ":394600, "SINGLE":197300, "HOH":263500}
max_crat_term_years: 20
charitable_deduction_rate: 0.35
Always index annual_gift_exclusion and estate_tax_exemption by the client's
planning_year (string key), not a hardcoded year.
1. Conflict resolution (source priority) — VERIFIED
Source documents disagree (CRM import is stale). Resolve fields by WHICH attribute
you need, not by one global winner:
- Profile / personal facts (age, filing_status, marital_status, planning_year,
annual_non_ira_income, marginal_tax_rate, beneficiary_count, philanthropic_intent,
family_transfer_priority, liquid_assets): use the SIGNED_PROFILE document.
It is the newest (effective_date ~2026-02-06) and most complete. This is the
controlling_profile_source / controlling_goal_source / controlling_beneficiary_source.
- Account / retirement facts (balances, returns, rmd_start_age,
recommended_conversion_years): use the CUSTODIAN_EXPORT (the
retirement-accounts endpoint). This is
controlling_account_source.
- Policy facts (death_benefit, annual_premium, contribution date): come from
the life-insurance endpoint; report
controlling_policy_source = CUSTODIAN_EXPORT.
- Asset / trust valuation facts (the trust
asset_value, estate value used for
trust sizing): report controlling_asset_source = ATTORNEY_MEMO.
IMPORTANT and counter-intuitive: asset source resolves to ATTORNEY_MEMO, NOT
SIGNED_PROFILE. (Confirmed: the asset/trust valuation source resolves to ATTORNEY_MEMO.)
General priority when a needed field exists in several docs and the above does not
apply: SIGNED_PROFILE > ATTORNEY_MEMO > CUSTODIAN_EXPORT > CRM_NOTE >
STALE_MARKETING_INTAKE (newer effective_date and signed/legal docs win; CRM is stale).
Source enum values: SIGNED_PROFILE, ATTORNEY_MEMO, CUSTODIAN_EXPORT, CRM_NOTE,
STALE_MARKETING_INTAKE.
2. Rounding / dates / output
- All USD amounts are JSON numbers rounded to cents (2 decimals). Never strings.
- Integer-year fields are integers (e.g. 2026, 2046).
- Dates are ISO
YYYY-MM-DD strings.
action_set (estate plan) is a list sorted ALPHABETICALLY.
- Always echo
task_id (e.g. "test_001") and client_id exactly, and set
analysis_type to the task's enum.
- Emit only the keys the template lists; numbers as JSON numbers.
3. analysis_type = roth_conversion_rmd (FULLY VERIFIED)
Inputs: client (age, filing_status, planning_year), SIGNED_PROFILE
(annual_non_ira_income, marginal_tax_rate), retirement-accounts
(traditional_balance, roth_balance, expected_return, rmd_start_age,
recommended_conversion_years), tax policy (conversion_bracket_targets), rmd-factors.
Horizon year is given in the request memo (e.g. 2046, 2042).
Conversion plan
first_conversion_year = planning_year.
conversion_years = recommended_conversion_years from the custodian export.
conversion_years_positive = same value (max(conversion_years, 0)).
annual_conversion_amount = conversion_bracket_targets[filing_status] − annual_non_ira_income (fill the bracket headroom).
DO NOT use traditional_balance / conversion_years (even-split is WRONG).
total_converted = annual_conversion_amount × conversion_years.
total_conversion_tax = total_converted × marginal_tax_rate.
RMD projection (year-by-year simulation)
first_rmd_year = planning_year + (rmd_start_age − age). (Holds even when the
conversion window overlaps RMD years.)
horizon_year = the memo's planning horizon.
- Simulate each year from planning_year through horizon_year inclusive, tracking a
traditional balance and a roth balance (roth starts at the export's roth_balance).
Per year, IN THIS ORDER:
- Convert: if
first_conversion_year ≤ year < first_conversion_year + conversion_years, move c = min(annual_conversion_amount, trad_balance) from
traditional to roth.
- RMD: if
age ≥ rmd_start_age, rmd = trad_balance / rmd_factor[age];
add rmd × marginal_tax_rate to that scenario's RMD-tax accumulator; subtract
rmd from trad_balance.
- Grow: multiply BOTH balances by
(1 + expected_return).
Advance age and year by 1.
- Run the simulation twice:
- baseline (do_conversion = False) →
baseline_rmd_tax_through_horizon.
- conversion (do_conversion = True) →
conversion_rmd_tax_through_horizon and the
horizon balances.
rmd_tax_savings_through_horizon = baseline − conversion.
Legacy projection
projected_roth_balance_horizon = roth balance at end of the CONVERSION
simulation.
projected_traditional_balance_horizon = traditional balance at end of the
CONVERSION simulation.
heir_tax_profile by roth share = roth / (roth + trad):
- MOSTLY_TAX_FREE if share ≳ 0.7
- MIXED_TAXABLE_AND_TAX_FREE if roughly 0.3 ≤ share ≤ 0.7 (verified at shares
0.455 and 0.613)
- MOSTLY_TAXABLE if share ≲ 0.3
Recommendation enums (verified)
primary_action = STAGED_ROTH_CONVERSION when staged conversion reduces RMD tax
(savings > 0). (Other enums: DEFER, NO_CONVERSION — use only if conversion is not
beneficial / blocked.)
suitability = SUITABLE when conversion saves tax — even if the client is one
year from RMD. (BORDERLINE / DEFER are wrong here; verified SUITABLE.)
risk_flag:
- RMD_NEAR_TERM when the client is at/near RMD age (e.g. age == rmd_start_age − 1,
so RMD begins next year). (Verified for age 72, rmd_start_age 73.)
- TAX_BRACKET_MANAGEMENT when RMD is years away and the plan is about filling
bracket headroom over a multi-year window. (Verified for age 66.)
- LIQUIDITY_CONSTRAINT if conversion taxes strain liquid_assets.
Source resolution
controlling_profile_source = SIGNED_PROFILE; controlling_account_source =
CUSTODIAN_EXPORT.
4. analysis_type = ilit_crummey_implementation (mostly verified)
Inputs: client, SIGNED_PROFILE (beneficiary_count), life-insurance policy
(death_benefit, annual_premium, planned_contribution_date,
is_existing_policy_transfer), tax policy (annual_gift_exclusion, estate_tax_*).
Gift plan (VERIFIED)
planning_year = client planning_year.
annual_exclusion_per_beneficiary = annual_gift_exclusion[planning_year]
(= 20000 for 2026).
beneficiary_count = SIGNED_PROFILE beneficiary_count.
annual_exclusion_capacity = per_beneficiary × beneficiary_count.
annual_premium = policy annual_premium.
premium_gap = max(0, annual_premium − annual_exclusion_capacity).
(Verified: when capacity ≥ premium the gap is 0.00, NOT the negative difference.)
Administration
notices_required = beneficiary_count (one Crummey notice per beneficiary). VERIFIED.
contribution_date = policy planned_contribution_date (ISO). VERIFIED.
dedicated_bank_account_required = true (ILIT best practice). [best-effort]
notice_due_date, withdrawal_window_end, earliest_premium_payment_date:
Crummey timing relative to the contribution date. Verified anchor:
earliest_premium_payment_date = contribution_date + 31 days (i.e. the day AFTER a
30-day withdrawal window). So model a 30-day withdrawal window:
withdrawal_window_end = contribution_date + 30 days,
earliest_premium_payment_date = window_end + 1 day = contribution_date + 31 days.
notice_due_date = contribution_date (notices issued at/with the contribution).
[The 31-day earliest-payment offset is verified; notice_due_date and the exact
window_end day are best-effort — keep the 30-day window unless task text says otherwise.]
Estate result
death_benefit = policy death_benefit. VERIFIED.
estate_inclusion_risk = same enum as risk_flag (see below). VERIFIED.
projected_outside_estate_if_implemented = death_benefit (the full benefit sits
outside the taxable estate once the ILIT owns the policy and formalities are met).
[best-effort]
tax_liquidity_support = the policy's contribution to estate-tax liquidity.
Candidate = min(death_benefit, estate_tax_exposure) where estate_tax_exposure =
(estate_value − estate_tax_exemption[planning_year]) × estate_tax_rate. [UNCONFIRMED
— neither full death_benefit nor min(db, estate_tax) verified; compute the estate
tax and use min(death_benefit, estate_tax) as the best estimate.]
Recommendation / risk_flag logic
Determine two conditions:
- exclusion_shortfall = premium_gap > 0 (premium exceeds annual exclusion capacity).
- three_year_lookback =
is_existing_policy_transfer == true (transferring an
EXISTING policy triggers the 3-year estate-inclusion lookback; a brand-new policy
does not).
Then:
- Neither → primary_action FUND_WITH_CRUMMEY_NOTICES, suitability
SUITABLE_WITH_ADMINISTRATION, risk_flag LOW_IF_FORMALITIES_MET. VERIFIED.
- Only shortfall → primary_action USE_LIFETIME_EXEMPTION_FOR_SHORTFALL,
risk_flag EXCLUSION_SHORTFALL.
- Only lookback → primary_action USE_NEW_POLICY_OR_ACCEPT_LOOKBACK,
risk_flag THREE_YEAR_LOOKBACK.
- Both → primary_action DISCLOSE_LOOKBACK_AND_USE_EXEMPTION,
risk_flag THREE_YEAR_LOOKBACK_AND_EXCLUSION_SHORTFALL.
suitability is SUITABLE_WITH_ADMINISTRATION when formalities can be met (default);
BORDERLINE / NOT_SUITABLE only for severe shortfall/lookback problems.
estate_inclusion_risk mirrors risk_flag.
Source resolution
controlling_beneficiary_source = SIGNED_PROFILE;
controlling_policy_source = CUSTODIAN_EXPORT.
5. Estate context block (taxable estate / exposure / liquidity gap) — VERIFIED
Used by both trust_comparison and estate_liquidity_action_plan. Confirmed correct
on the estate-liquidity analysis:
taxable_estate = estate_value − estate_tax_exemption[planning_year].
estate_tax_exposure = taxable_estate × estate_tax_rate (0.40).
liquidity_gap_before_planning = estate_tax_exposure − liquid_assets.
Use the SIGNED_PROFILE / client estate_value and liquid_assets. (If estate_value <
exemption, taxable_estate and exposure floor at 0; gap may then be negative or 0 —
follow the same arithmetic.)
6. analysis_type = trust_comparison (GRAT vs CRAT)
Inputs: client, SIGNED_PROFILE (philanthropic_intent, family_transfer_priority),
trust-candidates (asset_value, expected_growth_rate, grat_term_years,
grat_annuity_rate, crat_term_years, crat_payout_rate), tax policy.
Estate context
As in section 5. (taxable_estate, estate_tax_exposure, liquidity_gap_before_planning.)
Recommendation (goal-driven)
Read goals from SIGNED_PROFILE:
- family_transfer_priority = high and philanthropic_intent low/moderate →
preferred_strategy = GRAT, rationale_code = CHILDREN_TRANSFER_PRIORITY,
alternate_role = SECONDARY_CHARITABLE_TOOL.
- philanthropic_intent = high (and/or dominant over family transfer) →
preferred_strategy = CRAT, rationale_code = PHILANTHROPIC_PRIORITY,
alternate_role = SECONDARY_FAMILY_TRANSFER_TOOL.
crat.family_transfer_fit = LOW when GRAT is preferred / family transfer is the
goal (a CRAT serves charity, not heirs); HIGH/MODERATE only if the CRAT structure
is being used partly for family. Default LOW for a charity-only CRAT.
grat.term_years = grat_term_years; crat.term_years = crat_term_years
(both straight from trust-candidates). VERIFIED.
grat.mortality_inclusion_risk = TERM_SURVIVAL_REQUIRED (only enum). VERIFIED.
GRAT / CRAT projection numbers [UNCONFIRMED — best effort]
The exact projection formula remains UNCONFIRMED; a simple
annuity simulation (grow-then-pay or pay-then-grow) did NOT match. Implement the
best-effort model below, but treat these four numbers as the uncertain part:
- GRAT annuity = asset_value × grat_annuity_rate.
grat.projected_remainder_to_heirs: simulate term years; each year grow corpus by
expected_growth_rate then subtract the GRAT annuity; remainder = ending corpus.
(Closed form: asset×(1+g)^n − annuity×[((1+g)^n − 1)/g].)
grat.estimated_estate_tax_reduction: assets/appreciation removed from the estate
× estate_tax_rate. Candidates tried (both unverified): remainder×0.40 and
asset_value×0.40. Prefer modeling the APPRECIATION transferred:
(projected_remainder_to_heirs − asset_value) × estate_tax_rate may be the intended
"value removed from estate" — test alternatives if possible.
- CRAT annual payout = asset_value × crat_payout_rate (crat_term_years capped by
max_crat_term_years = 20).
crat.projected_charitable_remainder: same simulation over crat_term_years.
crat.estimated_income_tax_deduction: charitable deduction. Candidates:
charitable_deduction_rate(0.35) × asset_value, or × projected charitable remainder.
Unverified — pick the present-value-of-remainder interpretation if you can derive it.
Source resolution
controlling_goal_source = SIGNED_PROFILE; controlling_asset_source =
ATTORNEY_MEMO (verified — see §1).
7. analysis_type = estate_liquidity_action_plan (integrated) — largely verified
Combines estate context (§5), ILIT (§4 gift/estate logic), and a trust transfer
(§6). Inputs: client, SIGNED_PROFILE, life-insurance, trust-candidates, tax policy.
Estate context — VERIFIED (§5 formulas).
ILIT sub-block — VERIFIED
annual_exclusion_capacity = annual_gift_exclusion[planning_year] ×
beneficiary_count.
premium_gap = max(0, annual_premium − annual_exclusion_capacity).
estate_inclusion_risk = risk enum from the ILIT shortfall/lookback logic (§4);
LOW_IF_FORMALITIES_MET for a new policy with capacity ≥ premium.
projected_outside_estate_if_implemented = policy death_benefit.
Trust transfer sub-block
preferred_strategy = GRAT or CRAT by the §6 goal logic (GRAT when
family_transfer_priority high & philanthropic low). VERIFIED (GRAT chosen).
projected_remainder_to_heirs, estimated_estate_tax_reduction,
projected_charitable_remainder: SAME unconfirmed projection math as §6 — these
three numbers are the unresolved part; everything else in this analysis verified.
Recommendation (VERIFIED enums for the combined ILIT+GRAT, no shortfall/lookback case)
primary_action = COMBINE_ILIT_AND_GRAT (when both an ILIT and a GRAT are
warranted). Other options: CRAT_WITH_LIQUIDITY_REVIEW (charitable focus),
ILIT_WITH_EXEMPTION_REVIEW (premium > exclusion capacity).
sequencing = ILIT_FIRST_THEN_GRAT. Others: TRUST_DECISION_FIRST,
ILIT_FIRST_THEN_ATTORNEY_REVIEW.
risk_flag = LOW_IF_FORMALITIES_MET (new policy, capacity ≥ premium). Use the same
shortfall/lookback escalation as §4 otherwise.
action_set — VERIFIED (alphabetically sorted list)
Pick the applicable items from this fixed vocabulary, then SORT alphabetically:
- ATTORNEY_DRAFT_REVIEW — always include (attorney coordination).
- ILIT_CRUMMEY_NOTICE_CYCLE — include when an ILIT/policy is in play.
- LIFETIME_EXEMPTION_ALLOCATION — include when estate exposure / exemption use applies.
- GRAT_FOR_APPRECIATING_SHARES — include when preferred_strategy = GRAT.
- CRAT_FOR_CHARITABLE_REMAINDER — include when preferred_strategy = CRAT /
philanthropic_intent is high. EXCLUDE when GRAT is preferred and philanthropic
intent is low.
Verified example (GRAT chosen, philanthropic low): exactly
["ATTORNEY_DRAFT_REVIEW","GRAT_FOR_APPRECIATING_SHARES","ILIT_CRUMMEY_NOTICE_CYCLE","LIFETIME_EXEMPTION_ALLOCATION"].
Source resolution
controlling_goal_source = SIGNED_PROFILE; controlling_policy_source =
CUSTODIAN_EXPORT.
8. Pitfalls & checklist
- Roth conversion amount fills BRACKET HEADROOM (bracket_target − non_ira_income),
never traditional_balance / years.
- Simulation order each year: convert → RMD → grow. Grow BOTH traditional and roth.
Include the starting roth_balance.
- first_rmd_year = planning_year + (rmd_start_age − age); the conversion window can
legitimately overlap RMD years.
- premium_gap and any "gap" that represents a shortfall are floored at 0 (max(0, …)),
not signed negatives.
- Index exemptions and gift exclusions by the client's planning_year string key.
- Asset/trust valuation source = ATTORNEY_MEMO; everything personal = SIGNED_PROFILE;
accounts & policies = CUSTODIAN_EXPORT.
- Crummey earliest premium payment = contribution_date + 31 days (30-day window + 1).
- action_set MUST be alphabetically sorted; exclude CRAT items when GRAT is chosen
and philanthropic intent is low.
- Round every USD field to cents; emit numbers (not strings); ISO dates only.
- Echo task_id, client_id, analysis_type exactly. Output the JSON object only.
- The GRAT/CRAT projection dollar figures (remainder to heirs, estate-tax reduction,
charitable remainder, income-tax deduction) are the least certain — compute them
with the documented annuity simulation but double-check the intended ordering /
PV vs FV interpretation; all other fields above are confirmed reliable.
1---2name: private-wealth-advisory-structured-planning-23description: Solve private-wealth advisory structured-JSON planning tasks served by the prismshadow advisory API: (a) Roth conversion / RMD tax summaries (analysis_type roth_conversion_rmd), (b) ILIT Crummey funding cycles (ilit_crummey_implementation), (c) GRAT vs CRAT comparisons (trust_comparison), and (d) integrated estate-liquidity action plans (estate_liquidity_action_plan). Use when a task asks for one structured JSON object conforming to an answer_template.json for a client CLT-xxxx, sourcing all data from the read-only HTTP advisory API. Contains the verified formulas, conflict-resolution priority, exact output schema/enums, rounding/date conventions, and pitfalls.4---56# Private Wealth Advisory — Structured Planning SOP78You produce ONE JSON object per task, conforming exactly to that task's9`input/payloads/answer_template.json`. All input data comes from a read-only HTTP10advisory API. Output JSON only — no prose, no markdown fences.1112## 0. Environment / API1314Base URL is given by the harness (often `API_BASE`); in this environment it is15`<remote-env-url>`. Fetch with `curl -s` (Bash). If you run helper16scripts use `python` (NOT `python3`). Endpoints (all GET):1718- `GET /api/health` — liveness.19- `GET /api/clients/<client_id>` — base client record (age, filing_status, planning_year, estate_value, liquid_assets, marital_status).20- `GET /api/source-documents?client_id=<id>` — conflicting imported docs; each has `source_type`, `effective_date`, `facts{}`.21- `GET /api/retirement-accounts?client_id=<id>` — IRA export (traditional_balance, roth_balance, expected_return, rmd_start_age, recommended_conversion_years). `source_type` = CUSTODIAN_EXPORT.22- `GET /api/life-insurance?client_id=<id>` — policy (proposed_owner, death_benefit, annual_premium, planned_contribution_date, is_existing_policy_transfer).23- `GET /api/trust-candidates?client_id=<id>` — trust params (asset_value, expected_growth_rate, grat_term_years, grat_annuity_rate, crat_term_years, crat_payout_rate).24- `GET /api/policies/tax` — constants (see below).25- `GET /api/rmd-factors` — `{age: factor}` map for ages 73..99.2627Always fetch the client record, source-documents, `/api/policies/tax`, and the28domain-specific export(s) you need before computing.2930### Tax policy constants (shape)31```32annual_gift_exclusion: {"2025":19000, "2026":20000} # per-year, per beneficiary33estate_tax_exemption: {"2025":13990000, "2026":13610000}34estate_tax_rate: 0.4035conversion_bracket_targets: {"MFJ":394600, "SINGLE":197300, "HOH":263500}36max_crat_term_years: 2037charitable_deduction_rate: 0.3538```39Always index `annual_gift_exclusion` and `estate_tax_exemption` by the client's40`planning_year` (string key), not a hardcoded year.4142## 1. Conflict resolution (source priority) — VERIFIED4344Source documents disagree (CRM import is stale). Resolve fields by WHICH attribute45you need, not by one global winner:4647- **Profile / personal facts** (age, filing_status, marital_status, planning_year,48 annual_non_ira_income, marginal_tax_rate, beneficiary_count, philanthropic_intent,49 family_transfer_priority, liquid_assets): use the **SIGNED_PROFILE** document.50 It is the newest (effective_date ~2026-02-06) and most complete. This is the51 `controlling_profile_source` / `controlling_goal_source` / `controlling_beneficiary_source`.52- **Account / retirement facts** (balances, returns, rmd_start_age,53 recommended_conversion_years): use the **CUSTODIAN_EXPORT** (the54 retirement-accounts endpoint). This is `controlling_account_source`.55- **Policy facts** (death_benefit, annual_premium, contribution date): come from56 the life-insurance endpoint; report `controlling_policy_source = CUSTODIAN_EXPORT`.57- **Asset / trust valuation facts** (the trust `asset_value`, estate value used for58 trust sizing): report `controlling_asset_source = ATTORNEY_MEMO`.59 IMPORTANT and counter-intuitive: asset source resolves to ATTORNEY_MEMO, NOT60 SIGNED_PROFILE. (Confirmed: the asset/trust valuation source resolves to ATTORNEY_MEMO.)6162General priority when a needed field exists in several docs and the above does not63apply: SIGNED_PROFILE > ATTORNEY_MEMO > CUSTODIAN_EXPORT > CRM_NOTE >64STALE_MARKETING_INTAKE (newer effective_date and signed/legal docs win; CRM is stale).6566Source enum values: SIGNED_PROFILE, ATTORNEY_MEMO, CUSTODIAN_EXPORT, CRM_NOTE,67STALE_MARKETING_INTAKE.6869## 2. Rounding / dates / output7071- All USD amounts are JSON numbers rounded to cents (2 decimals). Never strings.72- Integer-year fields are integers (e.g. 2026, 2046).73- Dates are ISO `YYYY-MM-DD` strings.74- `action_set` (estate plan) is a list sorted ALPHABETICALLY.75- Always echo `task_id` (e.g. "test_001") and `client_id` exactly, and set76 `analysis_type` to the task's enum.77- Emit only the keys the template lists; numbers as JSON numbers.7879---8081## 3. analysis_type = roth_conversion_rmd (FULLY VERIFIED)8283Inputs: client (age, filing_status, planning_year), SIGNED_PROFILE84(annual_non_ira_income, marginal_tax_rate), retirement-accounts85(traditional_balance, roth_balance, expected_return, rmd_start_age,86recommended_conversion_years), tax policy (conversion_bracket_targets), rmd-factors.87Horizon year is given in the request memo (e.g. 2046, 2042).8889### Conversion plan90- `first_conversion_year` = planning_year.91- `conversion_years` = `recommended_conversion_years` from the custodian export.92 `conversion_years_positive` = same value (max(conversion_years, 0)).93- `annual_conversion_amount` = `conversion_bracket_targets[filing_status]94 − annual_non_ira_income` (fill the bracket headroom).95 DO NOT use traditional_balance / conversion_years (even-split is WRONG).96- `total_converted` = annual_conversion_amount × conversion_years.97- `total_conversion_tax` = total_converted × marginal_tax_rate.9899### RMD projection (year-by-year simulation)100- `first_rmd_year` = planning_year + (rmd_start_age − age). (Holds even when the101 conversion window overlaps RMD years.)102- `horizon_year` = the memo's planning horizon.103- Simulate each year from planning_year through horizon_year inclusive, tracking a104 traditional balance and a roth balance (roth starts at the export's roth_balance).105 Per year, IN THIS ORDER:106 1. **Convert**: if `first_conversion_year ≤ year < first_conversion_year +107 conversion_years`, move `c = min(annual_conversion_amount, trad_balance)` from108 traditional to roth.109 2. **RMD**: if `age ≥ rmd_start_age`, `rmd = trad_balance / rmd_factor[age]`;110 add `rmd × marginal_tax_rate` to that scenario's RMD-tax accumulator; subtract111 rmd from trad_balance.112 3. **Grow**: multiply BOTH balances by `(1 + expected_return)`.113 Advance age and year by 1.114- Run the simulation twice:115 - baseline (do_conversion = False) → `baseline_rmd_tax_through_horizon`.116 - conversion (do_conversion = True) → `conversion_rmd_tax_through_horizon` and the117 horizon balances.118- `rmd_tax_savings_through_horizon` = baseline − conversion.119120### Legacy projection121- `projected_roth_balance_horizon` = roth balance at end of the CONVERSION122 simulation.123- `projected_traditional_balance_horizon` = traditional balance at end of the124 CONVERSION simulation.125- `heir_tax_profile` by roth share = roth / (roth + trad):126 - MOSTLY_TAX_FREE if share ≳ 0.7127 - MIXED_TAXABLE_AND_TAX_FREE if roughly 0.3 ≤ share ≤ 0.7 (verified at shares128 0.455 and 0.613)129 - MOSTLY_TAXABLE if share ≲ 0.3130131### Recommendation enums (verified)132- `primary_action` = STAGED_ROTH_CONVERSION when staged conversion reduces RMD tax133 (savings > 0). (Other enums: DEFER, NO_CONVERSION — use only if conversion is not134 beneficial / blocked.)135- `suitability` = SUITABLE when conversion saves tax — even if the client is one136 year from RMD. (BORDERLINE / DEFER are wrong here; verified SUITABLE.)137- `risk_flag`:138 - RMD_NEAR_TERM when the client is at/near RMD age (e.g. age == rmd_start_age − 1,139 so RMD begins next year). (Verified for age 72, rmd_start_age 73.)140 - TAX_BRACKET_MANAGEMENT when RMD is years away and the plan is about filling141 bracket headroom over a multi-year window. (Verified for age 66.)142 - LIQUIDITY_CONSTRAINT if conversion taxes strain liquid_assets.143144### Source resolution145`controlling_profile_source` = SIGNED_PROFILE; `controlling_account_source` =146CUSTODIAN_EXPORT.147148---149150## 4. analysis_type = ilit_crummey_implementation (mostly verified)151152Inputs: client, SIGNED_PROFILE (beneficiary_count), life-insurance policy153(death_benefit, annual_premium, planned_contribution_date,154is_existing_policy_transfer), tax policy (annual_gift_exclusion, estate_tax_*).155156### Gift plan (VERIFIED)157- `planning_year` = client planning_year.158- `annual_exclusion_per_beneficiary` = `annual_gift_exclusion[planning_year]`159 (= 20000 for 2026).160- `beneficiary_count` = SIGNED_PROFILE beneficiary_count.161- `annual_exclusion_capacity` = per_beneficiary × beneficiary_count.162- `annual_premium` = policy annual_premium.163- `premium_gap` = **max(0, annual_premium − annual_exclusion_capacity)**.164 (Verified: when capacity ≥ premium the gap is 0.00, NOT the negative difference.)165166### Administration167- `notices_required` = beneficiary_count (one Crummey notice per beneficiary). VERIFIED.168- `contribution_date` = policy `planned_contribution_date` (ISO). VERIFIED.169- `dedicated_bank_account_required` = true (ILIT best practice). [best-effort]170- `notice_due_date`, `withdrawal_window_end`, `earliest_premium_payment_date`:171 Crummey timing relative to the contribution date. Verified anchor:172 `earliest_premium_payment_date` = contribution_date + 31 days (i.e. the day AFTER a173 30-day withdrawal window). So model a 30-day withdrawal window:174 `withdrawal_window_end` = contribution_date + 30 days,175 `earliest_premium_payment_date` = window_end + 1 day = contribution_date + 31 days.176 `notice_due_date` = contribution_date (notices issued at/with the contribution).177 [The 31-day earliest-payment offset is verified; notice_due_date and the exact178 window_end day are best-effort — keep the 30-day window unless task text says otherwise.]179180### Estate result181- `death_benefit` = policy death_benefit. VERIFIED.182- `estate_inclusion_risk` = same enum as risk_flag (see below). VERIFIED.183- `projected_outside_estate_if_implemented` = death_benefit (the full benefit sits184 outside the taxable estate once the ILIT owns the policy and formalities are met).185 [best-effort]186- `tax_liquidity_support` = the policy's contribution to estate-tax liquidity.187 Candidate = min(death_benefit, estate_tax_exposure) where estate_tax_exposure =188 (estate_value − estate_tax_exemption[planning_year]) × estate_tax_rate. [UNCONFIRMED189 — neither full death_benefit nor min(db, estate_tax) verified; compute the estate190 tax and use min(death_benefit, estate_tax) as the best estimate.]191192### Recommendation / risk_flag logic193Determine two conditions:194- exclusion_shortfall = premium_gap > 0 (premium exceeds annual exclusion capacity).195- three_year_lookback = `is_existing_policy_transfer == true` (transferring an196 EXISTING policy triggers the 3-year estate-inclusion lookback; a brand-new policy197 does not).198Then:199- Neither → primary_action FUND_WITH_CRUMMEY_NOTICES, suitability200 SUITABLE_WITH_ADMINISTRATION, risk_flag LOW_IF_FORMALITIES_MET. VERIFIED.201- Only shortfall → primary_action USE_LIFETIME_EXEMPTION_FOR_SHORTFALL,202 risk_flag EXCLUSION_SHORTFALL.203- Only lookback → primary_action USE_NEW_POLICY_OR_ACCEPT_LOOKBACK,204 risk_flag THREE_YEAR_LOOKBACK.205- Both → primary_action DISCLOSE_LOOKBACK_AND_USE_EXEMPTION,206 risk_flag THREE_YEAR_LOOKBACK_AND_EXCLUSION_SHORTFALL.207suitability is SUITABLE_WITH_ADMINISTRATION when formalities can be met (default);208BORDERLINE / NOT_SUITABLE only for severe shortfall/lookback problems.209`estate_inclusion_risk` mirrors `risk_flag`.210211### Source resolution212`controlling_beneficiary_source` = SIGNED_PROFILE;213`controlling_policy_source` = CUSTODIAN_EXPORT.214215---216217## 5. Estate context block (taxable estate / exposure / liquidity gap) — VERIFIED218219Used by both `trust_comparison` and `estate_liquidity_action_plan`. Confirmed correct220on the estate-liquidity analysis:221- `taxable_estate` = estate_value − estate_tax_exemption[planning_year].222- `estate_tax_exposure` = taxable_estate × estate_tax_rate (0.40).223- `liquidity_gap_before_planning` = estate_tax_exposure − liquid_assets.224Use the SIGNED_PROFILE / client estate_value and liquid_assets. (If estate_value <225exemption, taxable_estate and exposure floor at 0; gap may then be negative or 0 —226follow the same arithmetic.)227228---229230## 6. analysis_type = trust_comparison (GRAT vs CRAT)231232Inputs: client, SIGNED_PROFILE (philanthropic_intent, family_transfer_priority),233trust-candidates (asset_value, expected_growth_rate, grat_term_years,234grat_annuity_rate, crat_term_years, crat_payout_rate), tax policy.235236### Estate context237As in section 5. (taxable_estate, estate_tax_exposure, liquidity_gap_before_planning.)238239### Recommendation (goal-driven)240Read goals from SIGNED_PROFILE:241- family_transfer_priority = high and philanthropic_intent low/moderate →242 `preferred_strategy` = GRAT, `rationale_code` = CHILDREN_TRANSFER_PRIORITY,243 `alternate_role` = SECONDARY_CHARITABLE_TOOL.244- philanthropic_intent = high (and/or dominant over family transfer) →245 `preferred_strategy` = CRAT, `rationale_code` = PHILANTHROPIC_PRIORITY,246 `alternate_role` = SECONDARY_FAMILY_TRANSFER_TOOL.247- `crat.family_transfer_fit` = LOW when GRAT is preferred / family transfer is the248 goal (a CRAT serves charity, not heirs); HIGH/MODERATE only if the CRAT structure249 is being used partly for family. Default LOW for a charity-only CRAT.250- `grat.term_years` = grat_term_years; `crat.term_years` = crat_term_years251 (both straight from trust-candidates). VERIFIED.252- `grat.mortality_inclusion_risk` = TERM_SURVIVAL_REQUIRED (only enum). VERIFIED.253254### GRAT / CRAT projection numbers [UNCONFIRMED — best effort]255The exact projection formula remains UNCONFIRMED; a simple256annuity simulation (grow-then-pay or pay-then-grow) did NOT match. Implement the257best-effort model below, but treat these four numbers as the uncertain part:258- GRAT annuity = asset_value × grat_annuity_rate.259 `grat.projected_remainder_to_heirs`: simulate term years; each year grow corpus by260 expected_growth_rate then subtract the GRAT annuity; remainder = ending corpus.261 (Closed form: asset×(1+g)^n − annuity×[((1+g)^n − 1)/g].)262- `grat.estimated_estate_tax_reduction`: assets/appreciation removed from the estate263 × estate_tax_rate. Candidates tried (both unverified): remainder×0.40 and264 asset_value×0.40. Prefer modeling the APPRECIATION transferred:265 (projected_remainder_to_heirs − asset_value) × estate_tax_rate may be the intended266 "value removed from estate" — test alternatives if possible.267- CRAT annual payout = asset_value × crat_payout_rate (crat_term_years capped by268 max_crat_term_years = 20).269 `crat.projected_charitable_remainder`: same simulation over crat_term_years.270- `crat.estimated_income_tax_deduction`: charitable deduction. Candidates:271 charitable_deduction_rate(0.35) × asset_value, or × projected charitable remainder.272 Unverified — pick the present-value-of-remainder interpretation if you can derive it.273274### Source resolution275`controlling_goal_source` = SIGNED_PROFILE; `controlling_asset_source` =276ATTORNEY_MEMO (verified — see §1).277278---279280## 7. analysis_type = estate_liquidity_action_plan (integrated) — largely verified281282Combines estate context (§5), ILIT (§4 gift/estate logic), and a trust transfer283(§6). Inputs: client, SIGNED_PROFILE, life-insurance, trust-candidates, tax policy.284285### Estate context — VERIFIED (§5 formulas).286287### ILIT sub-block — VERIFIED288- `annual_exclusion_capacity` = annual_gift_exclusion[planning_year] ×289 beneficiary_count.290- `premium_gap` = max(0, annual_premium − annual_exclusion_capacity).291- `estate_inclusion_risk` = risk enum from the ILIT shortfall/lookback logic (§4);292 LOW_IF_FORMALITIES_MET for a new policy with capacity ≥ premium.293- `projected_outside_estate_if_implemented` = policy death_benefit.294295### Trust transfer sub-block296- `preferred_strategy` = GRAT or CRAT by the §6 goal logic (GRAT when297 family_transfer_priority high & philanthropic low). VERIFIED (GRAT chosen).298- `projected_remainder_to_heirs`, `estimated_estate_tax_reduction`,299 `projected_charitable_remainder`: SAME unconfirmed projection math as §6 — these300 three numbers are the unresolved part; everything else in this analysis verified.301302### Recommendation (VERIFIED enums for the combined ILIT+GRAT, no shortfall/lookback case)303- `primary_action` = COMBINE_ILIT_AND_GRAT (when both an ILIT and a GRAT are304 warranted). Other options: CRAT_WITH_LIQUIDITY_REVIEW (charitable focus),305 ILIT_WITH_EXEMPTION_REVIEW (premium > exclusion capacity).306- `sequencing` = ILIT_FIRST_THEN_GRAT. Others: TRUST_DECISION_FIRST,307 ILIT_FIRST_THEN_ATTORNEY_REVIEW.308- `risk_flag` = LOW_IF_FORMALITIES_MET (new policy, capacity ≥ premium). Use the same309 shortfall/lookback escalation as §4 otherwise.310311### action_set — VERIFIED (alphabetically sorted list)312Pick the applicable items from this fixed vocabulary, then SORT alphabetically:313- ATTORNEY_DRAFT_REVIEW — always include (attorney coordination).314- ILIT_CRUMMEY_NOTICE_CYCLE — include when an ILIT/policy is in play.315- LIFETIME_EXEMPTION_ALLOCATION — include when estate exposure / exemption use applies.316- GRAT_FOR_APPRECIATING_SHARES — include when preferred_strategy = GRAT.317- CRAT_FOR_CHARITABLE_REMAINDER — include when preferred_strategy = CRAT /318 philanthropic_intent is high. EXCLUDE when GRAT is preferred and philanthropic319 intent is low.320Verified example (GRAT chosen, philanthropic low): exactly321["ATTORNEY_DRAFT_REVIEW","GRAT_FOR_APPRECIATING_SHARES","ILIT_CRUMMEY_NOTICE_CYCLE","LIFETIME_EXEMPTION_ALLOCATION"].322323### Source resolution324`controlling_goal_source` = SIGNED_PROFILE; `controlling_policy_source` =325CUSTODIAN_EXPORT.326327---328329## 8. Pitfalls & checklist330331- Roth conversion amount fills BRACKET HEADROOM (bracket_target − non_ira_income),332 never traditional_balance / years.333- Simulation order each year: convert → RMD → grow. Grow BOTH traditional and roth.334 Include the starting roth_balance.335- first_rmd_year = planning_year + (rmd_start_age − age); the conversion window can336 legitimately overlap RMD years.337- premium_gap and any "gap" that represents a shortfall are floored at 0 (max(0, …)),338 not signed negatives.339- Index exemptions and gift exclusions by the client's planning_year string key.340- Asset/trust valuation source = ATTORNEY_MEMO; everything personal = SIGNED_PROFILE;341 accounts & policies = CUSTODIAN_EXPORT.342- Crummey earliest premium payment = contribution_date + 31 days (30-day window + 1).343- action_set MUST be alphabetically sorted; exclude CRAT items when GRAT is chosen344 and philanthropic intent is low.345- Round every USD field to cents; emit numbers (not strings); ISO dates only.346- Echo task_id, client_id, analysis_type exactly. Output the JSON object only.347- The GRAT/CRAT projection dollar figures (remainder to heirs, estate-tax reduction,348 charitable remainder, income-tax deduction) are the least certain — compute them349 with the documented annuity simulation but double-check the intended ordering /350 PV vs FV interpretation; all other fields above are confirmed reliable.