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"""