2020from edgar .files .html import Document
2121from datetime import datetime
2222
23+ share_concepts = [
24+ 'us-gaap_CommonStockSharesOutstanding' ,
25+ 'us-gaap_WeightedAverageNumberOfSharesOutstandingBasic' ,
26+ 'us-gaap_WeightedAverageNumberOfSharesOutstandingDiluted' ,
27+ 'us-gaap_WeightedAverageNumberOfDilutedSharesOutstanding' ,
28+ 'us-gaap_CommonStockSharesIssued'
29+ ]
30+
2331
2432def _is_html (text : str ) -> bool :
2533 """
@@ -353,7 +361,26 @@ def render_statement(
353361 # Determine the dominant scale for monetary values in this statement
354362 dominant_scale = determine_dominant_scale (statement_data , periods_to_display )
355363
356- # Add the fiscal period indicator and units note as a subtitle if available
364+ # Determine the scale used for share amounts if present
365+ shares_scale = None
366+
367+ # Look for share-related concepts to determine their scaling from the decimals attribute
368+ for item in statement_data :
369+ concept = item .get ('concept' , '' )
370+ if concept in share_concepts :
371+ # Check decimals attribute to determine proper scaling
372+ for period_key , _ in periods_to_display :
373+ decimals = item .get ('decimals' , {}).get (period_key )
374+ if isinstance (decimals , int ) and decimals <= 0 :
375+ # Use the decimals attribute to determine the scale
376+ # For shares, decimals is typically negative
377+ # -3 means thousands, -6 means millions, etc.
378+ shares_scale = decimals
379+ break
380+ if shares_scale is not None :
381+ break
382+
383+ # Add the fiscal period indicator and note as a subtitle if available
357384 if formatted_periods :
358385 subtitles = []
359386
@@ -363,12 +390,36 @@ def render_statement(
363390
364391 # Add units note
365392 if is_monetary_statement :
393+ monetary_scale_text = ""
366394 if dominant_scale == - 3 :
367- units_note = "[italic](In thousands, except per share data)[/italic] "
395+ monetary_scale_text = "thousands"
368396 elif dominant_scale == - 6 :
369- units_note = "[italic](In millions, except per share data)[/italic] "
397+ monetary_scale_text = "millions"
370398 elif dominant_scale == - 9 :
371- units_note = "[italic](In billions, except per share data)[/italic]"
399+ monetary_scale_text = "billions"
400+
401+ shares_scale_text = ""
402+ if shares_scale is not None :
403+ if shares_scale == - 3 :
404+ shares_scale_text = "thousands"
405+ elif shares_scale == - 6 :
406+ shares_scale_text = "millions"
407+ elif shares_scale == - 9 :
408+ shares_scale_text = "billions"
409+ elif shares_scale == 0 :
410+ shares_scale_text = "actual amounts"
411+ else :
412+ # For other negative scales (like -4, -5, -7, etc.)
413+ # Use a more generic description based on the scale
414+ scale_factor = 10 ** (- shares_scale )
415+ if scale_factor >= 1000 :
416+ shares_scale_text = f"scaled by { scale_factor :,} "
417+
418+ # Construct appropriate units note
419+ if monetary_scale_text and shares_scale_text and shares_scale != dominant_scale :
420+ units_note = f"[italic](In { monetary_scale_text } , except shares in { shares_scale_text } )[/italic]"
421+ elif monetary_scale_text :
422+ units_note = f"[italic](In { monetary_scale_text } , except per share data)[/italic]"
372423 else :
373424 units_note = ""
374425
@@ -595,8 +646,11 @@ def render_statement(
595646
596647 # Format the label based on level and abstract status
597648 level = item ['level' ]
649+
598650 # Remove [Abstract] from label if present
599651 label = item ['label' ].replace (' [Abstract]' , '' )
652+
653+ concept = item ['concept' ]
600654
601655 # Get values for each period
602656 period_values = []
@@ -608,22 +662,39 @@ def render_statement(
608662 # Check label to identify non-monetary items like shares, ratios, etc.
609663 is_monetary = is_monetary_statement
610664
611- # Check for shares-related values by examining label
665+ # Check for shares-related values by examining concept names
612666 label_lower = label .lower ()
613- if any (keyword in label_lower for keyword in [
614- 'earnings per share' , 'per common share' , 'per share' , 'in shares' , 'shares outstanding'
615- 'per basic' , 'per diluted'
616- ]):
617- is_monetary = False
667+ is_share_value = False
618668
669+ if concept in ['us-gaap_EarningsPerShareBasic' , 'us-gaap_EarningsPerShareDiluted' ]:
670+ is_monetary = False
671+ elif concept in share_concepts :
672+ is_monetary = False
673+ is_share_value = True
674+
619675 # Ratio-related items should not be monetary
620676 if any (keyword == word for keyword in ['ratio' , 'percentage' , 'per cent' ]
621677 for word in label_lower .split ()):
622678 is_monetary = False
623679
624680 # Format numeric values
625681 if isinstance (value , (int , float )):
626- formatted_value = Text (format_value (value , is_monetary , dominant_scale , fact_decimals ), justify = "right" )
682+ # Handle share values differently
683+ if is_share_value and isinstance (fact_decimals , int ):
684+ # Use fact_decimals to determine the appropriate scaling for share values
685+ # This ensures correct display for companies of all sizes
686+ if fact_decimals <= - 3 :
687+ # Apply appropriate scaling based on the actual decimals value
688+ scale_factor = 10 ** (- fact_decimals )
689+ scaled_value = value / scale_factor
690+ # Always display share amounts with 0 decimal places for cleaner presentation
691+ formatted_value = Text (f"{ scaled_value :,.0f} " , justify = "right" )
692+ else :
693+ # For smaller numbers or positive decimals, use unscaled values
694+ formatted_value = Text (f"{ value :,.0f} " , justify = "right" )
695+ else :
696+ # Format other values normally using the flexible format_value function
697+ formatted_value = Text (format_value (value , is_monetary , dominant_scale , fact_decimals ), justify = "right" )
627698 else :
628699 # Non-numeric values - check if it's HTML and convert if needed
629700 if value and isinstance (value , str ) and _is_html (value ):
0 commit comments