Source code for kfinance.domains.rounds_of_funding.rounds_of_funding_tools

from datetime import date
from textwrap import dedent
from typing import Literal, Type

import httpx
from pydantic import BaseModel, Field

from kfinance.async_batch_execution import AsyncTask, batch_execute_async_tasks
from kfinance.client.id_resolution import unified_fetch_id_triples
from kfinance.client.permission_models import Permission
from kfinance.domains.rounds_of_funding.rounds_of_funding_models import (
    AdvisorsResp,
    AdvisorTaskKey,
    FundingSummary,
    RoundOfFundingInfo,
    RoundOfFundingInfoWithAdvisors,
    RoundsOfFundingResp,
    RoundsOfFundingRole,
)
from kfinance.integrations.tool_calling.tool_calling_models import (
    KfinanceTool,
    ToolArgsWithIdentifiers,
    ToolRespWithErrors,
    ToolRespWithIdInfoAndErrors,
)


class GetRoundsofFundingFromIdentifiersArgs(ToolArgsWithIdentifiers):
    # no description because the description for enum fields comes from the enum docstring.
    role: RoundsOfFundingRole
    start_date: date | None = Field(
        default=None,
        description="Filter rounds to those closed on or after this date (YYYY-MM-DD format)",
    )
    end_date: date | None = Field(
        default=None,
        description="Filter rounds to those closed on or before this date (YYYY-MM-DD format)",
    )
    limit: int | None = Field(
        default=None, description="Limit to top N funding rounds by sort order"
    )
    sort_order: Literal["asc", "desc"] = Field(
        default="desc",
        description="Sort order for funding rounds by closed_date. 'desc' shows most recent first, 'asc' shows oldest first",
    )


class GetRoundsOfFundingFromIdentifiersResp(ToolRespWithIdInfoAndErrors[RoundsOfFundingResp]):
    pass


def filter_rounds_of_funding_responses_by_date_range(
    rounds_of_funding_responses: dict[str, RoundsOfFundingResp],
    start_date: date | None = None,
    end_date: date | None = None,
) -> dict[str, RoundsOfFundingResp]:
    """Filter rounds of funding responses by date range"""
    if start_date or end_date:
        filtered_responses = {}
        for identifier, response in rounds_of_funding_responses.items():
            filtered_rounds = []
            for round_of_funding in response.rounds_of_funding:
                # Skip rounds without a closed_date if filtering by date
                if round_of_funding.closed_date is None:
                    continue

                if start_date and round_of_funding.closed_date < start_date:
                    continue
                if end_date and round_of_funding.closed_date > end_date:
                    continue

                filtered_rounds.append(round_of_funding)

            filtered_responses[identifier] = RoundsOfFundingResp(rounds_of_funding=filtered_rounds)

        return filtered_responses

    return rounds_of_funding_responses


def sort_and_limit_rounds_of_funding_responses(
    rounds_of_funding_responses: dict[str, RoundsOfFundingResp],
    sort_order: Literal["asc", "desc"] = "desc",
    limit: int | None = None,
) -> dict[str, RoundsOfFundingResp]:
    """Sort rounds of funding by closed_date and optionally limit results."""
    sorted_responses = {}
    for identifier, response in rounds_of_funding_responses.items():
        rounds = response.rounds_of_funding

        # Sort by closed_date (putting None dates at the end)
        if sort_order == "desc":
            rounds.sort(key=lambda r: r.closed_date or date.min, reverse=True)
        else:
            rounds.sort(key=lambda r: r.closed_date or date.max, reverse=False)

        if limit is not None:
            rounds = rounds[:limit]

        sorted_responses[identifier] = RoundsOfFundingResp(rounds_of_funding=rounds)
    return sorted_responses


class GetRoundsOfFundingFromIdentifiers(KfinanceTool):
    name: str = "get_rounds_of_funding_from_identifiers"
    description: str = dedent(f"""
        Returns funding round overviews: transaction_ids, types, dates, basic notes. Use for funding/capital raising questions (NOT M&A).

        ⚠️ TWO-STEP REQUIREMENT: Most questions need BOTH tools:
        1. Call THIS → get transaction_ids
        2. Call get_rounds_of_funding_info_from_transaction_ids with those IDs
        3. Answer using data from BOTH

        STEP 2 MANDATORY for: pricing trends (up/down-rounds), exact valuations, security details (preferred shares, classes, participation caps), advisors, board seats, liquidation terms, use of proceeds, pre-deal context, investor contribution amounts, transaction specifics (upsizing, textual notes), fees.

        ⚠️ Don't rely on funding_round_notes alone—it's unstructured/incomplete. Always call STEP 2 for detailed questions.

        ROLE PARAMETER:
        • '{RoundsOfFundingRole.company_raising_funds}': Company receiving funds (e.g., "What rounds did Stripe raise?")
        • '{RoundsOfFundingRole.company_investing_in_round_of_funding}': Investor's perspective (e.g., "Which companies did Sequoia invest in?")

        ⚠️ INVESTOR QUESTIONS: "How much did [INVESTOR] contribute to [COMPANY]'s round?" → Use INVESTOR's identifier with role=company_investing_in_round_of_funding
        Example: "How much did Blackbird VC contribute to Morse Micro's Series C?" → identifier=Blackbird VC, role=company_investing_in_round_of_funding
    """).strip()
    args_schema: Type[BaseModel] = GetRoundsofFundingFromIdentifiersArgs
    accepted_permissions: set[Permission] | None = {Permission.MergersPermission}

    async def _arun(
        self,
        identifiers: list[str],
        role: RoundsOfFundingRole,
        start_date: date | None = None,
        end_date: date | None = None,
        limit: int | None = None,
        sort_order: Literal["asc", "desc"] = "desc",
    ) -> GetRoundsOfFundingFromIdentifiersResp:
        """"""
        return await get_rounds_of_funding_from_identifiers(
            identifiers=identifiers,
            role=role,
            start_date=start_date,
            end_date=end_date,
            limit=limit,
            sort_order=sort_order,
            httpx_client=self.kfinance_client.httpx_client,
        )


class GetRoundsOfFundingInfoFromTransactionIdsArgs(BaseModel):
    transaction_ids: list[int] = Field(
        description="List of transaction IDs for rounds of funding.", min_length=1
    )


class GetRoundsOfFundingInfoFromTransactionIdsResp(ToolRespWithErrors):
    results: dict[int, RoundOfFundingInfoWithAdvisors]


def merge_round_of_info_reponses_with_advisors_responses(
    round_of_info_responses: dict[int, RoundOfFundingInfo],
    advisor_responses: dict[AdvisorTaskKey, AdvisorsResp],
) -> dict[int, RoundOfFundingInfoWithAdvisors]:
    """Merge round of info responses with advisors responses"""

    round_of_info_with_advisors = {}
    for transaction_id, round_of_info in round_of_info_responses.items():
        target_key = AdvisorTaskKey(
            transaction_id=transaction_id,
            role=RoundsOfFundingRole.company_raising_funds,
            company_id=round_of_info.participants.target.company_id,
        )
        target_advisors_resp = advisor_responses.get(target_key)
        target_advisors = target_advisors_resp.advisors if target_advisors_resp else []

        investor_advisors = {}
        for investor in round_of_info.participants.investors:
            investor_key = AdvisorTaskKey(
                transaction_id=transaction_id,
                role=RoundsOfFundingRole.company_investing_in_round_of_funding,
                company_id=investor.company_id,
            )
            advisor_resp = advisor_responses.get(investor_key)
            investor_advisors[investor.company_id] = advisor_resp.advisors if advisor_resp else []

        # Create round info with advisors
        round_of_info_with_advisors[transaction_id] = round_of_info.with_advisors(
            target_advisors=target_advisors, investor_advisors=investor_advisors
        )

    return round_of_info_with_advisors


class GetRoundsOfFundingInfoFromTransactionIds(KfinanceTool):
    name: str = "get_rounds_of_funding_info_from_transaction_ids"
    description: str = dedent("""
        Returns DETAILED transaction data. STEP 2 of the two-step workflow—call after get_rounds_of_funding_from_identifiers.

        Pass transaction_ids from STEP 1. Default: pass ALL IDs (efficient), then filter results. Only pass specific IDs if question names exact rounds (e.g., "Series A").

        Provides: advisors (legal, financial), board seats, governance rights, liquidation preferences/multiples, security terms (anti-dilution, participation caps, redemption), exact valuations (pre/post-money), use of proceeds, investor contribution amounts, transaction specifics (upsizing, textual notes), fees.

        MANDATORY for questions about: pricing trends (up/down-rounds), security details (preferred shares, classes), advisors, board seats, liquidation terms, exact valuations, use of proceeds, pre-deal context, investor contributions, transaction details (upsizing, notes), fees.

        Examples requiring this:
        • "What is the funding price trend for X—up or down-rounds?"
        • "Did X issue participating preferred shares with a cap?"
        • "How much did [investor] contribute to [company]'s Series C?"
        • "What was the post-money valuation for X's Series E?"
        • "Did X outline pre-deal operating context?"
    """).strip()
    args_schema: Type[BaseModel] = GetRoundsOfFundingInfoFromTransactionIdsArgs
    accepted_permissions: set[Permission] | None = {Permission.MergersPermission}

    async def _arun(
        self, transaction_ids: list[int]
    ) -> GetRoundsOfFundingInfoFromTransactionIdsResp:
        """"""
        return await get_rounds_of_funding_info_from_transaction_ids(
            transaction_ids=transaction_ids,
            httpx_client=self.kfinance_client.httpx_client,
        )


class GetFundingSummaryFromIdentifiersArgs(ToolArgsWithIdentifiers):
    pass  # Only needs identifiers, no additional args needed


class GetFundingSummaryFromIdentifiersResp(ToolRespWithIdInfoAndErrors[FundingSummary]):
    pass


def build_funding_summaries_from_rof_responses(
    rounds_of_funding_responses: dict[str, RoundsOfFundingResp],
    detailed_round_info_responses: dict[int, RoundOfFundingInfo],
) -> dict[str, FundingSummary]:
    """Build funding summaries from rounds of funding and detailed round info responses."""
    summaries = {}
    for identifier, response in rounds_of_funding_responses.items():
        rounds = response.rounds_of_funding
        company_transaction_ids = [r.transaction_id for r in response.rounds_of_funding]

        total_rounds = len(rounds)
        dates = [r.closed_date for r in rounds if r.closed_date is not None]
        first_funding_date = min(dates) if dates else None
        most_recent_funding_date = max(dates) if dates else None

        rounds_by_type: dict[str, int] = {}
        for round_of_funding in rounds:
            funding_type = round_of_funding.funding_type or "Unknown"
            rounds_by_type[funding_type] = rounds_by_type.get(funding_type, 0) + 1

        total_capital_raised = None
        currency = None
        round_info_responses_with_aggregate_amount_raised = [
            detailed_round_info_responses[transaction_id]
            for transaction_id in company_transaction_ids
            if transaction_id in detailed_round_info_responses
            and detailed_round_info_responses[transaction_id].transaction.aggregate_amount_raised
            is not None
        ]
        # Sort transactions by closed_date descending to get most recent aggregate_amount_raised
        # (aggregate_amount_raised is cumulative, so the most recent round has the total)
        if round_info_responses_with_aggregate_amount_raised:
            if round_info_responses_with_aggregate_amount_raised:
                most_recent_round = max(
                    round_info_responses_with_aggregate_amount_raised,
                    key=lambda r: r.timeline.closed_date or date.min,
                )
                amount = most_recent_round.transaction.aggregate_amount_raised
                total_capital_raised = float(amount) if amount is not None else None
                currency = most_recent_round.transaction.currency

        summaries[identifier] = FundingSummary(
            company_id=identifier,
            total_capital_raised=total_capital_raised,
            total_capital_raised_currency=currency,
            total_rounds=total_rounds,
            first_funding_date=first_funding_date,
            most_recent_funding_date=most_recent_funding_date,
            rounds_by_type=rounds_by_type,
            sources=[
                {
                    "notes": "total_capital_raised, total_rounds, first_funding_date, most_recent_funding_date, and rounds_by_type are derived from underlying rounds of funding data that might be non-comprehensive."
                }
            ],
        )

    return summaries


class GetFundingSummaryFromIdentifiers(KfinanceTool):
    name: str = "get_funding_summary_from_identifiers"
    description: str = dedent("""
        Returns aggregate funding statistics: total_capital_raised, total_rounds count, first/most recent funding dates, rounds_by_type breakdown. No individual round details.

        ⚠️ Use for SIMPLE aggregates only (single summary numbers). For "CUMULATIVE" or "ACROSS ALL ROUNDS" questions, use get_rounds_of_funding_from_identifiers instead—those need individual rounds for verification/filtering.

        Use THIS for:
        • "How much TOTAL capital has X raised?" (if you don't need to verify individual rounds)
        • "How many rounds did X complete?"
        • "When was X's first/most recent funding?"

        DON'T use for:
        • "What is the cumulative amount raised by X across all disclosed rounds?" → Use get_rounds_of_funding_from_identifiers
        • "Show me X's funding history" → Use get_rounds_of_funding_from_identifiers
        • Any specific round questions → Use get_rounds_of_funding_from_identifiers

        ⚠️ If returns 0 rounds or null data, MUST follow up with get_rounds_of_funding_from_identifiers (summary often incomplete).
    """).strip()
    args_schema: Type[BaseModel] = GetFundingSummaryFromIdentifiersArgs
    accepted_permissions: set[Permission] | None = {Permission.MergersPermission}

    async def _arun(self, identifiers: list[str]) -> GetFundingSummaryFromIdentifiersResp:
        """"""
        return await get_funding_summary_from_identifiers(
            identifiers=identifiers,
            httpx_client=self.kfinance_client.httpx_client,
        )


async def get_rounds_of_funding_from_identifiers(
    identifiers: list[str],
    role: RoundsOfFundingRole,
    httpx_client: httpx.AsyncClient,
    start_date: date | None = None,
    end_date: date | None = None,
    limit: int | None = None,
    sort_order: Literal["asc", "desc"] = "desc",
) -> GetRoundsOfFundingFromIdentifiersResp:
    """Fetch rounds of funding for all identifiers."""

    id_triple_resp = await unified_fetch_id_triples(
        identifiers=identifiers, httpx_client=httpx_client
    )
    errors: list[str] = list(id_triple_resp.errors.values())

    tasks = [
        AsyncTask(
            func=fetch_rounds_of_funding_from_company_id,
            kwargs=dict(
                company_id=id_triple.company_id,
                role=role,
                httpx_client=httpx_client,
            ),
            result_key=identifier,
        )
        for identifier, id_triple in id_triple_resp.identifiers_to_id_triples.items()
    ]

    await batch_execute_async_tasks(tasks=tasks)

    results: dict[str, RoundsOfFundingResp] = dict()
    for task in tasks:
        if task.error:
            errors.append(task.error)
        else:
            results[task.result_key] = task.result

    filtered_responses = filter_rounds_of_funding_responses_by_date_range(
        results, start_date, end_date
    )

    sorted_responses = sort_and_limit_rounds_of_funding_responses(
        filtered_responses, sort_order, limit
    )

    return GetRoundsOfFundingFromIdentifiersResp(
        identifier_results=sorted_responses,
        identifier_info=id_triple_resp.identifiers_to_id_triples,
        errors=errors,
    )


async def fetch_rounds_of_funding_from_company_id(
    company_id: int,
    role: RoundsOfFundingRole,
    httpx_client: httpx.AsyncClient,
) -> RoundsOfFundingResp:
    """Fetch rounds of funding for one company_id."""
    if role is RoundsOfFundingRole.company_raising_funds:
        url = f"/fundingrounds/target/{company_id}"
    else:
        url = f"/fundingrounds/investor/{company_id}"

    resp = await httpx_client.get(url=url)
    resp.raise_for_status()
    return RoundsOfFundingResp.model_validate(resp.json())


async def get_rounds_of_funding_info_from_transaction_ids(
    transaction_ids: list[int],
    httpx_client: httpx.AsyncClient,
) -> GetRoundsOfFundingInfoFromTransactionIdsResp:
    """Fetch detailed round of funding info for transaction IDs."""

    tasks: list[AsyncTask[int]] = [
        AsyncTask(
            func=fetch_rounds_of_funding_info_from_transaction_id,
            kwargs=dict(
                transaction_id=transaction_id,
                httpx_client=httpx_client,
            ),
            result_key=transaction_id,
        )
        for transaction_id in transaction_ids
    ]

    await batch_execute_async_tasks(tasks=tasks)

    errors: list[str] = []
    round_of_info_responses: dict[int, RoundOfFundingInfo] = dict()
    for task in tasks:
        if task.error:
            errors.append(f"transaction_id {task.result_key}: {task.error}")
        else:
            round_of_info_responses[task.result_key] = task.result

    # Fetch advisor info for all companies in all transactions
    advisor_tasks: list[AsyncTask[AdvisorTaskKey]] = []

    for transaction_id, round_of_info in round_of_info_responses.items():
        target_key = AdvisorTaskKey(
            transaction_id=transaction_id,
            role=RoundsOfFundingRole.company_raising_funds,
            company_id=round_of_info.participants.target.company_id,
        )
        advisor_tasks.append(
            AsyncTask(
                func=fetch_advisors_for_company_raising_round_of_funding,
                kwargs=dict(
                    transaction_id=transaction_id,
                    httpx_client=httpx_client,
                ),
                result_key=target_key,
            )
        )

        for investor in round_of_info.participants.investors:
            investor_key = AdvisorTaskKey(
                transaction_id=transaction_id,
                role=RoundsOfFundingRole.company_investing_in_round_of_funding,
                company_id=investor.company_id,
            )
            advisor_tasks.append(
                AsyncTask(
                    func=fetch_advisors_for_company_investing_in_round_of_funding,
                    kwargs=dict(
                        transaction_id=transaction_id,
                        advised_company_id=investor_key.company_id,
                        httpx_client=httpx_client,
                    ),
                    result_key=investor_key,
                )
            )

    await batch_execute_async_tasks(tasks=advisor_tasks)

    advisor_responses: dict[AdvisorTaskKey, AdvisorsResp] = dict()
    for task in advisor_tasks:  # type: ignore[assignment]
        if task.error:
            # Skip errors in advisor fetches
            continue
        else:
            advisor_responses[task.result_key] = task.result  # type: ignore[index]

    round_of_info_with_advisors = merge_round_of_info_reponses_with_advisors_responses(
        round_of_info_responses, advisor_responses
    )

    return GetRoundsOfFundingInfoFromTransactionIdsResp(
        results=round_of_info_with_advisors,
        errors=errors,
    )


async def fetch_rounds_of_funding_info_from_transaction_id(
    transaction_id: int,
    httpx_client: httpx.AsyncClient,
) -> RoundOfFundingInfo:
    """Fetch detailed round of funding info for one transaction_id."""
    url = f"/fundinground/info/{transaction_id}"
    resp = await httpx_client.get(url=url)
    resp.raise_for_status()
    return RoundOfFundingInfo.model_validate(resp.json())


async def fetch_advisors_for_company_raising_round_of_funding(
    transaction_id: int,
    httpx_client: httpx.AsyncClient,
) -> AdvisorsResp:
    """Fetch advisors for the target company raising funds in a round."""
    url = f"/fundinground/info/{transaction_id}/advisors/target"
    resp = await httpx_client.get(url=url)
    resp.raise_for_status()
    return AdvisorsResp.model_validate(resp.json())


async def fetch_advisors_for_company_investing_in_round_of_funding(
    transaction_id: int,
    advised_company_id: int,
    httpx_client: httpx.AsyncClient,
) -> AdvisorsResp:
    """Fetch advisors for an investing company in a round of funding."""
    url = f"/fundinground/info/{transaction_id}/advisors/investor/{advised_company_id}"
    resp = await httpx_client.get(url=url)
    resp.raise_for_status()
    return AdvisorsResp.model_validate(resp.json())


async def get_funding_summary_from_identifiers(
    identifiers: list[str],
    httpx_client: httpx.AsyncClient,
) -> GetFundingSummaryFromIdentifiersResp:
    """Fetch funding summaries for all identifiers."""

    id_triple_resp = await unified_fetch_id_triples(
        identifiers=identifiers, httpx_client=httpx_client
    )
    errors: list[str] = list(id_triple_resp.errors.values())

    tasks: list[AsyncTask[str]] = [
        AsyncTask(
            func=fetch_rounds_of_funding_from_company_id,
            kwargs=dict(
                company_id=id_triple.company_id,
                role=RoundsOfFundingRole.company_raising_funds,
                httpx_client=httpx_client,
            ),
            result_key=identifier,
        )
        for identifier, id_triple in id_triple_resp.identifiers_to_id_triples.items()
    ]

    await batch_execute_async_tasks(tasks=tasks)

    rounds_of_funding_responses: dict[str, RoundsOfFundingResp] = dict()
    for task in tasks:
        if task.error:
            errors.append(task.error)
        else:
            rounds_of_funding_responses[task.result_key] = task.result

    all_transaction_ids = []
    for response in rounds_of_funding_responses.values():
        transaction_ids = [r.transaction_id for r in response.rounds_of_funding]
        all_transaction_ids.extend(transaction_ids)

    detail_tasks: list[AsyncTask[int]] = [
        AsyncTask(
            func=fetch_rounds_of_funding_info_from_transaction_id,
            kwargs=dict(
                transaction_id=transaction_id,
                httpx_client=httpx_client,
            ),
            result_key=transaction_id,
        )
        for transaction_id in all_transaction_ids
    ]

    await batch_execute_async_tasks(tasks=detail_tasks)

    detailed_round_info_responses: dict[int, RoundOfFundingInfo] = dict()
    for task in detail_tasks:  # type: ignore[assignment]
        if task.error:
            # Skip errors in individual detail fetches
            continue
        else:
            detailed_round_info_responses[task.result_key] = task.result  # type: ignore[index]

    summaries = build_funding_summaries_from_rof_responses(
        rounds_of_funding_responses, detailed_round_info_responses
    )

    return GetFundingSummaryFromIdentifiersResp(
        identifier_results=summaries,
        identifier_info=id_triple_resp.identifiers_to_id_triples,
        errors=errors,
    )

import kfinance
import datetime
from typing import Optional
[docs] def get_rounds_of_funding_from_identifiers(identifiers: list[str], role: kfinance.domains.rounds_of_funding.rounds_of_funding_models.RoundsOfFundingRole, start_date: datetime.date | None = None, end_date: datetime.date | None = None, limit: int | None = None, sort_order: Literal['asc', 'desc'] = 'desc') -> 'GetRoundsOfFundingFromIdentifiersResp': """Returns funding round overviews: transaction_ids, types, dates, basic notes. Use for funding/capital raising questions (NOT M&A). ⚠️ TWO-STEP REQUIREMENT: Most questions need BOTH tools: 1. Call THIS → get transaction_ids 2. Call get_rounds_of_funding_info_from_transaction_ids with those IDs 3. Answer using data from BOTH STEP 2 MANDATORY for: pricing trends (up/down-rounds), exact valuations, security details (preferred shares, classes, participation caps), advisors, board seats, liquidation terms, use of proceeds, pre-deal context, investor contribution amounts, transaction specifics (upsizing, textual notes), fees. ⚠️ Don't rely on funding_round_notes alone—it's unstructured/incomplete. Always call STEP 2 for detailed questions. ROLE PARAMETER: • 'company_raising_funds': Company receiving funds (e.g., "What rounds did Stripe raise?") • 'company_investing_in_round_of_funding': Investor's perspective (e.g., "Which companies did Sequoia invest in?") ⚠️ INVESTOR QUESTIONS: "How much did [INVESTOR] contribute to [COMPANY]'s round?" → Use INVESTOR's identifier with role=company_investing_in_round_of_funding Example: "How much did Blackbird VC contribute to Morse Micro's Series C?" → identifier=Blackbird VC, role=company_investing_in_round_of_funding :param identifiers: The identifiers, which can be a list of ticker symbols, ISINs, or CUSIPs, or company_ids :type identifiers: list[str] :param role: The role of the company involved in the round of funding :type role: RoundsOfFundingRole :param start_date: Filter rounds to those closed on or after this date (YYYY-MM-DD format) :type start_date: Union[date, NoneType] :param end_date: Filter rounds to those closed on or before this date (YYYY-MM-DD format) :type end_date: Union[date, NoneType] :param limit: Limit to top N funding rounds by sort order :type limit: Union[int, NoneType] :param sort_order: Sort order for funding rounds by closed_date. 'desc' shows most recent first, 'asc' shows oldest first :type sort_order: Literal['asc', 'desc'] :rtype: GetRoundsOfFundingFromIdentifiersResp"""
import kfinance import datetime from typing import Optional
[docs] def get_rounds_of_funding_info_from_transaction_ids(transaction_ids: list[int]) -> 'GetRoundsOfFundingInfoFromTransactionIdsResp': """Returns DETAILED transaction data. STEP 2 of the two-step workflow—call after get_rounds_of_funding_from_identifiers. Pass transaction_ids from STEP 1. Default: pass ALL IDs (efficient), then filter results. Only pass specific IDs if question names exact rounds (e.g., "Series A"). Provides: advisors (legal, financial), board seats, governance rights, liquidation preferences/multiples, security terms (anti-dilution, participation caps, redemption), exact valuations (pre/post-money), use of proceeds, investor contribution amounts, transaction specifics (upsizing, textual notes), fees. MANDATORY for questions about: pricing trends (up/down-rounds), security details (preferred shares, classes), advisors, board seats, liquidation terms, exact valuations, use of proceeds, pre-deal context, investor contributions, transaction details (upsizing, notes), fees. Examples requiring this: • "What is the funding price trend for X—up or down-rounds?" • "Did X issue participating preferred shares with a cap?" • "How much did [investor] contribute to [company]'s Series C?" • "What was the post-money valuation for X's Series E?" • "Did X outline pre-deal operating context?" :param transaction_ids: List of transaction IDs for rounds of funding. :type transaction_ids: list[int] :rtype: GetRoundsOfFundingInfoFromTransactionIdsResp"""
import kfinance import datetime from typing import Optional
[docs] def get_funding_summary_from_identifiers(identifiers: list[str]) -> 'GetFundingSummaryFromIdentifiersResp': """Returns aggregate funding statistics: total_capital_raised, total_rounds count, first/most recent funding dates, rounds_by_type breakdown. No individual round details. ⚠️ Use for SIMPLE aggregates only (single summary numbers). For "CUMULATIVE" or "ACROSS ALL ROUNDS" questions, use get_rounds_of_funding_from_identifiers instead—those need individual rounds for verification/filtering. Use THIS for: • "How much TOTAL capital has X raised?" (if you don't need to verify individual rounds) • "How many rounds did X complete?" • "When was X's first/most recent funding?" DON'T use for: • "What is the cumulative amount raised by X across all disclosed rounds?" → Use get_rounds_of_funding_from_identifiers • "Show me X's funding history" → Use get_rounds_of_funding_from_identifiers • Any specific round questions → Use get_rounds_of_funding_from_identifiers ⚠️ If returns 0 rounds or null data, MUST follow up with get_rounds_of_funding_from_identifiers (summary often incomplete). :param identifiers: The identifiers, which can be a list of ticker symbols, ISINs, or CUSIPs, or company_ids :type identifiers: list[str] :rtype: GetFundingSummaryFromIdentifiersResp"""