Files
edgartools/venv/lib/python3.10/site-packages/edgar/xbrl/current_period.py
2025-12-09 12:13:01 +01:00

877 lines
36 KiB
Python

"""
Current Period API - Convenient access to current period financial data.
This module provides the CurrentPeriodView class that offers simplified access
to the most recent period's financial data without comparative information,
addressing GitHub issue #425.
Key features:
- Automatic detection of the current (most recent) period
- Direct access to balance sheet, income statement, and cash flow data
- Support for raw XBRL concept names (unprocessed)
- Notes and disclosures access
- Beginner-friendly API design
"""
from datetime import date, datetime
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
import pandas as pd
from edgar.core import log
from edgar.richtools import repr_rich
from edgar.xbrl.exceptions import StatementNotFound
if TYPE_CHECKING:
from edgar.xbrl.statements import Statement
class CurrentPeriodView:
"""
Convenient access to current period financial data.
This class provides simplified access to the most recent period's
financial data without comparative information. It automatically
detects the current period and provides easy access to key statements.
Example usage:
>>> xbrl = filing.xbrl()
>>> current = xbrl.current_period
>>> balance_sheet = current.balance_sheet()
>>> income_statement = current.income_statement(raw_concepts=True)
"""
def __init__(self, xbrl):
"""
Initialize CurrentPeriodView with an XBRL object.
Args:
xbrl: XBRL object containing parsed financial data
"""
self.xbrl = xbrl
self._current_period_key = None
self._current_period_label = None
@property
def period_key(self) -> str:
"""
Get the current period key (most recent period).
The current period is determined by:
1. Document period end date if available
2. Most recent period in reporting periods
3. Fallback to any available period
Returns:
Period key string (e.g., "instant_2024-12-31" or "duration_2024-01-01_2024-12-31")
"""
if self._current_period_key is None:
self._current_period_key = self._detect_current_period()
return self._current_period_key
@property
def period_label(self) -> str:
"""
Get the human-readable label for the current period.
Returns:
Human-readable period label (e.g., "December 31, 2024" or "Year Ended December 31, 2024")
"""
if self._current_period_label is None:
self._detect_current_period() # This sets both key and label
return self._current_period_label or self.period_key
def _detect_current_period(self) -> str:
"""
Detect the current (most recent) period from available data.
Strategy:
1. Use document period end date to find matching instant period
2. If no instant match, find most recent duration period ending on document period end
3. Fall back to most recent period by end date
4. Final fallback to first available period
Returns:
Period key for the current period
"""
if not self.xbrl.reporting_periods:
log.warning("No reporting periods found in XBRL data")
return ""
# Try to use document period end date if available
document_period_end = None
if hasattr(self.xbrl, 'period_of_report') and self.xbrl.period_of_report:
try:
if isinstance(self.xbrl.period_of_report, str):
document_period_end = datetime.strptime(self.xbrl.period_of_report, '%Y-%m-%d').date()
elif isinstance(self.xbrl.period_of_report, (date, datetime)):
document_period_end = self.xbrl.period_of_report
if isinstance(document_period_end, datetime):
document_period_end = document_period_end.date()
except (ValueError, TypeError):
log.debug(f"Could not parse document period end date: {self.xbrl.period_of_report}")
# Sort periods by end date (most recent first)
periods_by_date = []
for period in self.xbrl.reporting_periods:
period_key = period['key']
period_label = period.get('label', period_key)
end_date = None
try:
if period_key.startswith('instant_'):
# Format: "instant_2024-12-31"
date_str = period_key.split('_', 1)[1]
end_date = datetime.strptime(date_str, '%Y-%m-%d').date()
elif period_key.startswith('duration_'):
# Format: "duration_2024-01-01_2024-12-31"
parts = period_key.split('_')
if len(parts) >= 3:
date_str = parts[2] # End date
end_date = datetime.strptime(date_str, '%Y-%m-%d').date()
if end_date:
periods_by_date.append((end_date, period_key, period_label))
except (ValueError, IndexError):
log.debug(f"Could not parse period key: {period_key}")
continue
if not periods_by_date:
# Fallback to first available period if no dates could be parsed
first_period = self.xbrl.reporting_periods[0]
self._current_period_key = first_period['key']
self._current_period_label = first_period.get('label', first_period['key'])
log.debug(f"Using fallback period: {self._current_period_key}")
return self._current_period_key
# Sort by date (most recent first)
periods_by_date.sort(key=lambda x: x[0], reverse=True)
# Strategy 1: If we have document period end, look for exact matches
# Prefer instant periods over duration periods when both match document end date
if document_period_end:
instant_match = None
duration_match = None
for end_date, period_key, period_label in periods_by_date:
if end_date == document_period_end:
if period_key.startswith('instant_'):
instant_match = (period_key, period_label)
elif period_key.startswith('duration_'):
duration_match = (period_key, period_label)
# Prefer instant match if available
if instant_match:
self._current_period_key = instant_match[0]
self._current_period_label = instant_match[1]
log.debug(f"Found instant period matching document end date: {instant_match[0]}")
return self._current_period_key
elif duration_match:
self._current_period_key = duration_match[0]
self._current_period_label = duration_match[1]
log.debug(f"Found duration period matching document end date: {duration_match[0]}")
return self._current_period_key
# Strategy 2: Use most recent period
most_recent = periods_by_date[0]
self._current_period_key = most_recent[1]
self._current_period_label = most_recent[2]
log.debug(f"Selected most recent period: {self._current_period_key} ({self._current_period_label})")
return self._current_period_key
def _get_appropriate_period_for_statement(self, statement_type: str) -> str:
"""
Get the appropriate period type for the given statement type.
Balance sheet items are point-in-time (instant periods).
Income statement and cash flow items represent activities over time (duration periods).
Args:
statement_type: Type of statement ('BalanceSheet', 'IncomeStatement', etc.)
Returns:
Period key appropriate for the statement type
"""
# Statements that use instant periods (point in time)
instant_statements = {
'BalanceSheet',
'StatementOfEquity',
'StatementOfFinancialPosition'
}
# Statements that use duration periods (period of time)
duration_statements = {
'IncomeStatement',
'CashFlowStatement',
'ComprehensiveIncome',
'StatementOfOperations',
'StatementOfCashFlows'
}
if statement_type in instant_statements:
# Use the current instant period
return self.period_key
elif statement_type in duration_statements:
# Find the most recent duration period with the same end date
if not self.xbrl.reporting_periods:
return self.period_key # Fallback to current period
# Get the end date from the current period (which might be instant)
current_end_date = None
current_period_key = self.period_key
if current_period_key.startswith('instant_'):
# Extract date from instant period
date_str = current_period_key.split('_', 1)[1]
try:
from datetime import datetime
current_end_date = datetime.strptime(date_str, '%Y-%m-%d').date()
except (ValueError, IndexError):
return self.period_key # Fallback
elif current_period_key.startswith('duration_'):
# Extract end date from duration period
parts = current_period_key.split('_')
if len(parts) >= 3:
try:
from datetime import datetime
current_end_date = datetime.strptime(parts[2], '%Y-%m-%d').date()
except (ValueError, IndexError):
return self.period_key # Fallback
if current_end_date:
# Look for a duration period ending on the same date
# Prefer annual periods, then quarterly, then other durations
matching_periods = []
for period in self.xbrl.reporting_periods:
period_key = period['key']
if period_key.startswith('duration_'):
parts = period_key.split('_')
if len(parts) >= 3:
try:
from datetime import datetime
end_date = datetime.strptime(parts[2], '%Y-%m-%d').date()
if end_date == current_end_date:
period_type = period.get('period_type', '')
priority = 1 if period_type == 'Annual' else (2 if period_type == 'Quarterly' else 3)
matching_periods.append((priority, period_key, period.get('label', period_key)))
except (ValueError, IndexError):
continue
if matching_periods:
# Sort by priority (1=Annual, 2=Quarterly, 3=Other) and return the best match
matching_periods.sort(key=lambda x: x[0])
selected_period = matching_periods[0][1]
log.debug(f"Selected duration period for {statement_type}: {selected_period}")
return selected_period
# Fallback: use current period even if it's not ideal
return self.period_key
else:
# Unknown statement type, use current period
log.debug(f"Unknown statement type {statement_type}, using current period: {self.period_key}")
return self.period_key
def balance_sheet(self, raw_concepts: bool = False, as_statement: bool = True) -> Union[pd.DataFrame, 'Statement']:
"""
Get current period balance sheet data.
Args:
raw_concepts: If True, preserve original XBRL concept names
(e.g., "us-gaap:Assets" instead of "Assets")
as_statement: If True, return a Statement object (default),
if False, return DataFrame
Returns:
Statement object with rich formatting by default,
or pandas DataFrame if as_statement=False
Example:
>>> stmt = xbrl.current_period.balance_sheet()
>>> print(stmt) # Rich formatted table
>>> df = xbrl.current_period.balance_sheet(as_statement=False)
>>> assets = df[df['label'].str.contains('Assets', case=False)]['value'].iloc[0]
"""
if as_statement:
return self._get_statement_object('BalanceSheet')
return self._get_statement_dataframe('BalanceSheet', raw_concepts=raw_concepts)
def income_statement(self, raw_concepts: bool = False, as_statement: bool = True) -> Union[pd.DataFrame, 'Statement']:
"""
Get current period income statement data.
Args:
raw_concepts: If True, preserve original XBRL concept names
(e.g., "us-gaap:Revenues" instead of "Revenue")
as_statement: If True, return a Statement object (default),
if False, return DataFrame
Returns:
Statement object with rich formatting by default,
or pandas DataFrame if as_statement=False
Example:
>>> stmt = xbrl.current_period.income_statement()
>>> print(stmt) # Rich formatted table
>>> df = xbrl.current_period.income_statement(as_statement=False, raw_concepts=True)
>>> revenue = df[df['concept'].str.contains('Revenues')]['value'].iloc[0]
"""
if as_statement:
return self._get_statement_object('IncomeStatement')
return self._get_statement_dataframe('IncomeStatement', raw_concepts=raw_concepts)
def cashflow_statement(self, raw_concepts: bool = False, as_statement: bool = True) -> Union[pd.DataFrame, 'Statement']:
"""
Get current period cash flow statement data.
Args:
raw_concepts: If True, preserve original XBRL concept names
(e.g., "us-gaap:NetCashProvidedByUsedInOperatingActivities")
as_statement: If True, return a Statement object (default),
if False, return DataFrame
Returns:
Statement object with rich formatting by default,
or pandas DataFrame if as_statement=False
Example:
>>> stmt = xbrl.current_period.cashflow_statement()
>>> print(stmt) # Rich formatted table
>>> df = xbrl.current_period.cashflow_statement(as_statement=False)
>>> operating_cf = df[df['label'].str.contains('Operating')]['value'].iloc[0]
"""
if as_statement:
return self._get_statement_object('CashFlowStatement')
return self._get_statement_dataframe('CashFlowStatement', raw_concepts=raw_concepts)
def statement_of_equity(self, raw_concepts: bool = False, as_statement: bool = True) -> Union[pd.DataFrame, 'Statement']:
"""
Get current period statement of equity data.
Args:
raw_concepts: If True, preserve original XBRL concept names
as_statement: If True, return a Statement object (default),
if False, return DataFrame
Returns:
Statement object with rich formatting by default,
or pandas DataFrame if as_statement=False
"""
if as_statement:
return self._get_statement_object('StatementOfEquity')
return self._get_statement_dataframe('StatementOfEquity', raw_concepts=raw_concepts)
def comprehensive_income(self, raw_concepts: bool = False, as_statement: bool = True) -> Union[pd.DataFrame, 'Statement']:
"""
Get current period comprehensive income statement data.
Args:
raw_concepts: If True, preserve original XBRL concept names
as_statement: If True, return a Statement object (default),
if False, return DataFrame
Returns:
Statement object with rich formatting by default,
or pandas DataFrame if as_statement=False
"""
if as_statement:
return self._get_statement_object('ComprehensiveIncome')
return self._get_statement_dataframe('ComprehensiveIncome', raw_concepts=raw_concepts)
def _get_statement_dataframe(self, statement_type: str, raw_concepts: bool = False) -> pd.DataFrame:
"""
Internal method to get statement data as DataFrame for current period.
Args:
statement_type: Type of statement ('BalanceSheet', 'IncomeStatement', etc.)
raw_concepts: Whether to preserve raw XBRL concept names
Returns:
pandas DataFrame with statement data filtered to current period
Raises:
StatementNotFound: If the requested statement type is not available
"""
try:
# Select appropriate period based on statement type
period_filter = self._get_appropriate_period_for_statement(statement_type)
# Get raw statement data filtered to current period
statement_data = self.xbrl.get_statement(statement_type, period_filter=period_filter)
if not statement_data:
entity_name = getattr(self.xbrl, 'entity_name', 'Unknown')
raise StatementNotFound(
statement_type=statement_type,
confidence=0.0,
found_statements=[],
entity_name=entity_name,
reason=f"No data found for {statement_type} in period {self.period_label}"
)
# Convert to DataFrame
rows = []
for item in statement_data:
# Get the value for appropriate period
values = item.get('values', {})
current_value = values.get(period_filter)
if current_value is not None:
row = {
'concept': self._get_concept_name(item, raw_concepts),
'label': item.get('label', ''),
'value': current_value,
'level': item.get('level', 0),
'is_abstract': item.get('is_abstract', False)
}
# Add original concept name if raw_concepts is requested
if raw_concepts:
row['standardized_label'] = item.get('label', '')
# Try to get original concept names from all_names
all_names = item.get('all_names', [])
if all_names:
row['original_concept'] = all_names[0] # First is usually original
# Add dimension information if present
if item.get('is_dimension', False):
row['dimension_label'] = item.get('full_dimension_label', '')
row['is_dimension'] = True
rows.append(row)
if not rows:
# Create empty DataFrame with expected structure
columns = ['concept', 'label', 'value', 'level', 'is_abstract']
if raw_concepts:
columns.extend(['standardized_label', 'original_concept'])
return pd.DataFrame(columns=columns)
return pd.DataFrame(rows)
except Exception as e:
log.error(f"Error retrieving {statement_type} for current period: {str(e)}")
entity_name = getattr(self.xbrl, 'entity_name', 'Unknown')
raise StatementNotFound(
statement_type=statement_type,
confidence=0.0,
found_statements=[],
entity_name=entity_name,
reason=f"Failed to retrieve {statement_type}: {str(e)}"
) from e
def _get_statement_object(self, statement_type: str) -> 'Statement':
"""
Internal method to get statement as a Statement object for current period.
Args:
statement_type: Type of statement ('BalanceSheet', 'IncomeStatement', etc.)
Returns:
Statement object with current period filtering applied
Raises:
StatementNotFound: If the requested statement type is not available
"""
try:
# Import here to avoid circular imports
# Select appropriate period based on statement type
period_filter = self._get_appropriate_period_for_statement(statement_type)
# Find the statement using the unified statement finder
matching_statements, found_role, actual_statement_type = self.xbrl.find_statement(statement_type)
if not found_role:
entity_name = getattr(self.xbrl, 'entity_name', 'Unknown')
raise StatementNotFound(
statement_type=statement_type,
confidence=0.0,
found_statements=[],
entity_name=entity_name,
reason=f"No matching {statement_type} found for current period {self.period_label}"
)
# Create a Statement object with period filtering
# We'll create a custom Statement class that applies period filtering
statement = CurrentPeriodStatement(
self.xbrl,
found_role,
canonical_type=statement_type,
period_filter=period_filter,
period_label=self.period_label
)
return statement
except Exception as e:
log.error(f"Error retrieving {statement_type} statement object for current period: {str(e)}")
entity_name = getattr(self.xbrl, 'entity_name', 'Unknown')
raise StatementNotFound(
statement_type=statement_type,
confidence=0.0,
found_statements=[],
entity_name=entity_name,
reason=f"Failed to retrieve {statement_type} statement: {str(e)}"
) from e
def _get_concept_name(self, item: Dict[str, Any], raw_concepts: bool) -> str:
"""
Get the appropriate concept name based on raw_concepts flag.
Args:
item: Statement line item dictionary
raw_concepts: Whether to use raw XBRL concept names
Returns:
Concept name (raw or processed)
"""
if raw_concepts:
# Try to get original concept name
all_names = item.get('all_names', [])
if all_names:
# Return first name, converting underscores back to colons for XBRL format
original = all_names[0]
if '_' in original and ':' not in original:
# This looks like a normalized name, try to restore colon format
parts = original.split('_', 1)
if len(parts) == 2 and parts[0] in ['us-gaap', 'dei', 'srt']:
return f"{parts[0]}:{parts[1]}"
return original
return item.get('concept', '')
else:
# Use processed concept name
return item.get('concept', '')
def notes(self, section_name: Optional[str] = None) -> List[Dict[str, Any]]:
"""
Get notes to financial statements for the current period.
Args:
section_name: Optional specific note section to retrieve
(e.g., "inventory", "revenue recognition")
Returns:
List of note sections with their content
Note:
This is a placeholder implementation. Full notes access would require
additional development to parse and structure note content.
"""
# Get all statements and filter for notes
all_statements = self.xbrl.get_all_statements()
note_statements = []
for stmt in all_statements:
stmt_type = (stmt.get('type') or '').lower()
definition = (stmt.get('definition') or '').lower()
# Check if this looks like a note section
if ('note' in stmt_type or 'note' in definition or
'disclosure' in stmt_type or 'disclosure' in definition):
# If specific section requested, filter by name
if section_name:
if section_name.lower() in definition or section_name.lower() in stmt_type:
note_statements.append({
'section_name': stmt.get('definition', 'Untitled Note'),
'type': stmt.get('type', ''),
'role': stmt.get('role', ''),
'element_count': stmt.get('element_count', 0)
})
else:
# Return all note sections
note_statements.append({
'section_name': stmt.get('definition', 'Untitled Note'),
'type': stmt.get('type', ''),
'role': stmt.get('role', ''),
'element_count': stmt.get('element_count', 0)
})
return note_statements
def get_fact(self, concept: str, raw_concept: bool = False) -> Any:
"""
Get a specific fact value for the current period.
Args:
concept: XBRL concept name to look up
raw_concept: If True, treat concept as raw XBRL name (with colons)
Returns:
Fact value if found, None otherwise
Example:
>>> revenue = xbrl.current_period.get_fact('Revenues')
>>> revenue_raw = xbrl.current_period.get_fact('us-gaap:Revenues', raw_concept=True)
"""
try:
# Normalize concept name if needed
if raw_concept and ':' in concept:
# Convert colon format to underscore for internal lookup
concept = concept.replace(':', '_')
# Use XBRL's fact finding method with current period filter
facts = self.xbrl._find_facts_for_element(concept, period_filter=self.period_key)
if facts:
# Return the first matching fact's value
for _context_id, wrapped_fact in facts.items():
fact = wrapped_fact['fact']
return fact.numeric_value if fact.numeric_value is not None else fact.value
return None
except Exception as e:
log.debug(f"Error retrieving fact {concept}: {str(e)}")
return None
def to_dict(self) -> Dict[str, Any]:
"""
Convert current period data to a dictionary format.
Returns:
Dictionary with current period information and key financial data
"""
result = {
'period_key': self.period_key,
'period_label': self.period_label,
'entity_name': getattr(self.xbrl, 'entity_name', None),
'document_type': getattr(self.xbrl, 'document_type', None),
'statements': {}
}
# Try to get key statements
statement_types = ['BalanceSheet', 'IncomeStatement', 'CashFlowStatement']
for stmt_type in statement_types:
try:
df = self._get_statement_dataframe(stmt_type, raw_concepts=False)
if not df.empty:
# Convert DataFrame to list of dicts for JSON serialization
result['statements'][stmt_type] = df.to_dict('records')
except StatementNotFound:
result['statements'][stmt_type] = None
return result
def debug_info(self) -> Dict[str, Any]:
"""
Get debugging information about the current period and data availability.
Returns:
Dictionary with detailed debugging information
"""
info = {
'current_period_key': self.period_key,
'current_period_label': self.period_label,
'total_reporting_periods': len(self.xbrl.reporting_periods),
'entity_name': getattr(self.xbrl, 'entity_name', 'Unknown'),
'document_period_end': getattr(self.xbrl, 'period_of_report', None),
'periods': [],
'statements': {}
}
# Add all periods with basic info
for period in self.xbrl.reporting_periods:
period_info = {
'key': period['key'],
'label': period.get('label', 'No label'),
'type': 'instant' if 'instant_' in period['key'] else 'duration'
}
info['periods'].append(period_info)
# Check statement availability
statement_types = ['BalanceSheet', 'IncomeStatement', 'CashFlowStatement']
for stmt_type in statement_types:
try:
# Get the period that would be used for this statement
period_for_stmt = self._get_appropriate_period_for_statement(stmt_type)
# Get raw statement data
raw_data = self.xbrl.get_statement(stmt_type, period_filter=period_for_stmt)
if raw_data:
# Count items with values
items_with_values = sum(1 for item in raw_data
if period_for_stmt in item.get('values', {}))
info['statements'][stmt_type] = {
'period_used': period_for_stmt,
'raw_data_items': len(raw_data),
'items_with_values': items_with_values,
'available': items_with_values > 0,
'error': None
}
else:
info['statements'][stmt_type] = {
'period_used': period_for_stmt,
'raw_data_items': 0,
'items_with_values': 0,
'available': False,
'error': 'No raw data returned'
}
except Exception as e:
info['statements'][stmt_type] = {
'period_used': None,
'raw_data_items': 0,
'items_with_values': 0,
'available': False,
'error': str(e)
}
return info
def __repr__(self) -> str:
"""String representation showing current period info."""
entity_name = getattr(self.xbrl, 'entity_name', 'Unknown Entity')
return f"CurrentPeriodView(entity='{entity_name}', period='{self.period_label}')"
def __str__(self) -> str:
"""User-friendly string representation."""
entity_name = getattr(self.xbrl, 'entity_name', 'Unknown Entity')
return f"Current Period Data for {entity_name}\nPeriod: {self.period_label}"
class CurrentPeriodStatement:
"""
A Statement object that applies current period filtering.
This class wraps a regular Statement object and ensures that only
the current period data is shown when rendering or accessing data.
"""
def __init__(self, xbrl, role_or_type: str, canonical_type: Optional[str] = None,
period_filter: Optional[str] = None, period_label: Optional[str] = None):
"""
Initialize with period filtering.
Args:
xbrl: XBRL object containing parsed data
role_or_type: Role URI, statement type, or statement short name
canonical_type: Optional canonical statement type
period_filter: Period key to filter to
period_label: Human-readable period label
"""
self.xbrl = xbrl
self.role_or_type = role_or_type
self.canonical_type = canonical_type
self.period_filter = period_filter
self.period_label = period_label
# Create the underlying Statement object
from edgar.xbrl.statements import Statement
self._statement = Statement(xbrl, role_or_type, canonical_type, skip_concept_check=True)
def render(self, standard: bool = True, show_date_range: bool = False,
include_dimensions: bool = True) -> Any:
"""
Render the statement as a formatted table for current period only.
Args:
standard: Whether to use standardized concept labels
show_date_range: Whether to show full date ranges for duration periods
include_dimensions: Whether to include dimensional segment data
Returns:
Rich Table containing the rendered statement for current period
"""
# Use the canonical type for rendering if available, otherwise use the role
rendering_type = self.canonical_type if self.canonical_type else self.role_or_type
return self.xbrl.render_statement(
rendering_type,
period_filter=self.period_filter,
standard=standard,
show_date_range=show_date_range,
include_dimensions=include_dimensions
)
def get_raw_data(self) -> List[Dict[str, Any]]:
"""
Get the raw statement data filtered to current period.
Returns:
List of line items with values for current period only
"""
return self._statement.get_raw_data(period_filter=self.period_filter)
def get_dataframe(self, raw_concepts: bool = False) -> pd.DataFrame:
"""
Convert the statement to a DataFrame for current period.
Args:
raw_concepts: If True, preserve original XBRL concept names
Returns:
pandas DataFrame with current period data only
"""
# Get raw data for current period
raw_data = self.get_raw_data()
# Convert to DataFrame format similar to CurrentPeriodView
rows = []
for item in raw_data:
values = item.get('values', {})
current_value = values.get(self.period_filter)
if current_value is not None:
concept_name = item.get('concept', '')
if raw_concepts:
# Try to get original concept name
all_names = item.get('all_names', [])
if all_names:
original = all_names[0]
if '_' in original and ':' not in original:
parts = original.split('_', 1)
if len(parts) == 2 and parts[0] in ['us-gaap', 'dei', 'srt']:
concept_name = f"{parts[0]}:{parts[1]}"
else:
concept_name = original
else:
concept_name = original
row = {
'concept': concept_name,
'label': item.get('label', ''),
'value': current_value,
'level': item.get('level', 0),
'is_abstract': item.get('is_abstract', False)
}
# Add original concept name if raw_concepts is requested
if raw_concepts:
row['standardized_label'] = item.get('label', '')
all_names = item.get('all_names', [])
if all_names:
row['original_concept'] = all_names[0]
# Add dimension information if present
if item.get('is_dimension', False):
row['dimension_label'] = item.get('full_dimension_label', '')
row['is_dimension'] = True
rows.append(row)
return pd.DataFrame(rows)
def calculate_ratios(self) -> Dict[str, float]:
"""Calculate common financial ratios for this statement."""
return self._statement.calculate_ratios()
def __rich__(self) -> Any:
"""Rich console representation."""
return self.render()
def __repr__(self) -> str:
"""String representation."""
return repr_rich(self.__rich__())
def __str__(self) -> str:
"""User-friendly string representation."""
return repr(self)