Source code for kfinance.domains.line_items.line_item_tools

from difflib import SequenceMatcher
from textwrap import dedent
from typing import Any, Literal, Type

import httpx
from pydantic import BaseModel, Field, model_validator

from kfinance.client.id_resolution import unified_fetch_id_triples
from kfinance.client.models.date_and_period_models import NumPeriods, NumPeriodsBack, PeriodType
from kfinance.client.models.response_models import PostResponse
from kfinance.client.permission_models import Permission
from kfinance.domains.line_items.line_item_models import (
    LINE_ITEM_NAMES_AND_ALIASES,
    LINE_ITEM_TO_DESCRIPTIONS_MAP,
    CalendarType,
    LineItemResp,
    LineItemScore,
)
from kfinance.domains.line_items.response_notes import (
    insert_fiscal_period_notes,
    insert_source_link_note,
)
from kfinance.integrations.tool_calling.tool_calling_models import (
    KfinanceTool,
    ToolArgsWithIdentifiers,
    ToolRespWithIdInfoAndErrors,
    ValidQuarter,
)


def _find_similar_line_items(
    invalid_item: str, descriptors: dict[str, str], max_suggestions: int = 8
) -> list[LineItemScore]:
    """Find similar line items using keyword matching and string similarity.

    Args:
        invalid_item: The invalid line item provided by the user
        descriptors: Dictionary mapping line item names to descriptions
        max_suggestions: Maximum number of suggestions to return

    Returns:
        List of LineItemScore objects for the best matches
    """
    if not descriptors:
        return []

    invalid_lower = invalid_item.lower()
    scores: list[LineItemScore] = []

    for line_item, description in descriptors.items():
        # Calculate similarity scores
        name_similarity = SequenceMatcher(None, invalid_lower, line_item.lower()).ratio()

        # Check for keyword matches in the line item name
        invalid_words = set(invalid_lower.replace("_", " ").split())
        item_words = set(line_item.lower().replace("_", " ").split())
        keyword_match_score = len(invalid_words.intersection(item_words)) / max(
            len(invalid_words), 1
        )

        # Check for keyword matches in description
        description_words = set(description.lower().split())
        description_match_score = len(invalid_words.intersection(description_words)) / max(
            len(invalid_words), 1
        )

        # Combined score (weighted)
        total_score = (
            name_similarity * 0.5  # Direct name similarity
            + keyword_match_score * 0.3  # Keyword matches in name
            + description_match_score * 0.2  # Keyword matches in description
        )

        scores.append(LineItemScore(name=line_item, description=description, score=total_score))

    # Sort by score (descending) and return top matches
    scores.sort(reverse=True, key=lambda x: x.score)
    return [item for item in scores[:max_suggestions] if item.score > 0.1]


def _smart_line_item_validator(v: str) -> str:
    """Custom validator that provides intelligent suggestions for invalid line items."""
    if v not in LINE_ITEM_NAMES_AND_ALIASES:
        # Find similar items using pre-computed descriptors
        suggestions = _find_similar_line_items(v, LINE_ITEM_TO_DESCRIPTIONS_MAP)

        if suggestions:
            suggestion_text = "\n\nDid you mean one of these?\n"
            for item in suggestions:
                suggestion_text += f"  • '{item.name}': {item.description}\n"

            error_msg = f"Invalid line_item '{v}'.{suggestion_text}"
        else:
            error_msg = f"Invalid line_item '{v}'. Please refer to the tool documentation for valid options."

        raise ValueError(error_msg)
    return v


class GetFinancialLineItemFromIdentifiersArgs(ToolArgsWithIdentifiers):
    # Note: mypy will not enforce this literal because of the type: ignore.
    # But pydantic still uses the literal to check for allowed values and only includes
    # allowed values in generated schemas.
    line_item: Literal[tuple(LINE_ITEM_NAMES_AND_ALIASES)] = Field(  # type: ignore[valid-type]
        description="The type of financial line_item requested"
    )
    period_type: PeriodType | None = Field(
        default=None, description="The period type (annual or quarterly)"
    )
    start_year: int | None = Field(
        default=None,
        description="The starting year for the data range. Use null for the most recent data.",
    )
    end_year: int | None = Field(
        default=None,
        description="The ending year for the data range. Use null for the most recent data.",
    )
    start_quarter: ValidQuarter | None = Field(
        default=None, description="Starting quarter (1-4). Only used when period_type is quarterly."
    )
    end_quarter: ValidQuarter | None = Field(
        default=None, description="Ending quarter (1-4). Only used when period_type is quarterly."
    )
    calendar_type: CalendarType | None = Field(
        default=None, description="Fiscal year or calendar year"
    )
    num_periods: NumPeriods | None = Field(
        default=None, description="The number of periods to retrieve data for (1-99)"
    )
    num_periods_back: NumPeriodsBack | None = Field(
        default=None,
        description="The end period of the data range expressed as number of periods back relative to the present period (0-99)",
    )

    @model_validator(mode="before")
    @classmethod
    def validate_line_item_with_suggestions(cls, values: dict) -> dict:
        """Custom validator that provides intelligent suggestions for invalid line items."""
        if isinstance(values, dict) and "line_item" in values:
            line_item = values["line_item"]
            # Use the helper function to validate and provide suggestions
            _smart_line_item_validator(line_item)
        return values


class GetFinancialLineItemFromIdentifiersResp(ToolRespWithIdInfoAndErrors[LineItemResp]):
    notes: list[str] = Field(default_factory=list)


class GetFinancialLineItemFromIdentifiers(KfinanceTool):
    name: str = "get_financial_line_item_from_identifiers"
    description: str = dedent("""
        Get the financial line item associated with a list of identifiers.

        - When possible, pass multiple identifiers in a single call rather than making multiple calls.
        - To fetch the most recent value, leave all time parameters as null.
        - Line item names are case-insensitive, use underscores, and support common aliases (e.g., 'revenue' and 'normal_revenue' return the same data).
        - To filter by time, use either absolute time (start_year, end_year, start_quarter, end_quarter) OR relative time (num_periods, num_periods_back)—but not both.
        - Set calendar_type based on how the query references the time period—use "fiscal" for fiscal year references and "calendar" for calendar year references.
        - When calendar_type=None, it defaults to 'fiscal'.
        - Exception: with multiple identifiers and absolute time, calendar_type=None defaults to 'calendar' for cross-company comparability; calendar_type='fiscal' returns fiscal data but should not be compared across companies since fiscal years have different end dates.

        Examples:
        Query: "Get MSFT and AAPL revenue and gross profit quarterly"
        Function: get_financial_line_item_from_identifiers(line_item="revenue", identifiers=["MSFT", "AAPL"], period_type="quarterly")
        Function: get_financial_line_item_from_identifiers(line_item="gross_profit", identifiers=["MSFT", "AAPL"], period_type="quarterly")

        Query: "General Electric's ebt excluding unusual items for FY2023"
        Function: get_financial_line_item_from_identifiers(line_item="ebt_excluding_unusual_items", identifiers=["General Electric"], period_type="annual", calendar_type="fiscal", start_year=2023, end_year=2023)

        Query: "What is the most recent three quarters except one ppe for Exxon and Hasbro?"
        Function: get_financial_line_item_from_identifiers(line_item="ppe", period_type="quarterly", num_periods=2, num_periods_back=1, identifiers=["Exxon", "Hasbro"])

        Query: "What are the ytd operating income values for Hilton for the calendar year 2022?"
        Function: get_financial_line_item_from_identifiers(line_item="operating_income", period_type="ytd", calendar_type="calendar", start_year=2022, end_year=2022, identifiers=["Hilton"])

        Query: "Compare AAPL and MSFT revenue for 2023"
        Function: get_financial_line_item_from_identifiers(line_item="revenue", identifiers=["AAPL", "MSFT"], period_type="annual", calendar_type="calendar", start_year=2023, end_year=2023)

        Note: This tool automatically includes explanatory notes about data sources, fiscal period warnings, and terminology guidelines.
    """).strip()
    args_schema: Type[BaseModel] = GetFinancialLineItemFromIdentifiersArgs
    accepted_permissions: set[Permission] | None = {
        Permission.StatementsPermission,
        Permission.PrivateCompanyFinancialsPermission,
    }

    async def _arun(
        self,
        identifiers: list[str],
        line_item: str,
        period_type: PeriodType | None = None,
        start_year: int | None = None,
        end_year: int | None = None,
        start_quarter: Literal[1, 2, 3, 4] | None = None,
        end_quarter: Literal[1, 2, 3, 4] | None = None,
        calendar_type: CalendarType | None = None,
        num_periods: int | None = None,
        num_periods_back: int | None = None,
    ) -> GetFinancialLineItemFromIdentifiersResp:
        """"""
        return await get_financial_line_item_from_identifiers(
            identifiers=identifiers,
            line_item=line_item,
            httpx_client=self.kfinance_client.httpx_client,
            period_type=period_type,
            start_year=start_year,
            end_year=end_year,
            start_quarter=start_quarter,
            end_quarter=end_quarter,
            calendar_type=calendar_type,
            num_periods=num_periods,
            num_periods_back=num_periods_back,
        )


async def get_financial_line_item_from_identifiers(
    identifiers: list[str],
    line_item: str,
    httpx_client: httpx.AsyncClient,
    period_type: PeriodType | None = None,
    start_year: int | None = None,
    end_year: int | None = None,
    start_quarter: Literal[1, 2, 3, 4] | None = None,
    end_quarter: Literal[1, 2, 3, 4] | None = None,
    calendar_type: CalendarType | None = None,
    num_periods: int | None = None,
    num_periods_back: int | None = None,
) -> GetFinancialLineItemFromIdentifiersResp:
    """Fetch financial line items for all identifiers."""

    # First resolve identifiers to company IDs
    id_triple_resp = await unified_fetch_id_triples(
        identifiers=identifiers, httpx_client=httpx_client
    )
    errors: list[str] = list(id_triple_resp.errors.values())

    # Fetch line items for all resolved company IDs
    if id_triple_resp.company_ids:
        line_item_resp = await fetch_line_item_from_company_ids(
            company_ids=id_triple_resp.company_ids,
            line_item=line_item,
            httpx_client=httpx_client,
            period_type=period_type,
            start_year=start_year,
            end_year=end_year,
            start_quarter=start_quarter,
            end_quarter=end_quarter,
            calendar_type=calendar_type,
            num_periods=num_periods,
            num_periods_back=num_periods_back,
        )

        # Add any errors from the line item API, mapping company_id keys back to identifiers
        for company_id_str, error in line_item_resp.errors.items():
            original_identifier = id_triple_resp.get_identifier_from_company_id(int(company_id_str))
            errors.append(f"{original_identifier}: {error}")

        # Map results back to original identifiers
        identifier_to_results = {}
        for company_id_str, line_item_data in line_item_resp.results.items():
            original_identifier = id_triple_resp.get_identifier_from_company_id(int(company_id_str))
            identifier_to_results[original_identifier] = line_item_data

        results = identifier_to_results
    else:
        results = {}

    # If no date and multiple companies, only return the most recent value
    if (
        start_year is None
        and end_year is None
        and start_quarter is None
        and end_quarter is None
        and num_periods is None
        and num_periods_back is None
        and len(results) > 1
    ):
        for line_item_response in results.values():
            line_item_response.remove_all_periods_other_than_the_most_recent_one()

    resp_model = GetFinancialLineItemFromIdentifiersResp(
        identifier_results=results,
        identifier_info=id_triple_resp.identifiers_to_id_triples,
        errors=errors,
    )

    # Add explanatory notes
    insert_source_link_note(resp_model)
    insert_fiscal_period_notes(
        calendar_type=calendar_type,
        period_type=period_type,
        resp_model=resp_model,
    )

    return resp_model


async def fetch_line_item_from_company_ids(
    company_ids: list[int],
    line_item: str,
    httpx_client: httpx.AsyncClient,
    period_type: PeriodType | None = None,
    start_year: int | None = None,
    end_year: int | None = None,
    start_quarter: Literal[1, 2, 3, 4] | None = None,
    end_quarter: Literal[1, 2, 3, 4] | None = None,
    calendar_type: CalendarType | None = None,
    num_periods: int | None = None,
    num_periods_back: int | None = None,
) -> PostResponse[LineItemResp]:
    """Fetch line items for a list of company IDs."""
    # Build the request payload
    params: dict[str, Any] = {
        "company_ids": company_ids,
        "line_item": line_item,
    }

    if period_type is not None:
        params["period_type"] = period_type.value
    if start_year is not None:
        params["start_year"] = start_year
    if end_year is not None:
        params["end_year"] = end_year
    if start_quarter is not None:
        params["start_quarter"] = start_quarter
    if end_quarter is not None:
        params["end_quarter"] = end_quarter
    if calendar_type is not None:
        params["calendar_type"] = calendar_type.value
    if num_periods is not None:
        params["num_periods"] = num_periods
    if num_periods_back is not None:
        params["num_periods_back"] = num_periods_back

    resp = await httpx_client.post(url="/line_item/", json=params)
    resp.raise_for_status()

    return PostResponse[LineItemResp].model_validate(resp.json())

import kfinance
import datetime
from typing import Optional
[docs] def get_financial_line_item_from_identifiers(identifiers: list[str], line_item: str, period_type: kfinance.client.models.date_and_period_models.PeriodType | None = None, start_year: int | None = None, end_year: int | None = None, start_quarter: Optional[Literal[1, 2, 3, 4]] = None, end_quarter: Optional[Literal[1, 2, 3, 4]] = None, calendar_type: kfinance.domains.line_items.line_item_models.CalendarType | None = None, num_periods: int | None = None, num_periods_back: int | None = None) -> 'GetFinancialLineItemFromIdentifiersResp': """Get the financial line item associated with a list of identifiers. - When possible, pass multiple identifiers in a single call rather than making multiple calls. - To fetch the most recent value, leave all time parameters as null. - Line item names are case-insensitive, use underscores, and support common aliases (e.g., 'revenue' and 'normal_revenue' return the same data). - To filter by time, use either absolute time (start_year, end_year, start_quarter, end_quarter) OR relative time (num_periods, num_periods_back)—but not both. - Set calendar_type based on how the query references the time period—use "fiscal" for fiscal year references and "calendar" for calendar year references. - When calendar_type=None, it defaults to 'fiscal'. - Exception: with multiple identifiers and absolute time, calendar_type=None defaults to 'calendar' for cross-company comparability; calendar_type='fiscal' returns fiscal data but should not be compared across companies since fiscal years have different end dates. Examples: Query: "Get MSFT and AAPL revenue and gross profit quarterly" Function: get_financial_line_item_from_identifiers(line_item="revenue", identifiers=["MSFT", "AAPL"], period_type="quarterly") Function: get_financial_line_item_from_identifiers(line_item="gross_profit", identifiers=["MSFT", "AAPL"], period_type="quarterly") Query: "General Electric's ebt excluding unusual items for FY2023" Function: get_financial_line_item_from_identifiers(line_item="ebt_excluding_unusual_items", identifiers=["General Electric"], period_type="annual", calendar_type="fiscal", start_year=2023, end_year=2023) Query: "What is the most recent three quarters except one ppe for Exxon and Hasbro?" Function: get_financial_line_item_from_identifiers(line_item="ppe", period_type="quarterly", num_periods=2, num_periods_back=1, identifiers=["Exxon", "Hasbro"]) Query: "What are the ytd operating income values for Hilton for the calendar year 2022?" Function: get_financial_line_item_from_identifiers(line_item="operating_income", period_type="ytd", calendar_type="calendar", start_year=2022, end_year=2022, identifiers=["Hilton"]) Query: "Compare AAPL and MSFT revenue for 2023" Function: get_financial_line_item_from_identifiers(line_item="revenue", identifiers=["AAPL", "MSFT"], period_type="annual", calendar_type="calendar", start_year=2023, end_year=2023) Note: This tool automatically includes explanatory notes about data sources, fiscal period warnings, and terminology guidelines. :param identifiers: The identifiers, which can be a list of ticker symbols, ISINs, or CUSIPs, or company_ids :type identifiers: list[str] :param line_item: The type of financial line_item requested :type line_item: Literal['regular_revenue', 'normal_revenue', 'operating_revenue', 'finance_division_revenue', 'insurance_division_revenue', 'revenue_from_sale_of_assets', 'revenue_from_sale_of_investments', 'revenue_from_interest_and_investment_income', 'other_revenue', 'total_other_revenue', 'fees_and_other_income', 'total_revenue', 'revenue', 'combined_revenue', 'cost_of_goods_sold', 'cogs', 'finance_division_operating_expense', 'operating_expense_finance_division', 'insurance_division_operating_expense', 'operating_expense_insurance_division', 'finance_division_interest_expense', 'interest_expense_finance_division', 'cost_of_revenue', 'cor', 'gross_profit', 'selling_general_and_admin_expense', 'selling_general_and_admin', 'selling_general_and_admin_cost', 'sga', 'sg_and_a', 'exploration_and_drilling_costs', 'exploration_and_drilling_expense', 'provision_for_bad_debts', 'provision_for_bad_debt', 'pre_opening_costs', 'pre_opening_expense', 'total_selling_general_and_admin_expense', 'total_sga', 'total_selling_general_and_admin_cost', 'total_selling_general_and_admin', 'research_and_development_expense', 'rnd_expense', 'r_and_d_cost', 'r_and_d_expense', 'research_and_development_cost', 'rnd_cost', 'depreciation_and_amortization', 'd_and_a', 'dna', 'amortization_of_goodwill_and_intangibles', 'impairment_of_oil_gas_and_mineral_properties', 'impairment_o_and_g', 'impairment_of_oil_and_gas', 'total_depreciation_and_amortization', 'total_dna', 'total_d_and_a', 'other_operating_expense', 'total_other_operating_expense', 'total_operating_expense', 'operating_expense', 'operating_income', 'interest_expense', 'interest_and_investment_income', 'net_interest_expense', 'income_from_affiliates', 'currency_exchange_gains', 'other_non_operating_income', 'total_other_non_operating_income', 'ebt_excluding_unusual_items', 'earnings_before_taxes_excluding_unusual_items', 'restructuring_charges', 'merger_charges', 'merger_and_restructuring_charges', 'impairment_of_goodwill', 'gain_from_sale_of_assets', 'gain_from_sale_of_investments', 'asset_writedown', 'in_process_research_and_development_expense', 'in_process_research_and_development_cost', 'in_process_rnd_expense', 'in_process_r_and_d_cost', 'in_process_r_and_d_expense', 'in_process_rnd_cost', 'insurance_settlements', 'legal_settlements', 'other_unusual_items', 'total_other_unusual_items', 'total_unusual_items', 'unusual_items', 'ebt_including_unusual_items', 'earnings_before_taxes_including_unusual_items', 'income_tax_expense', 'income_tax', 'income_taxes', 'earnings_from_continued_operations', 'continued_operations_earnings', 'earnings_from_discontinued_operations', 'discontinued_operations_earnings', 'extraordinary_item_and_accounting_change', 'net_income_to_company', 'minority_interest_in_earnings', 'net_income_to_minority_interest', 'net_income', 'premium_on_redemption_of_preferred_stock', 'preferred_stock_dividend', 'other_preferred_stock_adjustments', 'other_adjustments_to_net_income', 'preferred_dividends_and_other_adjustments', 'net_income_allocable_to_general_partner', 'net_income_to_common_shareholders_including_extra_items', 'net_income_to_common_shareholders_excluding_extra_items', 'cash_and_equivalents', 'cash', 'cash_and_cash_equivalents', 'short_term_investments', 'trading_asset_securities', 'total_cash_and_short_term_investments', 'cash_and_short_term_investments', 'accounts_receivable', 'short_term_accounts_receivable', 'current_accounts_receivable', 'other_receivables', 'current_other_receivables', 'short_term_other_receivables', 'notes_receivable', 'current_notes_receivable', 'short_term_notes_receivable', 'total_receivables', 'current_total_receivable', 'current_total_receivables', 'total_receivable', 'short_term_total_receivable', 'short_term_total_receivables', 'inventory', 'inventories', 'prepaid_expense', 'prepaid_expenses', 'finance_division_loans_and_leases_short_term', 'short_term_finance_division_loans_and_leases', 'finance_division_short_term_loans_and_leases', 'short_term_loans_and_leases_of_the_finance_division', 'finance_division_other_current_assets', 'finance_division_other_short_term_assets', 'other_short_term_assets_of_the_finance_division', 'other_current_assets_of_the_finance_division', 'loans_held_for_sale', 'deferred_tax_asset_current_portion', 'current_deferred_tax_asset', 'short_term_deferred_tax_asset', 'restricted_cash', 'other_current_assets', 'total_current_assets', 'total_short_term_assets', 'short_term_assets', 'current_assets', 'gross_property_plant_and_equipment', 'gppe', 'gross_ppe', 'accumulated_depreciation', 'net_property_plant_and_equipment', 'nppe', 'property_plant_and_equipment', 'ppe', 'net_ppe', 'long_term_investments', 'non_current_investments', 'goodwill', 'other_intangibles', 'finance_division_loans_and_leases_long_term', 'finance_division_long_term_loans_and_leases', 'long_term_loans_and_leases_of_the_finance_division', 'long_term_finance_division_loans_and_leases', 'finance_division_other_non_current_assets', 'other_long_term_assets_of_the_finance_division', 'other_non_current_assets_of_the_finance_division', 'finance_division_other_long_term_assets', 'long_term_accounts_receivable', 'non_current_accounts_receivable', 'long_term_loans_receivable', 'non_current_loans_receivable', 'loans_receivable', 'long_term_deferred_tax_assets', 'non_current_deferred_tax_assets', 'long_term_deferred_charges', 'non_current_deferred_charges', 'other_long_term_assets', 'other_non_current_assets', 'non_current_other_assets', 'long_term_other_assets', 'total_assets', 'assets', 'accounts_payable', 'accrued_expenses', 'short_term_borrowings', 'current_borrowings', 'current_borrowing', 'short_term_borrowing', 'current_portion_of_long_term_debt', 'current_portion_of_non_current_debt', 'current_portion_of_lt_debt', 'current_portion_of_capital_leases', 'current_portion_of_cap_leases', 'current_portion_of_capitalized_leases', 'current_portion_of_leases', 'current_portion_of_long_term_debt_and_capital_leases', 'total_current_portion_of_non_current_debt_and_capitalized_leases', 'current_portion_of_non_current_debt_and_capital_leases', 'current_portion_of_non_current_debt_and_capitalized_leases', 'current_portion_of_lt_debt_and_cap_leases', 'total_current_portion_of_long_term_debt_and_capital_leases', 'total_current_portion_of_non_current_debt_and_capital_leases', 'total_current_portion_of_long_term_debt_and_capitalized_leases', 'total_current_portion_of_lt_debt_and_cap_leases', 'current_portion_of_long_term_debt_and_capitalized_leases', 'finance_division_debt_current_portion', 'finance_division_other_current_liabilities', 'current_income_taxes_payable', 'current_portion_of_income_taxes_payable', 'current_unearned_revenue', 'current_portion_of_unearned_revenue', 'current_deferred_tax_liability', 'other_current_liability', 'other_current_liabilities', 'total_current_liabilities', 'current_liabilities', 'long_term_debt', 'non_current_debt', 'capital_leases', 'capitalized_leases', 'long_term_leases', 'finance_division_debt_non_current_portion', 'finance_division_long_term_debt', 'finance_division_non_current_debt', 'finance_division_debt_long_term_portion', 'finance_division_other_non_current_liabilities', 'finance_division_other_long_term_liabilities', 'non_current_unearned_revenue', 'long_term_unearned_revenue', 'pension_and_other_post_retirement_benefit', 'non_current_deferred_tax_liability', 'other_non_current_liabilities', 'long_term_other_liabilities', 'non_current_other_liabilities', 'other_long_term_liabilities', 'total_liabilities', 'liabilities', 'preferred_stock_redeemable', 'redeemable_preferred_stock', 'preferred_stock_non_redeemable', 'non_redeemable_preferred_stock', 'preferred_stock_convertible', 'convertible_preferred_stock', 'preferred_stock_other', 'other_preferred_stock', 'preferred_stock_additional_paid_in_capital', 'additional_paid_in_capital_preferred_stock', 'preferred_stock_equity_adjustment', 'equity_adjustment_preferred_stock', 'treasury_stock_preferred_stock_convertible', 'treasury_convertible_preferred_stock', 'treasury_stock_convertible_preferred_stock', 'treasury_preferred_stock_convertible', 'treasury_stock_preferred_stock_non_redeemable', 'treasury_stock_non_redeemable_preferred_stock', 'treasury_preferred_stock_non_redeemable', 'treasury_non_redeemable_preferred_stock', 'treasury_stock_preferred_stock_redeemable', 'treasury_stock_redeemable_preferred_stock', 'treasury_redeemable_preferred_stock', 'treasury_preferred_stock_redeemable', 'total_preferred_equity', 'preferred_stock', 'preferred_equity', 'total_preferred_stock', 'common_stock', 'additional_paid_in_capital', 'retained_earnings', 'treasury_stock', 'other_equity', 'total_common_equity', 'common_equity', 'total_equity', 'total_shareholders_equity', 'equity', 'shareholders_equity', 'total_liabilities_and_equity', 'liabilities_and_equity', 'common_shares_outstanding', 'adjustments_to_cash_flow_net_income', 'other_amortization', 'total_other_non_cash_items', 'net_decrease_in_loans_originated_and_sold', 'provision_for_credit_losses', 'loss_on_equity_investments', 'stock_based_compensation', 'tax_benefit_from_stock_options', 'net_cash_from_discontinued_operation', 'cash_from_discontinued_operation', 'other_operating_activities', 'change_in_trading_asset_securities', 'change_in_accounts_receivable', 'change_in_inventories', 'change_in_accounts_payable', 'change_in_unearned_revenue', 'change_in_income_taxes', 'change_in_deferred_taxes', 'change_in_other_net_operating_assets', 'change_in_net_operating_assets', 'cash_from_operations', 'cash_flow_from_operations', 'cash_from_operating_activities', 'capital_expenditure', 'capex', 'capital_expenditures', 'sale_of_property_plant_and_equipment', 'sale_of_ppe', 'cash_acquisitions', 'divestitures', 'sale_of_real_estate', 'sale_of_real_properties', 'sale_of_real_estate_properties', 'sale_of_intangible_assets', 'sale_of_intangible_asset', 'sale_of_intangibles', 'net_cash_from_investments', 'net_decrease_in_investment_loans_originated_and_sold', 'other_investing_activities', 'total_other_investing_activities', 'cash_from_investing', 'cashflow_from_investing', 'cash_from_investing_activities', 'cashflow_from_investing_activities', 'short_term_debt_issued', 'current_debt_issued', 'long_term_debt_issued', 'non_current_debt_issued', 'total_debt_issued', 'short_term_debt_repaid', 'current_debt_repaid', 'long_term_debt_repaid', 'non_current_debt_repaid', 'total_debt_repaid', 'issuance_of_common_stock', 'repurchase_of_common_stock', 'issuance_of_preferred_stock', 'repurchase_of_preferred_stock', 'common_dividends_paid', 'preferred_dividends_paid', 'total_dividends_paid', 'dividends_paid', 'special_dividends_paid', 'other_financing_activities', 'cash_from_financing', 'cash_from_financing_activities', 'cashflow_from_financing', 'cashflow_from_financing_activities', 'foreign_exchange_rate_adjustments', 'foreign_exchange_adjustments', 'fx_adjustments', 'miscellaneous_cash_flow_adjustments', 'misc_cash_flow_adj', 'net_change_in_cash', 'change_in_cash', 'depreciation', 'depreciation_of_rental_assets', 'sale_proceeds_from_rental_assets', 'basic_eps', 'basic_earning_per_share', 'basic_earning_per_share_including_extra_items', 'basic_eps_including_extra_items', 'basic_eps_excluding_extra_items', 'basic_earning_per_share_excluding_extra_items', 'basic_eps_from_accounting_change', 'basic_earning_per_share_from_accounting_change', 'basic_eps_from_extraordinary_items', 'basic_earning_per_share_from_extraordinary_items', 'basic_eps_from_accounting_change_and_extraordinary_items', 'basic_earning_per_share_from_accounting_change_and_extraordinary_items', 'weighted_average_basic_shares_outstanding', 'diluted_eps', 'diluted_earning_per_share', 'diluted_eps_including_extra_items', 'diluted_earning_per_share_including_extra_items', 'diluted_eps_excluding_extra_items', 'diluted_earning_per_share_excluding_extra_items', 'weighted_average_diluted_shares_outstanding', 'normalized_basic_eps', 'normalized_basic_earning_per_share', 'normalized_diluted_eps', 'normalized_diluted_earning_per_share', 'dividends_per_share', 'distributable_cash_per_share', 'diluted_eps_from_accounting_change_and_extraordinary_items', 'diluted_earning_per_share_from_accounting_change_and_extraordinary_items', 'diluted_eps_from_accounting_change', 'diluted_earning_per_share_from_accounting_change', 'diluted_eps_from_extraordinary_items', 'diluted_earning_per_share_from_extraordinary_items', 'diluted_eps_from_discontinued_operations', 'diluted_earning_per_share_from_discontinued_operations', 'funds_from_operations', 'ffo', 'ebitda', 'earnings_before_interest_taxes_depreciation_and_amortization', 'ebita', 'earnings_before_interest_taxes_and_amortization', 'ebit', 'earnings_before_interest_and_taxes', 'ebitdar', 'earnings_before_interest_taxes_depreciation_amortization_and_rental_expense', 'net_debt', 'effective_tax_rate', 'tax_rate', 'current_ratio', 'quick_ratio', 'total_debt_to_capital', 'net_working_capital', 'working_capital', 'change_in_net_working_capital', 'total_debt', 'total_debt_to_equity_ratio', 'total_debt_to_equity', 'total_debt_to_total_equity', 'debt_ratio', 'total_debt_ratio'] :param period_type: The period type (annual or quarterly) :type period_type: Union[PeriodType, NoneType] :param start_year: The starting year for the data range. Use null for the most recent data. :type start_year: Union[int, NoneType] :param end_year: The ending year for the data range. Use null for the most recent data. :type end_year: Union[int, NoneType] :param start_quarter: Starting quarter (1-4). Only used when period_type is quarterly. :type start_quarter: Union[Annotated[Literal[1, 2, 3, 4], BeforeValidator], NoneType] :param end_quarter: Ending quarter (1-4). Only used when period_type is quarterly. :type end_quarter: Union[Annotated[Literal[1, 2, 3, 4], BeforeValidator], NoneType] :param calendar_type: Fiscal year or calendar year :type calendar_type: Union[CalendarType, NoneType] :param num_periods: The number of periods to retrieve data for (1-99) :type num_periods: Union[Annotated[int, FieldInfo(annotation=NoneType, required=True, description='The number of periods to retrieve data for (1-99)', metadata=[Ge(ge=1), Le(le=99)])], NoneType] :param num_periods_back: The end period of the data range expressed as number of periods back relative to the present period (0-99) :type num_periods_back: Union[Annotated[int, FieldInfo(annotation=NoneType, required=True, description='The end period of the data range expressed as number of periods back relative to the present period (0-99)', metadata=[Ge(ge=0), Le(le=99)])], NoneType] :rtype: GetFinancialLineItemFromIdentifiersResp"""