This is the code for investment_advice.py:
from typing import Literal

from mellea import MelleaSession, generative, start_session


@generative
def analyze_client_profile(profile: str) -> dict:
    """Extract risk_tolerance, time_horizon, and liquidity_needs from the profile."""


@generative
def should_have_equity_investments(client_analysis: dict) -> bool:
    """Determine if the client should have equity investments based on their risk_tolerance."""


@generative
def generate_advice_letter(profile: str) -> str:
    """Generate a personalized investment advice letter based on the client profile."""


@generative
def list_mentioned_products(text: str) -> list[str]:
    """Return a list of financial products mentioned in the text (e.g. ETFs, bonds, options)."""


@generative
def detect_prohibited_language(text: str) -> Literal["clean", "prohibited"]:
    """Detect whether the letter contains prohibited phrases like 'guaranteed returns' or 'no risk'."""


def check_semantic_preconditions(client_analysis: dict) -> None:
    required_fields = ["risk_tolerance", "time_horizon", "liquidity_needs"]
    missing = [
        f for f in required_fields if f not in client_analysis or not client_analysis[f]
    ]
    if missing:
        raise ValueError(f"Missing client data: {', '.join(missing)}")


def check_semantic_postconditions(
    m: MelleaSession,
    letter: str,
    client_analysis: dict,
    products: list[str],
    lang_flag: str,
) -> None:
    if should_have_equity_investments(m, client_analysis=client_analysis):
        if (
            "ETF" not in [p.upper() for p in products]
            and "index fund" not in [p.lower() for p in products]
            and "mutual fund" not in [p.lower() for p in products]
        ):
            raise ValueError(
                "Letter must recommend at least one diversified investment vehicle (e.g., ETF, mutual fund, or index fund)."
            )
    if client_analysis.get("risk_tolerance") == "low":
        if any(
            p.lower() in ("options", "futures", "swaps", "derivatives")
            for p in products
        ):
            raise ValueError(
                "Client has low risk tolerance — advice must not include derivatives."
            )
    if lang_flag == "prohibited":
        raise ValueError("Letter contains prohibited compliance language.")


def render_financial_advice(m: MelleaSession, profile: str) -> str:
    # Step 1: Analyze profile
    analysis = analyze_client_profile(m, profile=profile)
    check_semantic_preconditions(analysis)

    # Step 2: Generate advice
    letter = generate_advice_letter(m, profile=profile)

    # Step 3: Run postcondition validations
    products = list_mentioned_products(m, text=letter)
    lang_flag = detect_prohibited_language(m, text=letter)
    check_semantic_postconditions(m, letter, analysis, products, lang_flag)

    return letter


if __name__ == "__main__":
    m = start_session()
    profile_text = (
        "Client is a 62-year-old retiree with a conservative outlook.\n"
        "They need access to funds within 3-5 years and are concerned about volatility.\n"
        "Liquidity is important, and they've asked for low-risk options."
    )

    try:
        print(render_financial_advice(m, profile_text))
    except ValueError as e:
        print("🚫 Validation failed:", e)