Mini-project 3: Balance Sheet¶

1) Setup¶

Again we are keeping the importations on top and pringin the data from the past mini projects, Tidy historical pack from mini project 1 and income statment data from project 2

#importations on top for code quality and organization
from pathlib import Path

import numpy as np
import pandas as pd
from IPython.display import display

pd.set_option('display.max_columns', 200)
pd.set_option('display.width', 180)
pd.set_option('display.float_format', lambda x: f'{x:,.2f}')

#Locating the base direction to get CSVs
def locate_base_dir(start: Path) -> Path:
    required_files = [
        Path('Mini-projects/outputs/unit1_tidy_historical_pack_usdm.csv'),
        Path('Mini-projects/outputs/unit1_tidy_historical_audit.csv'),
        Path('Mini projects/outputs/mini2_income_statement_full_model.csv'),
    ]
    for candidate in [start, *start.parents]:
        if all((candidate / rel_path).exists() for rel_path in required_files):
            return candidate
    raise FileNotFoundError('Could not locate the Chapter 4 tidy-data inputs from the current working directory.')


BASE_DIR = locate_base_dir(Path.cwd())
UNIT1_PACK_PATH = BASE_DIR / 'Mini-projects' / 'outputs' / 'unit1_tidy_historical_pack_usdm.csv'
UNIT1_AUDIT_PATH = BASE_DIR / 'Mini-projects' / 'outputs' / 'unit1_tidy_historical_audit.csv'
MINI2_PATH = BASE_DIR / 'Mini projects' / 'outputs' / 'mini2_income_statement_full_model.csv'
OUTPUT_DIR = BASE_DIR / 'Mini-projects' / 'outputs'
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

print(f'Base directory: {BASE_DIR}')
print(f'Unit 1 tidy pack: {UNIT1_PACK_PATH}')
print(f'Unit 1 audit: {UNIT1_AUDIT_PATH}')
print(f'Mini Project 2 model: {MINI2_PATH}')
print(f'Chapter 4 outputs will be written to: {OUTPUT_DIR}')
Base directory: /Users/theomachado/Library/CloudStorage/OneDrive-UniversityofFlorida/Spring 2026/FIN4453 - OFC
Unit 1 tidy pack: /Users/theomachado/Library/CloudStorage/OneDrive-UniversityofFlorida/Spring 2026/FIN4453 - OFC/Mini-projects/outputs/unit1_tidy_historical_pack_usdm.csv
Unit 1 audit: /Users/theomachado/Library/CloudStorage/OneDrive-UniversityofFlorida/Spring 2026/FIN4453 - OFC/Mini-projects/outputs/unit1_tidy_historical_audit.csv
Mini Project 2 model: /Users/theomachado/Library/CloudStorage/OneDrive-UniversityofFlorida/Spring 2026/FIN4453 - OFC/Mini projects/outputs/mini2_income_statement_full_model.csv
Chapter 4 outputs will be written to: /Users/theomachado/Library/CloudStorage/OneDrive-UniversityofFlorida/Spring 2026/FIN4453 - OFC/Mini-projects/outputs

2) Getting the CSV loaded and checked¶

Loading the csv and making sure they are not broke and contain all the data we need

#using pd to read the csv
pack = pd.read_csv(UNIT1_PACK_PATH)
audit = pd.read_csv(UNIT1_AUDIT_PATH)
mini2 = pd.read_csv(MINI2_PATH)

#Setting the required columns we are going to use
required_pack_cols = {
    'Quarter', 'FiscalYear', 'QuarterNumber', 'Date', 'Cash', 'CashAndSTInvestments',
    'AccountsReceivable', 'Inventory', 'AccountsPayable', 'AccruedExpenses',
    'DeferredRevenue', 'OtherCurrentAssets', 'OtherCurrentLiabilities',
    'TotalCurrentAssets', 'PPENet', 'TotalAssets', 'TotalLiabilities', 'TotalEquity',
    'TotalDebt', 'CapEx', 'Dividends', 'SBC', 'DeltaWC_CFSign'
}
required_audit_cols = {'Quarter', 'AuditPass', 'BalanceError_USDm', 'GrossProfitError_USDm'}
required_mini2_cols = {
    'Period', 'FiscalYear', 'Quarter', 'Stage', 'Revenue', 'COGS', 'DA',
    'InterestExpense', 'NetIncome', 'EBITDA', 'EBIT', 'GrossProfit',
    'OperatingIncome', 'EBT', 'TaxExpense'
}

#As mentioned in first class we should keep guardrails of the code and notify if any of the other projects data broke and we are missing something
missing_pack = sorted(required_pack_cols - set(pack.columns))
missing_audit = sorted(required_audit_cols - set(audit.columns))
missing_mini2 = sorted(required_mini2_cols - set(mini2.columns))
if missing_pack:
    raise KeyError(f'Missing pack columns: {missing_pack}')
if missing_audit:
    raise KeyError(f'Missing audit columns: {missing_audit}')
if missing_mini2:
    raise KeyError(f'Missing mini2 columns: {missing_mini2}')

pack = pack.copy()
pack['ReportDate'] = pd.to_datetime(pack['Date'])
pack['PeriodKey'] = pack['FiscalYear'].astype(int).astype(str) + '-Q' + pack['QuarterNumber'].astype(int).astype(str)

mini2_hist = mini2.loc[mini2['Stage'].eq('Historical')].copy()
mini2_hist['PeriodKey'] = mini2_hist['FiscalYear'].astype(int).astype(str) + '-Q' + mini2_hist['Quarter'].astype(int).astype(str)

spine = pack.merge(
    mini2_hist[
        [
            'PeriodKey', 'Stage', 'Revenue', 'COGS', 'GrossProfit', 'OperatingIncome',
            'EBIT', 'DA', 'EBITDA', 'InterestExpense', 'EBT', 'TaxExpense', 'NetIncome'
        ]
    ],
    on='PeriodKey',
    how='inner',
    suffixes=('_pack', '_mini2'),
)
spine = spine.sort_values(['FiscalYear', 'QuarterNumber']).reset_index(drop=True)

if spine['PeriodKey'].duplicated().any():
    duplicates = sorted(spine.loc[spine['PeriodKey'].duplicated(keep=False), 'PeriodKey'].unique().tolist())
    raise ValueError(f'Duplicate merged periods found: {duplicates}')

#Asserting all the 40 quarters are loaded
assert len(pack) == 40, f'Expected 40 historical pack rows, found {len(pack)}'
assert len(mini2_hist) == 40, f'Expected 40 historical mini2 rows, found {len(mini2_hist)}'
assert len(spine) == 40, f'Expected 40 merged historical rows, found {len(spine)}'

source_summary = pd.DataFrame(
    [
        ('unit1_tidy_historical_pack_usdm.csv', len(pack), pack['Quarter'].min(), pack['Quarter'].max()),
        ('unit1_tidy_historical_audit.csv', len(audit), audit['Quarter'].min(), audit['Quarter'].max()),
        ('mini2_income_statement_full_model.csv (Historical only)', len(mini2_hist), mini2_hist['PeriodKey'].min(), mini2_hist['PeriodKey'].max()),
        ('Merged Chapter 4 modeling spine', len(spine), spine['Quarter'].min(), spine['Quarter'].max()),
    ],
    columns=['Source', 'Rows', 'FirstPeriod', 'LastPeriod'],
)


audit_exceptions = audit.loc[~audit['AuditPass']].copy()

display(source_summary)

print(f"Inherited Unit 1 audit passes: {int(audit['AuditPass'].sum())} of {len(audit)} quarters")
if audit_exceptions.empty:
    print('No inherited Unit 1 audit exceptions were found.')
else:
    print('Inherited Unit 1 audit exceptions are shown below. They come from the prior project output and do not stop the Chapter 4 build.')
    display(audit_exceptions)
Source Rows FirstPeriod LastPeriod
0 unit1_tidy_historical_pack_usdm.csv 40 FY2016 Q4 FY2026 Q3
1 unit1_tidy_historical_audit.csv 40 FY2016 Q4 FY2026 Q3
2 mini2_income_statement_full_model.csv (Histori... 40 2016-Q4 2026-Q3
3 Merged Chapter 4 modeling spine 40 FY2016 Q4 FY2026 Q3
Inherited Unit 1 audit passes: 39 of 40 quarters
Inherited Unit 1 audit exceptions are shown below. They come from the prior project output and do not stop the Chapter 4 build.
Quarter IS_RecordCount BS_RecordCount CF_RecordCount Ratios_RecordCount BalanceError_USDm GrossProfitError_USDm AuditPass
3 FY2017 Q3 1 1 1 1 0.00 0.60 False

3) Helper functions¶

Here we are creating 3 functions:

  1. flaging the outliers based on z-scores, can help in financial analysis and highlight important quarters to include in the executive summary
  2. Finding max absolute value, also guide in the executive summary
  3. Helping in the exportation phase
def robust_outlier_flags(series: pd.Series, threshold: float = 3.5) -> pd.Series:
    values = pd.to_numeric(series, errors='coerce')
    median = values.dropna().median()
    mad = (values.dropna() - median).abs().median()
    if pd.isna(median) or pd.isna(mad) or mad == 0:
        return pd.Series(False, index=series.index, dtype='bool')
    score = 0.6745 * (values - median).abs() / mad
    return score.gt(threshold).fillna(False)


def max_abs_period(frame: pd.DataFrame, column: str) -> tuple[str, float]:
    series = pd.to_numeric(frame[column], errors='coerce')
    idx = series.abs().idxmax()
    return str(frame.loc[idx, 'Quarter']), float(series.loc[idx])


def write_schedule_csv(df: pd.DataFrame, file_name: str) -> Path:
    output_path = OUTPUT_DIR / file_name
    df.to_csv(output_path, index=False)
    return output_path

4) Computing BS metrics¶

This is the core of the project where we are going to create the schedules, assemble the balance sheet, check the build and export the results.

quarter_day_count = 365.0 / 4.0

# Creating WC by hand what are we including on it
working_capital = pd.DataFrame({
    'Quarter': spine['Quarter'],
    'PeriodKey': spine['PeriodKey'],
    'ReportDate': spine['ReportDate'].dt.strftime('%Y-%m-%d'),
    'Stage': spine['Stage'],
    'Revenue': spine['Revenue_mini2'],
    'COGS': spine['COGS_mini2'],
    'AR_Reported': spine['AccountsReceivable'],
    'Inventory_Reported': spine['Inventory'],
    'AP_Reported': spine['AccountsPayable'],
    'OtherCurrentAssets': spine['OtherCurrentAssets'],
    'AccruedExpenses': spine['AccruedExpenses'],
    'DeferredRevenue': spine['DeferredRevenue'],
    'OtherCurrentLiabilities': spine['OtherCurrentLiabilities'],
})

#Calculating BS based on schedules, using the quarter_day_count to not have to input the numbers
working_capital['ShortTermInvestments'] = spine['CashAndSTInvestments'] - spine['Cash']
working_capital['DSO'] = quarter_day_count * working_capital['AR_Reported'] / working_capital['Revenue'].replace(0.0, np.nan)
working_capital['DIO'] = quarter_day_count * working_capital['Inventory_Reported'] / working_capital['COGS'].replace(0.0, np.nan)
working_capital['DPO'] = quarter_day_count * working_capital['AP_Reported'] / working_capital['COGS'].replace(0.0, np.nan)
working_capital['CCC'] = working_capital['DSO'] + working_capital['DIO'] - working_capital['DPO']
working_capital['AR'] = working_capital['Revenue'] * working_capital['DSO'] / quarter_day_count
working_capital['Inventory'] = working_capital['COGS'] * working_capital['DIO'] / quarter_day_count
working_capital['AP'] = working_capital['COGS'] * working_capital['DPO'] / quarter_day_count

#Current asset residual
working_capital['CurrentAssetResidual'] = (
    spine['TotalCurrentAssets']
    - spine['Cash']
    - working_capital['ShortTermInvestments']
    - working_capital['AR']
    - working_capital['Inventory']
    - working_capital['OtherCurrentAssets']
)

#Basic working capital calculation
working_capital['NWC'] = (
    working_capital['AR']
    + working_capital['Inventory']
    + working_capital['OtherCurrentAssets']
    + working_capital['CurrentAssetResidual']
    - working_capital['AP']
    - working_capital['AccruedExpenses']
    - working_capital['DeferredRevenue']
    - working_capital['OtherCurrentLiabilities']
)

#final WC calculations
working_capital['Delta_NWC'] = working_capital['NWC'].diff()
working_capital['WC_CashImpact'] = -working_capital['Delta_NWC']
working_capital['ReportedCF_WorkingCapital'] = spine['DeltaWC_CFSign']
working_capital['WC_CashImpact_Gap'] = working_capital['WC_CashImpact'] - working_capital['ReportedCF_WorkingCapital']
working_capital['AR_Error'] = working_capital['AR'] - working_capital['AR_Reported']
working_capital['Inventory_Error'] = working_capital['Inventory'] - working_capital['Inventory_Reported']
working_capital['AP_Error'] = working_capital['AP'] - working_capital['AP_Reported']
working_capital['CCC_OutlierFlag'] = robust_outlier_flags(working_capital['CCC'])

#Moving on to PPE
ppe = pd.DataFrame({
    'Quarter': spine['Quarter'],
    'PeriodKey': spine['PeriodKey'],
    'ReportDate': spine['ReportDate'].dt.strftime('%Y-%m-%d'),
    'CapEx': spine['CapEx'],
    'Depreciation': spine['DA_mini2'],
    'PPE_Reported': spine['PPENet'],
})

#Shifting the PPE so the open is correctly positioned
ppe['PPE_Open'] = ppe['PPE_Reported'].shift(1)
ppe.loc[ppe.index[0], 'PPE_Open'] = max(float(ppe.loc[ppe.index[0], 'PPE_Reported'] - ppe.loc[ppe.index[0], 'CapEx'] + ppe.loc[ppe.index[0], 'Depreciation']), 0.0)
ppe['OtherPPEAdjustments'] = ppe['PPE_Reported'] - ppe['PPE_Open'] - ppe['CapEx'] + ppe['Depreciation']
ppe.loc[ppe.index[0], 'OtherPPEAdjustments'] = 0.0
ppe['PPE_Net'] = ppe['PPE_Open'] + ppe['CapEx'] - ppe['Depreciation'] + ppe['OtherPPEAdjustments']
ppe['PPE_Error'] = ppe['PPE_Net'] - ppe['PPE_Reported']
ppe['PPE_Adjustment_OutlierFlag'] = robust_outlier_flags(ppe['OtherPPEAdjustments'])

#Equity dataframe definition
equity = pd.DataFrame({
    'Quarter': spine['Quarter'],
    'PeriodKey': spine['PeriodKey'],
    'ReportDate': spine['ReportDate'].dt.strftime('%Y-%m-%d'),
    'NetIncome': spine['NetIncome_mini2'],
    'Dividends': spine['Dividends'],
    'StockBasedCompensation': spine['SBC'],
    'TotalEquity_Reported': spine['TotalEquity'],
})

#Same process we did with the PPE shift
equity['Equity_Open'] = equity['TotalEquity_Reported'].shift(1)
equity.loc[equity.index[0], 'Equity_Open'] = (
    equity.loc[equity.index[0], 'TotalEquity_Reported']
    - equity.loc[equity.index[0], 'NetIncome']
    + equity.loc[equity.index[0], 'Dividends']
    - equity.loc[equity.index[0], 'StockBasedCompensation']
)
equity['RetainedEarningsLinkage'] = equity['NetIncome'] - equity['Dividends']
equity['CapitalActionsAndOCI'] = (
    equity['TotalEquity_Reported']
    - equity['Equity_Open']
    - equity['RetainedEarningsLinkage']
    - equity['StockBasedCompensation']
)
equity.loc[equity.index[0], 'CapitalActionsAndOCI'] = 0.0
equity['TotalEquity'] = (
    equity['Equity_Open']
    + equity['RetainedEarningsLinkage']
    + equity['StockBasedCompensation']
    + equity['CapitalActionsAndOCI']
)
equity['TotalEquity_Error'] = equity['TotalEquity'] - equity['TotalEquity_Reported']
equity['Equity_Adjustment_OutlierFlag'] = robust_outlier_flags(equity['CapitalActionsAndOCI'])

#Creating Debt DF
debt = pd.DataFrame({
    'Quarter': spine['Quarter'],
    'PeriodKey': spine['PeriodKey'],
    'ReportDate': spine['ReportDate'].dt.strftime('%Y-%m-%d'),
    'Debt_Reported': spine['TotalDebt'],
    'InterestExpense_Reported': spine['InterestExpense_mini2'].abs(),
})
debt['Debt_Open'] = debt['Debt_Reported'].shift(1)
debt.loc[debt.index[0], 'Debt_Open'] = debt.loc[debt.index[0], 'Debt_Reported']
debt['Debt_Change'] = debt['Debt_Reported'] - debt['Debt_Open']
debt['Borrowings'] = debt['Debt_Change'].clip(lower=0.0)
debt['Repayments'] = (-debt['Debt_Change']).clip(lower=0.0)
debt['Debt'] = debt['Debt_Open'] + debt['Borrowings'] - debt['Repayments']
debt['Debt_Error'] = debt['Debt'] - debt['Debt_Reported']
debt['AverageDebt'] = (debt['Debt_Open'] + debt['Debt']) / 2.0
fallback_rate = float((debt['InterestExpense_Reported'] / debt['AverageDebt'].replace(0.0, np.nan)).dropna().median())
debt['InterestRate_Implied'] = debt['InterestExpense_Reported'] / debt['AverageDebt'].replace(0.0, np.nan)
debt['InterestRate_Implied'] = debt['InterestRate_Implied'].fillna(fallback_rate).clip(lower=0.0)
debt['InterestExpense_Modeled'] = debt['AverageDebt'] * debt['InterestRate_Implied']
debt['DebtMovement_OutlierFlag'] = robust_outlier_flags(debt['Debt_Change'])

5) Balance sheet DF + CALC¶

Now we are going to tie all the df created in the cell above into the balance sheet

#creating the df
balance_sheet = pd.DataFrame({
    'Quarter': spine['Quarter'],
    'PeriodKey': spine['PeriodKey'],
    'ReportDate': spine['ReportDate'].dt.strftime('%Y-%m-%d'),
    'ReportedCash': spine['Cash'],
    'ShortTermInvestments': working_capital['ShortTermInvestments'],
    'AR': working_capital['AR'],
    'Inventory': working_capital['Inventory'],
    'OtherCurrentAssets': working_capital['OtherCurrentAssets'],
    'CurrentAssetResidual': working_capital['CurrentAssetResidual'],
    'PPE_Net': ppe['PPE_Net'],
    'AP': working_capital['AP'],
    'AccruedExpenses': working_capital['AccruedExpenses'],
    'DeferredRevenue': working_capital['DeferredRevenue'],
    'OtherCurrentLiabilities': working_capital['OtherCurrentLiabilities'],
    'Debt': debt['Debt'],
    'TotalEquity': equity['TotalEquity'],
    'ReportedTotalAssets': spine['TotalAssets'],
    'ReportedTotalLiabilities': spine['TotalLiabilities'],
    'ReportedTotalEquity': spine['TotalEquity'],
})

#quantify the other
balance_sheet['OtherAssetResidual'] = (
    balance_sheet['ReportedTotalAssets']
    - balance_sheet['ReportedCash']
    - balance_sheet['ShortTermInvestments']
    - balance_sheet['AR']
    - balance_sheet['Inventory']
    - balance_sheet['OtherCurrentAssets']
    - balance_sheet['CurrentAssetResidual']
    - balance_sheet['PPE_Net']
)
#quantify the other again
balance_sheet['OtherLiabilityResidual'] = (
    balance_sheet['ReportedTotalLiabilities']
    - balance_sheet['AP']
    - balance_sheet['AccruedExpenses']
    - balance_sheet['DeferredRevenue']
    - balance_sheet['OtherCurrentLiabilities']
    - balance_sheet['Debt']
)
#quantify the TA excluding cash
balance_sheet['TotalAssets_exCash'] = (
    balance_sheet['ShortTermInvestments']
    + balance_sheet['AR']
    + balance_sheet['Inventory']
    + balance_sheet['OtherCurrentAssets']
    + balance_sheet['CurrentAssetResidual']
    + balance_sheet['PPE_Net']
    + balance_sheet['OtherAssetResidual']
)

#quantify TL
balance_sheet['TotalLiabilities'] = (
    balance_sheet['AP']
    + balance_sheet['AccruedExpenses']
    + balance_sheet['DeferredRevenue']
    + balance_sheet['OtherCurrentLiabilities']
    + balance_sheet['Debt']
    + balance_sheet['OtherLiabilityResidual']
)


balance_sheet['Cash_Reconciled'] = balance_sheet['TotalLiabilities'] + balance_sheet['TotalEquity'] - balance_sheet['TotalAssets_exCash']
balance_sheet['TotalAssets'] = balance_sheet['TotalAssets_exCash'] + balance_sheet['Cash_Reconciled']
balance_sheet['CashPlugGap'] = balance_sheet['Cash_Reconciled'] - balance_sheet['ReportedCash']
balance_sheet['AssetsGap'] = balance_sheet['TotalAssets'] - balance_sheet['ReportedTotalAssets']
balance_sheet['LiabilitiesGap'] = balance_sheet['TotalLiabilities'] - balance_sheet['ReportedTotalLiabilities']
balance_sheet['EquityGap'] = balance_sheet['TotalEquity'] - balance_sheet['ReportedTotalEquity']
balance_sheet['BalanceError'] = balance_sheet['TotalAssets'] - balance_sheet['TotalLiabilities'] - balance_sheet['TotalEquity']

#Balance check (A = L + E)
balance_check = pd.DataFrame({
    'Quarter': balance_sheet['Quarter'],
    'PeriodKey': balance_sheet['PeriodKey'],
    'Cash_Reconciled': balance_sheet['Cash_Reconciled'],
    'ReportedCash': balance_sheet['ReportedCash'],
    'CashPlugGap': balance_sheet['CashPlugGap'],
    'BalanceError': balance_sheet['BalanceError'],
    'AssetsGap': balance_sheet['AssetsGap'],
    'LiabilitiesGap': balance_sheet['LiabilitiesGap'],
    'EquityGap': balance_sheet['EquityGap'],
    'CCC': working_capital['CCC'],
    'WC_CashImpact_Gap': working_capital['WC_CashImpact_Gap'],
    'PPE_OtherAdjustments': ppe['OtherPPEAdjustments'],
    'CapitalActionsAndOCI': equity['CapitalActionsAndOCI'],
    'Debt_Change': debt['Debt_Change'],
})

#Run all the checks and display them to ensure correctness
balance_check['NegativeCashFlag'] = balance_sheet['Cash_Reconciled'] < 0.0
balance_check['NegativeARFlag'] = balance_sheet['AR'] < 0.0
balance_check['NegativeInventoryFlag'] = balance_sheet['Inventory'] < 0.0
balance_check['NegativeAPFlag'] = balance_sheet['AP'] < 0.0
balance_check['NegativePPEFlag'] = balance_sheet['PPE_Net'] < 0.0
balance_check['NegativeDebtFlag'] = balance_sheet['Debt'] < 0.0
balance_check['NegativeTotalEquityFlag'] = balance_sheet['TotalEquity'] < 0.0
balance_check['CCC_OutlierFlag'] = working_capital['CCC_OutlierFlag'].astype('bool')
balance_check['PPE_Adjustment_OutlierFlag'] = ppe['PPE_Adjustment_OutlierFlag'].astype('bool')
balance_check['Equity_Adjustment_OutlierFlag'] = equity['Equity_Adjustment_OutlierFlag'].astype('bool')
balance_check['DebtMovement_OutlierFlag'] = debt['DebtMovement_OutlierFlag'].astype('bool')
balance_check['CashPlugMismatchFlag'] = balance_check['CashPlugGap'].abs().gt(1e-2)
balance_check['BalanceErrorFlag'] = balance_check['BalanceError'].abs().gt(1e-2)
flag_cols = [col for col in balance_check.columns if col.endswith('Flag')]
balance_check['NegativeBalanceCount'] = balance_check[[col for col in flag_cols if col.startswith('Negative')]].sum(axis=1)
balance_check['OutlierCount'] = balance_check[[col for col in flag_cols if 'Outlier' in col]].sum(axis=1)
balance_check['FlagCount'] = balance_check[flag_cols].sum(axis=1)

working_capital_q = working_capital.set_index('Quarter')
ppe_q = ppe.set_index('Quarter')
equity_q = equity.set_index('Quarter')
debt_q = debt.set_index('Quarter')
balance_sheet_q = balance_sheet.set_index('Quarter')
balance_check_q = balance_check.set_index('Quarter')

#Outputs fro control pupose, using the function we defined in section 2 
output_files = {
    'working_capital_csv': write_schedule_csv(working_capital, 'chapter4_working_capital_schedule_usdm.csv'),
    'ppe_csv': write_schedule_csv(ppe, 'chapter4_ppe_schedule_usdm.csv'),
    'equity_csv': write_schedule_csv(equity, 'chapter4_equity_schedule_usdm.csv'),
    'debt_csv': write_schedule_csv(debt, 'chapter4_debt_schedule_usdm.csv'),
    'balance_sheet_csv': write_schedule_csv(balance_sheet, 'chapter4_balance_sheet_usdm.csv'),
    'balance_check_csv': write_schedule_csv(balance_check, 'chapter4_balance_check_report.csv'),
}


print('Schedules built and Chapter 4 outputs exported.')
print(f"Max abs balance error: {balance_check['BalanceError'].abs().max():,.6f}")
print(f"Max abs cash plug gap: {balance_check['CashPlugGap'].abs().max():,.6f}")
Schedules built and Chapter 4 outputs exported.
Max abs balance error: 0.000000
Max abs cash plug gap: 0.000000

6) WC driver view¶

wc_driver_view = working_capital_q[
    [
        'Revenue', 'COGS', 'DSO', 'DIO', 'DPO', 'CCC', 'AR',
        'Inventory', 'AP', 'NWC', 'WC_CashImpact', 'ReportedCF_WorkingCapital',
        'WC_CashImpact_Gap'
    ]
]
display(wc_driver_view)

#median CCC to use in the executive summary

median_ccc = working_capital_q["CCC"].median()
display(median_ccc)
Revenue COGS DSO DIO DPO CCC AR Inventory AP NWC WC_CashImpact ReportedCF_WorkingCapital WC_CashImpact_Gap
Quarter
FY2016 Q4 1,871.00 857.00 25.19 119.79 16.08 128.90 516.50 1,125.00 151.00 1,118.80 NaN -153.00 NaN
FY2017 Q1 1,552.00 657.60 19.87 172.34 26.64 165.57 338.00 1,242.00 192.00 961.00 157.80 81.00 76.80
FY2017 Q2 1,821.00 866.40 24.55 123.54 16.75 131.35 490.00 1,173.00 159.00 1,122.00 -161.00 -244.00 83.00
FY2017 Q3 1,714.00 731.40 15.17 122.76 19.71 118.23 285.00 984.00 158.00 758.00 364.00 391.00 -27.00
FY2017 Q4 1,565.80 746.70 26.24 96.72 18.05 104.91 450.20 791.50 147.70 706.40 51.60 5.60 46.00
FY2018 Q1 1,347.10 495.90 18.91 158.23 29.61 147.53 279.20 859.90 160.90 602.00 104.40 182.50 -78.10
FY2018 Q2 1,664.20 668.40 25.79 118.04 23.59 120.23 470.30 864.60 172.80 746.10 -144.10 -136.20 -7.90
FY2018 Q3 1,641.80 645.60 16.41 116.66 26.05 107.02 295.20 825.40 184.30 486.90 259.20 479.20 -220.00
FY2018 Q4 1,529.20 620.70 25.15 111.92 24.35 112.72 421.40 761.30 165.60 549.70 -62.80 -236.50 173.70
FY2019 Q1 1,390.60 494.90 17.06 164.10 37.37 143.79 260.00 890.00 202.70 581.50 -31.80 15.40 -47.20
FY2019 Q2 1,690.90 661.60 23.33 137.18 27.87 132.63 432.30 994.60 202.10 898.60 -317.10 -293.00 -24.10
FY2019 Q3 1,725.80 666.30 16.07 125.24 23.16 118.16 304.00 914.50 169.10 718.20 180.40 234.80 -54.40
FY2019 Q4 1,505.70 604.20 24.13 123.51 30.55 117.08 398.10 817.80 202.30 666.00 52.20 -34.20 86.40
FY2020 Q1 1,428.80 508.00 18.57 177.58 63.14 133.01 290.70 988.60 351.50 720.50 -54.50 -0.70 -53.80
FY2020 Q2 1,706.20 657.20 25.84 140.58 48.64 117.79 483.20 1,012.50 350.30 820.60 -100.10 -274.00 173.90
FY2020 Q3 1,750.70 661.60 18.21 124.77 36.91 106.07 349.40 904.60 267.60 570.60 250.00 246.90 3.10
FY2020 Q4 1,274.10 679.70 19.85 98.84 33.13 85.55 277.10 736.20 246.80 402.50 168.10 101.70 66.40
FY2021 Q1 487.50 138.80 20.35 508.32 94.80 433.86 108.70 773.20 144.20 419.40 -16.90 59.90 -76.80
FY2021 Q2 1,193.50 394.10 27.07 205.38 65.87 166.57 354.00 887.00 284.50 385.80 33.60 15.70 17.90
FY2021 Q3 1,432.80 502.40 23.79 157.29 60.85 120.24 373.60 866.00 335.00 265.70 120.10 155.20 -35.10
FY2021 Q4 1,287.00 504.10 32.01 137.39 64.42 104.98 451.50 759.00 355.90 284.50 -18.80 -154.90 136.10
FY2022 Q1 1,376.30 408.20 24.35 179.50 82.78 121.07 367.20 803.00 370.30 292.40 -7.90 -15.20 7.30
FY2022 Q2 1,504.10 488.90 25.44 173.24 84.25 114.43 419.30 928.20 451.40 310.50 -18.10 -52.40 34.30
FY2022 Q3 1,815.40 617.30 20.64 137.34 69.89 88.09 410.70 929.10 472.80 196.30 114.20 61.80 52.40
FY2022 Q4 1,522.70 556.60 24.29 160.22 73.56 110.95 405.40 977.30 448.70 307.60 -111.30 -163.80 52.50
FY2023 Q1 1,490.60 489.20 21.45 219.77 104.85 136.37 350.40 1,178.20 562.10 489.50 -181.90 -180.90 -1.00
FY2023 Q2 1,579.90 556.80 28.28 206.72 81.61 153.39 489.60 1,261.40 498.00 776.20 -286.70 -289.90 3.20
FY2023 Q3 1,832.30 641.60 21.12 176.13 66.56 130.68 424.00 1,238.40 468.00 697.60 78.60 133.80 -55.20
FY2023 Q4 1,540.80 590.20 26.51 165.63 57.45 134.69 447.70 1,071.30 371.60 693.60 4.00 -87.60 91.60
FY2024 Q1 1,496.50 464.50 21.09 233.34 88.09 166.34 345.80 1,187.80 448.40 554.90 138.70 56.60 82.10
FY2024 Q2 1,633.00 562.90 25.77 193.77 74.59 144.95 461.10 1,195.30 460.10 776.70 -221.80 -182.20 -39.60
FY2024 Q3 1,934.00 648.00 19.06 148.58 57.99 109.64 403.90 1,055.10 411.80 559.00 217.70 274.00 -56.30
FY2024 Q4 1,567.90 524.20 25.99 157.05 57.83 125.21 446.50 902.20 332.20 562.20 -3.20 -33.40 30.20
FY2025 Q1 1,512.20 446.40 22.44 212.41 97.67 137.17 371.80 1,039.10 477.80 558.60 3.60 16.30 -12.70
FY2025 Q2 1,726.00 570.30 27.38 180.47 79.31 128.53 517.90 1,127.90 495.70 685.70 -127.10 -146.40 19.30
FY2025 Q3 2,143.50 677.40 18.53 134.52 65.94 87.11 435.20 998.60 489.50 451.20 234.50 335.10 -100.60
FY2025 Q4 1,697.30 532.00 24.70 162.88 74.78 112.80 459.50 949.60 436.00 494.60 -43.40 -3.10 -40.30
FY2026 Q1 1,719.10 476.80 21.05 233.90 116.57 138.39 396.60 1,222.20 609.10 598.20 -103.60 -117.30 13.70
FY2026 Q2 2,010.70 644.30 23.81 178.63 75.44 127.00 524.60 1,261.30 532.70 567.30 30.90 -262.50 293.40
FY2026 Q3 2,406.00 724.30 17.47 144.81 68.50 93.78 460.70 1,149.40 543.70 281.20 286.10 379.80 -93.70
123.14045795070761
wc_support_view = working_capital_q[
    [
        'AR_Reported', 'AR_Error', 'Inventory_Reported', 'Inventory_Error',
        'AP_Reported', 'AP_Error', 'OtherCurrentAssets', 'CurrentAssetResidual',
        'AccruedExpenses', 'DeferredRevenue', 'OtherCurrentLiabilities',
        'CCC_OutlierFlag'
    ]
]
display(wc_support_view.head(12))

wc_summary = working_capital[['DSO', 'DIO', 'DPO', 'CCC', 'WC_CashImpact_Gap']].describe().T
print(f'CCC outlier quarters: {int(working_capital["CCC_OutlierFlag"].sum())}')
display(wc_summary)
AR_Reported AR_Error Inventory_Reported Inventory_Error AP_Reported AP_Error OtherCurrentAssets CurrentAssetResidual AccruedExpenses DeferredRevenue OtherCurrentLiabilities CCC_OutlierFlag
Quarter
FY2016 Q4 516.50 0.00 1,125.00 0.00 151.00 0.00 90.20 235.60 581.20 50.10 66.20 False
FY2017 Q1 338.00 0.00 1,242.00 0.00 192.00 0.00 97.00 249.00 591.00 48.00 134.00 False
FY2017 Q2 490.00 0.00 1,173.00 0.00 159.00 -0.00 81.00 267.00 565.00 38.00 127.00 False
FY2017 Q3 285.00 0.00 984.00 0.00 158.00 0.00 130.00 254.00 609.00 34.00 94.00 False
FY2017 Q4 450.20 0.00 791.50 0.00 147.70 0.00 82.30 277.50 564.60 29.70 153.10 False
FY2018 Q1 279.20 0.00 859.90 0.00 160.90 0.00 90.30 286.40 587.40 30.60 134.90 False
FY2018 Q2 470.30 0.00 864.60 0.00 172.80 0.00 81.80 289.00 610.10 33.10 143.60 False
FY2018 Q3 295.20 0.00 825.40 0.00 184.30 0.00 73.70 300.90 659.60 33.90 130.50 False
FY2018 Q4 421.40 0.00 761.30 0.00 165.60 0.00 78.90 282.80 668.30 30.40 130.40 False
FY2019 Q1 260.00 0.00 890.00 0.00 202.70 0.00 104.30 275.80 631.20 25.50 89.20 False
FY2019 Q2 432.30 0.00 994.60 0.00 202.10 0.00 81.80 312.20 649.60 19.00 51.60 False
FY2019 Q3 304.00 0.00 914.50 0.00 169.10 -0.00 93.90 321.00 668.40 20.10 57.60 False
CCC outlier quarters: 1
count mean std min 25% 50% 75% max
DSO 40.00 22.57 3.85 15.17 19.65 23.56 25.52 32.01
DIO 40.00 165.98 65.89 96.72 125.12 157.76 178.85 508.32
DPO 40.00 56.63 27.47 16.08 29.17 61.99 74.95 116.57
CCC 40.00 131.92 52.95 85.55 112.28 123.14 136.57 433.86
WC_CashImpact_Gap 39.00 13.32 90.04 -220.00 -43.75 3.20 59.45 293.40

7) Roll foward section - PPE¶

PP&E is reconstructed as opening (reason for the shift 1) net PP&E plus CapEx minus depreciation plus any residual adjustment required to tie back to the reported closing balance. We can use the tidy DB from module 1

ppe_view = ppe_q[
    [
        'PPE_Open', 'CapEx', 'Depreciation', 'OtherPPEAdjustments',
        'PPE_Net', 'PPE_Reported', 'PPE_Error', 'PPE_Adjustment_OutlierFlag'
    ]
]
display(ppe_view)

#For usage in the executive summary
largest_ppe_residual_period = ppe.loc[ppe['OtherPPEAdjustments'].abs().idxmax(), 'Quarter']
print(f'Largest PP&E residual quarter: {largest_ppe_residual_period}')
print(f"Largest abs PP&E residual: {ppe['OtherPPEAdjustments'].abs().max():,.2f} USDm")
PPE_Open CapEx Depreciation OtherPPEAdjustments PPE_Net PPE_Reported PPE_Error PPE_Adjustment_OutlierFlag
Quarter
FY2016 Q4 1,497.00 93.00 7.00 0.00 1,583.00 1,583.00 0.00 False
FY2017 Q1 1,583.00 78.00 6.00 -90.00 1,565.00 1,565.00 0.00 False
FY2017 Q2 1,565.00 87.00 76.00 -12.00 1,564.00 1,564.00 0.00 False
FY2017 Q3 1,564.00 60.00 6.00 -104.00 1,514.00 1,514.00 0.00 False
FY2017 Q4 1,514.00 59.00 75.50 -181.50 1,316.00 1,316.00 0.00 True
FY2018 Q1 1,316.00 41.90 72.90 -11.70 1,273.30 1,273.30 0.00 False
FY2018 Q2 1,273.30 32.80 73.80 8.20 1,240.50 1,240.50 0.00 False
FY2018 Q3 1,240.50 48.30 72.70 -0.20 1,215.90 1,215.90 0.00 False
FY2018 Q4 1,215.90 38.60 75.80 7.60 1,186.30 1,186.30 0.00 False
FY2019 Q1 1,186.30 42.30 70.30 -16.60 1,141.70 1,141.70 0.00 False
FY2019 Q2 1,141.70 50.80 69.70 7.70 1,130.50 1,130.50 0.00 False
FY2019 Q3 1,130.50 56.10 72.00 -35.30 1,079.30 1,079.30 0.00 False
FY2019 Q4 1,079.30 48.50 69.30 -19.30 1,039.20 1,039.20 0.00 False
FY2020 Q1 1,039.20 49.40 66.20 1,380.40 2,402.80 2,402.80 0.00 True
FY2020 Q2 2,402.80 81.20 66.60 160.70 2,578.10 2,578.10 0.00 True
FY2020 Q3 2,578.10 85.40 68.20 6.30 2,601.60 2,601.60 0.00 False
FY2020 Q4 2,601.60 54.30 68.50 -96.30 2,491.10 2,491.10 0.00 False
FY2021 Q1 2,491.10 21.30 63.70 -38.80 2,409.90 2,409.90 0.00 False
FY2021 Q2 2,409.90 32.60 61.50 -53.20 2,327.80 2,327.80 0.00 False
FY2021 Q3 2,327.80 26.90 60.30 131.60 2,426.00 2,426.00 0.00 True
FY2021 Q4 2,426.00 27.00 62.10 -137.40 2,253.50 2,253.50 0.00 False
FY2022 Q1 2,253.50 28.20 57.20 -68.60 2,155.90 2,155.90 0.00 False
FY2022 Q2 2,155.90 35.20 55.90 -14.90 2,120.30 2,120.30 -0.00 False
FY2022 Q3 2,120.30 50.20 56.20 -17.30 2,097.00 2,097.00 0.00 False
FY2022 Q4 2,097.00 53.30 60.40 -9.10 2,080.80 2,080.80 0.00 False
FY2023 Q1 2,080.80 39.40 54.80 -79.50 1,985.90 1,985.90 0.00 False
FY2023 Q2 1,985.90 44.50 53.30 -61.30 1,915.80 1,915.80 0.00 False
FY2023 Q3 1,915.80 72.00 55.20 87.90 2,020.50 2,020.50 0.00 False
FY2023 Q4 2,020.50 61.60 57.20 64.60 2,089.50 2,089.50 0.00 False
FY2024 Q1 2,089.50 39.60 58.30 -34.20 2,036.60 2,036.60 -0.00 False
FY2024 Q2 2,036.60 42.80 58.50 -57.10 1,963.80 1,963.80 0.00 False
FY2024 Q3 1,963.80 42.50 56.20 0.90 1,951.00 1,951.00 0.00 False
FY2024 Q4 1,951.00 39.90 56.00 -69.90 1,865.00 1,865.00 0.00 False
FY2025 Q1 1,865.00 33.40 54.40 1.30 1,845.30 1,845.30 0.00 False
FY2025 Q2 1,845.30 41.70 55.90 14.90 1,846.00 1,846.00 0.00 False
FY2025 Q3 1,846.00 61.20 54.30 -3.60 1,849.30 1,849.30 0.00 False
FY2025 Q4 1,849.30 79.90 55.00 -14.70 1,859.50 1,859.50 0.00 False
FY2026 Q1 1,859.50 187.30 55.50 114.20 2,105.50 2,105.50 0.00 False
FY2026 Q2 2,105.50 93.80 57.00 47.30 2,189.60 2,189.60 0.00 False
FY2026 Q3 2,189.60 75.60 59.70 6.00 2,211.50 2,211.50 0.00 False
Largest PP&E residual quarter: FY2020 Q1
Largest abs PP&E residual: 1,380.40 USDm

7) Roll foward section - Equity¶

Same logic here, rolling foward Equity

equity_view = equity_q[
    [
        'PeriodKey', 'Equity_Open', 'NetIncome', 'Dividends',
        'StockBasedCompensation', 'RetainedEarningsLinkage',
        'CapitalActionsAndOCI', 'TotalEquity', 'TotalEquity_Reported',
        'TotalEquity_Error', 'Equity_Adjustment_OutlierFlag'
    ]
]
display(equity_view.head(12))

print(f'Equity residual outlier quarters: {int(equity["Equity_Adjustment_OutlierFlag"].sum())}')
print(f"Largest abs capital-actions / OCI residual: {equity['CapitalActionsAndOCI'].abs().max():,.2f} USDm")
PeriodKey Equity_Open NetIncome Dividends StockBasedCompensation RetainedEarningsLinkage CapitalActionsAndOCI TotalEquity TotalEquity_Reported TotalEquity_Error Equity_Adjustment_OutlierFlag
Quarter
FY2016 Q4 2016-Q4 3,727.00 41.00 42.00 18.00 -1.00 0.00 3,744.00 3,744.00 0.00 False
FY2017 Q1 2017-Q1 3,744.00 -33.20 41.40 18.00 -74.60 -121.40 3,566.00 3,566.00 0.00 False
FY2017 Q2 2017-Q2 3,566.00 45.70 41.00 14.00 4.70 13.30 3,598.00 3,598.00 0.00 False
FY2017 Q3 2017-Q3 3,598.00 81.30 42.00 14.00 39.30 -41.30 3,610.00 3,610.00 0.00 False
FY2017 Q4 2017-Q4 3,610.00 -268.90 40.80 17.60 -309.70 -18.30 3,299.60 3,299.60 0.00 False
FY2018 Q1 2018-Q1 3,299.60 59.50 40.50 21.60 19.00 19.90 3,360.10 3,360.10 0.00 False
FY2018 Q2 2018-Q2 3,360.10 143.80 40.60 17.80 103.20 28.50 3,509.60 3,509.60 0.00 False
FY2018 Q3 2018-Q3 3,509.60 111.78 40.60 16.90 71.18 -190.18 3,407.50 3,407.50 0.00 False
FY2018 Q4 2018-Q4 3,407.50 24.90 40.70 18.20 -15.80 47.50 3,457.40 3,457.40 -0.00 False
FY2019 Q1 2019-Q1 3,457.40 109.00 40.60 21.50 68.40 -126.30 3,421.00 3,421.00 0.00 False
FY2019 Q2 2019-Q2 3,421.00 170.30 50.70 21.30 119.60 -99.90 3,462.00 3,462.00 0.00 False
FY2019 Q3 2019-Q3 3,462.00 120.00 50.30 22.50 69.70 -200.30 3,353.90 3,353.90 0.00 False
Equity residual outlier quarters: 0
Largest abs capital-actions / OCI residual: 365.70 USDm

8) Roll foward section - Debt¶

Same logic a

debt_view = debt_q[
    [
        'Debt_Open', 'Borrowings', 'Repayments', 'Debt', 'Debt_Reported',
        'Debt_Error', 'Debt_Change', 'AverageDebt', 'InterestRate_Implied',
        'InterestExpense_Reported', 'InterestExpense_Modeled', 'DebtMovement_OutlierFlag'
    ]
]
display(debt_view)

print(f'Debt movement outlier quarters: {int(debt["DebtMovement_OutlierFlag"].sum())}')
print(f"Largest abs debt change: {debt['Debt_Change'].abs().max():,.2f} USDm")
Debt_Open Borrowings Repayments Debt Debt_Reported Debt_Error Debt_Change AverageDebt InterestRate_Implied InterestExpense_Reported InterestExpense_Modeled DebtMovement_OutlierFlag
Quarter
FY2016 Q4 999.50 0.00 -0.00 999.50 999.50 0.00 0.00 999.50 0.01 7.00 7.00 False
FY2017 Q1 999.50 0.00 21.50 978.00 978.00 0.00 -21.50 988.75 0.00 3.40 3.40 False
FY2017 Q2 978.00 0.00 5.00 973.00 973.00 0.00 -5.00 975.50 0.00 4.10 4.10 False
FY2017 Q3 973.00 0.00 110.00 863.00 863.00 0.00 -110.00 918.00 0.00 3.60 3.60 False
FY2017 Q4 863.00 0.00 1.30 861.70 861.70 0.00 -1.30 862.35 0.00 1.40 1.40 False
FY2018 Q1 861.70 0.00 2.50 859.20 859.20 0.00 -2.50 860.45 0.01 5.00 5.00 False
FY2018 Q2 859.20 0.00 4.10 855.10 855.10 0.00 -4.10 857.15 0.01 4.60 4.60 False
FY2018 Q3 855.10 0.00 6.10 849.00 849.00 0.00 -6.10 852.05 0.01 4.80 4.80 False
FY2018 Q4 849.00 3.10 0.00 852.10 852.10 0.00 3.10 850.55 0.00 3.80 3.80 False
FY2019 Q1 852.10 0.00 16.00 836.10 836.10 0.00 -16.00 844.10 0.01 4.40 4.40 False
FY2019 Q2 836.10 91.90 0.00 928.00 928.00 0.00 91.90 882.05 0.01 6.00 6.00 False
FY2019 Q3 928.00 0.00 1.40 926.60 926.60 0.00 -1.40 927.30 0.01 5.20 5.20 False
FY2019 Q4 926.60 0.00 2.60 924.00 924.00 0.00 -2.60 925.30 0.01 5.10 5.10 False
FY2020 Q1 924.00 1,756.20 0.00 2,680.20 2,680.20 0.00 1,756.20 1,802.10 0.00 4.20 4.20 True
FY2020 Q2 2,680.20 144.50 0.00 2,824.70 2,824.70 0.00 144.50 2,752.45 0.00 4.40 4.40 False
FY2020 Q3 2,824.70 0.00 3.90 2,820.80 2,820.80 0.00 -3.90 2,822.75 0.00 4.20 4.20 False
FY2020 Q4 2,820.80 406.10 0.00 3,226.90 3,226.90 0.00 406.10 3,023.85 0.00 4.80 4.80 True
FY2021 Q1 3,226.90 734.60 0.00 3,961.50 3,961.50 0.00 734.60 3,594.20 0.00 9.60 9.60 True
FY2021 Q2 3,961.50 0.00 353.60 3,607.90 3,607.90 0.00 -353.60 3,784.70 0.00 12.80 12.80 True
FY2021 Q3 3,607.90 99.50 0.00 3,707.40 3,707.40 0.00 99.50 3,657.65 0.00 12.20 12.20 False
FY2021 Q4 3,707.40 0.00 86.90 3,620.50 3,620.50 0.00 -86.90 3,663.95 0.00 13.90 13.90 False
FY2022 Q1 3,620.50 0.00 87.90 3,532.60 3,532.60 0.00 -87.90 3,576.55 0.00 13.30 13.30 False
FY2022 Q2 3,532.60 0.00 45.70 3,486.90 3,486.90 0.00 -45.70 3,509.75 0.00 13.60 13.60 False
FY2022 Q3 3,486.90 0.00 48.20 3,438.70 3,438.70 0.00 -48.20 3,462.80 0.00 13.40 13.40 False
FY2022 Q4 3,438.70 0.00 46.80 3,391.90 3,391.90 0.00 -46.80 3,415.30 0.00 13.70 13.70 False
FY2023 Q1 3,391.90 0.00 581.30 2,810.60 2,810.60 0.00 -581.30 3,101.25 0.00 11.80 11.80 True
FY2023 Q2 2,810.60 0.00 48.80 2,761.80 2,761.80 0.00 -48.80 2,786.20 0.00 9.50 9.50 False
FY2023 Q3 2,761.80 60.60 0.00 2,822.40 2,822.40 0.00 60.60 2,792.10 0.00 12.00 12.00 False
FY2023 Q4 2,822.40 59.50 0.00 2,881.90 2,881.90 0.00 59.50 2,852.15 0.00 7.10 7.10 False
FY2024 Q1 2,881.90 0.00 42.40 2,839.50 2,839.50 0.00 -42.40 2,860.70 0.00 10.00 10.00 False
FY2024 Q2 2,839.50 0.00 64.60 2,774.90 2,774.90 0.00 -64.60 2,807.20 0.00 10.00 10.00 False
FY2024 Q3 2,774.90 0.00 18.80 2,756.10 2,756.10 0.00 -18.80 2,765.50 0.00 10.60 10.60 False
FY2024 Q4 2,756.10 0.00 80.80 2,675.30 2,675.30 0.00 -80.80 2,715.70 0.00 11.60 11.60 False
FY2025 Q1 2,675.30 6.90 0.00 2,682.20 2,682.20 0.00 6.90 2,678.75 0.00 10.90 10.90 False
FY2025 Q2 2,682.20 0.00 14.30 2,667.90 2,667.90 0.00 -14.30 2,675.05 0.00 11.40 11.40 False
FY2025 Q3 2,667.90 15.80 0.00 2,683.70 2,683.70 0.00 15.80 2,675.80 0.00 11.60 11.60 False
FY2025 Q4 2,683.70 0.00 15.60 2,668.10 2,668.10 0.00 -15.60 2,675.90 0.00 10.20 10.20 False
FY2026 Q1 2,668.10 1,680.70 0.00 4,348.80 4,348.80 0.00 1,680.70 3,508.45 0.00 11.50 11.50 True
FY2026 Q2 4,348.80 0.00 325.50 4,023.30 4,023.30 0.00 -325.50 4,186.05 0.00 15.90 15.90 True
FY2026 Q3 4,023.30 0.00 9.30 4,014.00 4,014.00 0.00 -9.30 4,018.65 0.00 13.30 13.30 False
Debt movement outlier quarters: 7
Largest abs debt change: 1,756.20 USDm

9) Assemble the BS¶

balance_view = balance_sheet_q[
    [
        'ReportedCash', 'Cash_Reconciled', 'CashPlugGap', 'ShortTermInvestments',
        'AR', 'Inventory', 'OtherCurrentAssets', 'CurrentAssetResidual', 'OtherAssetResidual',
        'PPE_Net', 'TotalAssets', 'AP', 'AccruedExpenses', 'DeferredRevenue',
        'OtherCurrentLiabilities', 'Debt', 'OtherLiabilityResidual', 'TotalLiabilities',
        'TotalEquity', 'BalanceError'
    ]
]
display(balance_view.tail(8))
print(f"Max abs balance error: {balance_sheet['BalanceError'].abs().max():,.6f}")
print(f"Max abs cash plug gap: {balance_sheet['CashPlugGap'].abs().max():,.6f}")
ReportedCash Cash_Reconciled CashPlugGap ShortTermInvestments AR Inventory OtherCurrentAssets CurrentAssetResidual OtherAssetResidual PPE_Net TotalAssets AP AccruedExpenses DeferredRevenue OtherCurrentLiabilities Debt OtherLiabilityResidual TotalLiabilities TotalEquity BalanceError
Quarter
FY2024 Q4 1,662.20 1,662.20 -0.00 121.00 446.50 902.20 55.70 172.20 1,377.80 1,865.00 6,602.60 332.20 632.30 17.30 32.60 2,675.30 462.60 4,152.30 2,450.30 0.00
FY2025 Q1 1,586.90 1,586.90 0.00 173.60 371.80 1,039.10 64.90 211.60 1,347.80 1,845.30 6,641.00 477.80 604.40 18.30 28.30 2,682.20 463.00 4,274.00 2,367.00 0.00
FY2025 Q2 1,355.00 1,355.00 0.00 334.70 517.90 1,127.90 67.60 201.50 1,349.40 1,846.00 6,800.00 495.70 687.80 15.70 30.00 2,667.90 460.60 4,357.70 2,442.30 0.00
FY2025 Q3 1,940.20 1,940.20 -0.00 203.00 435.20 998.60 112.20 211.10 1,331.30 1,849.30 7,080.90 489.50 777.80 22.50 16.10 2,683.70 552.10 4,541.70 2,539.20 -0.00
FY2025 Q4 1,922.50 1,922.50 0.00 160.50 459.50 949.60 82.10 215.70 1,397.90 1,859.50 7,047.30 436.00 736.50 16.60 23.20 2,668.10 578.40 4,458.80 2,588.50 0.00
FY2026 Q1 2,090.20 2,090.20 -0.00 186.60 396.60 1,222.20 56.70 246.20 1,447.70 2,105.50 7,751.70 609.10 657.50 15.00 41.90 4,348.80 -435.90 5,236.40 2,515.30 0.00
FY2026 Q2 1,444.40 1,444.40 0.00 202.50 524.60 1,261.30 64.10 256.10 1,405.10 2,189.60 7,347.70 532.70 246.10 21.80 738.20 4,023.30 -796.60 4,765.50 2,582.20 0.00
FY2026 Q3 2,034.00 2,034.00 0.00 218.90 460.70 1,149.40 68.30 258.00 1,413.00 2,211.50 7,813.80 543.70 292.60 27.60 791.30 4,014.00 -743.80 4,925.40 2,888.40 0.00
Max abs balance error: 0.000000
Max abs cash plug gap: 0.000000

11) Balance check report¶

Balance sheet output and checked.

display(balance_check_q)

flagged_periods = balance_check_q[balance_check_q['FlagCount'] > 0]
if flagged_periods.empty:
    print('No balance-check flags triggered.')
else:
    display(flagged_periods)
PeriodKey Cash_Reconciled ReportedCash CashPlugGap BalanceError AssetsGap LiabilitiesGap EquityGap CCC WC_CashImpact_Gap PPE_OtherAdjustments CapitalActionsAndOCI Debt_Change NegativeCashFlag NegativeARFlag NegativeInventoryFlag NegativeAPFlag NegativePPEFlag NegativeDebtFlag NegativeTotalEquityFlag CCC_OutlierFlag PPE_Adjustment_OutlierFlag Equity_Adjustment_OutlierFlag DebtMovement_OutlierFlag CashPlugMismatchFlag BalanceErrorFlag NegativeBalanceCount OutlierCount FlagCount
Quarter
FY2016 Q4 2016-Q4 456.30 456.30 0.00 0.00 0.00 0.00 0.00 128.90 NaN 0.00 0.00 0.00 False False False False False False False False False False False False False 0 0 0
FY2017 Q1 2017-Q1 457.00 457.00 0.00 0.00 0.00 0.00 0.00 165.57 76.80 -90.00 -121.40 -21.50 False False False False False False False False False False False False False 0 0 0
FY2017 Q2 2017-Q2 434.00 434.00 0.00 0.00 0.00 0.00 0.00 131.35 83.00 -12.00 13.30 -5.00 False False False False False False False False False False False False False 0 0 0
FY2017 Q3 2017-Q3 928.00 928.00 0.00 0.00 0.00 0.00 0.00 118.23 -27.00 -104.00 -41.30 -110.00 False False False False False False False False False False False False False 0 0 0
FY2017 Q4 2017-Q4 668.30 668.30 -0.00 -0.00 0.00 0.00 0.00 104.91 46.00 -181.50 -18.30 -1.30 False False False False False False False False True False False False False 0 1 1
FY2018 Q1 2018-Q1 830.40 830.40 -0.00 0.00 0.00 -0.00 0.00 147.53 -78.10 -11.70 19.90 -2.50 False False False False False False False False False False False False False 0 0 0
FY2018 Q2 2018-Q2 1,111.60 1,111.60 -0.00 0.00 0.00 0.00 0.00 120.23 -7.90 8.20 28.50 -4.10 False False False False False False False False False False False False False 0 0 0
FY2018 Q3 2018-Q3 1,175.70 1,175.70 -0.00 0.00 0.00 0.00 0.00 107.02 -220.00 -0.20 -190.18 -6.10 False False False False False False False False False False False False False 0 0 0
FY2018 Q4 2018-Q4 1,304.60 1,304.60 -0.00 -0.00 -0.00 0.00 -0.00 112.72 173.70 7.60 47.50 3.10 False False False False False False False False False False False False False 0 0 0
FY2019 Q1 2019-Q1 532.30 532.30 0.00 0.00 0.00 0.00 0.00 143.79 -47.20 -16.60 -126.30 -16.00 False False False False False False False False False False False False False 0 0 0
FY2019 Q2 2019-Q2 553.10 553.10 0.00 -0.00 0.00 0.00 0.00 132.63 -24.10 7.70 -99.90 91.90 False False False False False False False False False False False False False 0 0 0
FY2019 Q3 2019-Q3 680.50 680.50 0.00 -0.00 0.00 0.00 0.00 118.16 -54.40 -35.30 -200.30 -1.40 False False False False False False False False False False False False False 0 0 0
FY2019 Q4 2019-Q4 584.10 584.10 -0.00 -0.00 -0.00 0.00 0.00 117.08 86.40 -19.30 -72.50 -2.60 False False False False False False False False False False False False False 0 0 0
FY2020 Q1 2020-Q1 648.40 648.40 0.00 0.00 0.00 0.00 0.00 133.01 -53.80 1,380.40 -365.70 1,756.20 False False False False False False False False True False True False False 0 2 2
FY2020 Q2 2020-Q2 548.10 548.10 -0.00 0.00 0.00 0.00 0.00 117.79 173.90 160.70 -256.00 144.50 False False False False False False False False True False False False False 0 1 1
FY2020 Q3 2020-Q3 1,079.90 1,079.90 -0.00 0.00 0.00 0.00 0.00 106.07 3.10 6.30 1.70 -3.90 False False False False False False False False False False False False False 0 0 0
FY2020 Q4 2020-Q4 1,620.40 1,620.40 -0.00 0.00 0.00 0.00 -0.00 85.55 66.40 -96.30 -113.20 406.10 False False False False False False False False False False True False False 0 1 1
FY2021 Q1 2021-Q1 2,451.30 2,451.30 0.00 0.00 0.00 0.00 0.00 433.86 -76.80 -38.80 69.70 734.60 False False False False False False False True False False True False False 0 2 2
FY2021 Q2 2021-Q2 2,012.00 2,012.00 0.00 0.00 0.00 0.00 0.00 166.57 17.90 -53.20 -1.80 -353.60 False False False False False False False False False False True False False 0 1 1
FY2021 Q3 2021-Q3 2,621.50 2,621.50 0.00 -0.00 -0.00 -0.00 0.00 120.24 -35.10 131.60 8.30 99.50 False False False False False False False False True False False False False 0 1 1
FY2021 Q4 2021-Q4 2,579.00 2,579.00 0.00 -0.00 0.00 0.00 0.00 104.98 136.10 -137.40 -70.60 -86.90 False False False False False False False False False False False False False 0 0 0
FY2022 Q1 2022-Q1 2,596.40 2,596.40 -0.00 0.00 -0.00 -0.00 0.00 121.07 7.30 -68.60 -69.80 -87.90 False False False False False False False False False False False False False 0 0 0
FY2022 Q2 2022-Q2 2,387.90 2,387.90 0.00 0.00 0.00 0.00 0.00 114.43 34.30 -14.90 -19.90 -45.70 False False False False False False False False False False False False False 0 0 0
FY2022 Q3 2022-Q3 2,276.80 2,276.80 -0.00 -0.00 -0.00 -0.00 0.00 88.09 52.40 -17.30 -329.00 -48.20 False False False False False False False False False False False False False 0 0 0
FY2022 Q4 2022-Q4 1,863.80 1,863.80 -0.00 0.00 -0.00 -0.00 0.00 110.95 52.50 -9.10 -181.50 -46.80 False False False False False False False False False False False False False 0 0 0
FY2023 Q1 2023-Q1 1,456.80 1,456.80 -0.00 0.00 0.00 0.00 0.00 136.37 -1.00 -79.50 -265.40 -581.30 False False False False False False False False False False True False False 0 1 1
FY2023 Q2 2023-Q2 1,107.10 1,107.10 0.00 0.00 0.00 0.00 0.00 153.39 3.20 -61.30 -230.50 -48.80 False False False False False False False False False False False False False 0 0 0
FY2023 Q3 2023-Q3 1,566.10 1,566.10 0.00 0.00 0.00 0.00 0.00 130.68 -55.20 87.90 26.20 60.60 False False False False False False False False False False False False False 0 0 0
FY2023 Q4 2023-Q4 1,529.30 1,529.30 0.00 0.00 0.00 0.00 0.00 134.69 91.60 64.60 -35.70 59.50 False False False False False False False False False False False False False 0 0 0
FY2024 Q1 2024-Q1 1,607.20 1,607.20 -0.00 0.00 0.00 0.00 0.00 166.34 82.10 -34.20 -93.80 -42.40 False False False False False False False False False False False False False 0 0 0
FY2024 Q2 2024-Q2 1,381.80 1,381.80 0.00 0.00 0.00 0.00 0.00 144.95 -39.60 -57.10 -199.00 -64.60 False False False False False False False False False False False False False 0 0 0
FY2024 Q3 2024-Q3 1,803.60 1,803.60 0.00 -0.00 0.00 0.00 0.00 109.64 -56.30 0.90 -50.00 -18.80 False False False False False False False False False False False False False 0 0 0
FY2024 Q4 2024-Q4 1,662.20 1,662.20 -0.00 0.00 0.00 0.00 0.00 125.21 30.20 -69.90 -188.60 -80.80 False False False False False False False False False False False False False 0 0 0
FY2025 Q1 2025-Q1 1,586.90 1,586.90 0.00 0.00 0.00 0.00 0.00 137.17 -12.70 1.30 -228.70 6.90 False False False False False False False False False False False False False 0 0 0
FY2025 Q2 2025-Q2 1,355.00 1,355.00 0.00 0.00 0.00 0.00 0.00 128.53 19.30 14.90 -56.90 -14.30 False False False False False False False False False False False False False 0 0 0
FY2025 Q3 2025-Q3 1,940.20 1,940.20 -0.00 -0.00 -0.00 -0.00 -0.00 87.11 -100.60 -3.60 -174.00 15.80 False False False False False False False False False False False False False 0 0 0
FY2025 Q4 2025-Q4 1,922.50 1,922.50 0.00 0.00 0.00 0.00 0.00 112.80 -40.30 -14.70 -51.90 -15.60 False False False False False False False False False False False False False 0 0 0
FY2026 Q1 2026-Q1 2,090.20 2,090.20 -0.00 0.00 0.00 0.00 0.00 138.39 13.70 114.20 -265.30 1,680.70 False False False False False False False False False False True False False 0 1 1
FY2026 Q2 2026-Q2 1,444.40 1,444.40 0.00 0.00 0.00 0.00 0.00 127.00 293.40 47.30 -110.10 -325.50 False False False False False False False False False False True False False 0 1 1
FY2026 Q3 2026-Q3 2,034.00 2,034.00 0.00 0.00 0.00 0.00 0.00 93.78 -93.70 6.00 -25.00 -9.30 False False False False False False False False False False False False False 0 0 0
PeriodKey Cash_Reconciled ReportedCash CashPlugGap BalanceError AssetsGap LiabilitiesGap EquityGap CCC WC_CashImpact_Gap PPE_OtherAdjustments CapitalActionsAndOCI Debt_Change NegativeCashFlag NegativeARFlag NegativeInventoryFlag NegativeAPFlag NegativePPEFlag NegativeDebtFlag NegativeTotalEquityFlag CCC_OutlierFlag PPE_Adjustment_OutlierFlag Equity_Adjustment_OutlierFlag DebtMovement_OutlierFlag CashPlugMismatchFlag BalanceErrorFlag NegativeBalanceCount OutlierCount FlagCount
Quarter
FY2017 Q4 2017-Q4 668.30 668.30 -0.00 -0.00 0.00 0.00 0.00 104.91 46.00 -181.50 -18.30 -1.30 False False False False False False False False True False False False False 0 1 1
FY2020 Q1 2020-Q1 648.40 648.40 0.00 0.00 0.00 0.00 0.00 133.01 -53.80 1,380.40 -365.70 1,756.20 False False False False False False False False True False True False False 0 2 2
FY2020 Q2 2020-Q2 548.10 548.10 -0.00 0.00 0.00 0.00 0.00 117.79 173.90 160.70 -256.00 144.50 False False False False False False False False True False False False False 0 1 1
FY2020 Q4 2020-Q4 1,620.40 1,620.40 -0.00 0.00 0.00 0.00 -0.00 85.55 66.40 -96.30 -113.20 406.10 False False False False False False False False False False True False False 0 1 1
FY2021 Q1 2021-Q1 2,451.30 2,451.30 0.00 0.00 0.00 0.00 0.00 433.86 -76.80 -38.80 69.70 734.60 False False False False False False False True False False True False False 0 2 2
FY2021 Q2 2021-Q2 2,012.00 2,012.00 0.00 0.00 0.00 0.00 0.00 166.57 17.90 -53.20 -1.80 -353.60 False False False False False False False False False False True False False 0 1 1
FY2021 Q3 2021-Q3 2,621.50 2,621.50 0.00 -0.00 -0.00 -0.00 0.00 120.24 -35.10 131.60 8.30 99.50 False False False False False False False False True False False False False 0 1 1
FY2023 Q1 2023-Q1 1,456.80 1,456.80 -0.00 0.00 0.00 0.00 0.00 136.37 -1.00 -79.50 -265.40 -581.30 False False False False False False False False False False True False False 0 1 1
FY2026 Q1 2026-Q1 2,090.20 2,090.20 -0.00 0.00 0.00 0.00 0.00 138.39 13.70 114.20 -265.30 1,680.70 False False False False False False False False False False True False False 0 1 1
FY2026 Q2 2026-Q2 1,444.40 1,444.40 0.00 0.00 0.00 0.00 0.00 127.00 293.40 47.30 -110.10 -325.50 False False False False False False False False False False True False False 0 1 1
#asseting that max error can be 0.01
assert balance_check['BalanceError'].abs().max() < 1e-2
assert balance_check['CashPlugGap'].abs().max() < 1e-2
print('Verification passed: the reconstructed balance sheet balances and the reconciled cash line matches the reported cash balance.')
Verification passed: the reconstructed balance sheet balances and the reconciled cash line matches the reported cash balance.
for name, output_path in output_files.items():
    print(f'{name}: {output_path}')
working_capital_csv: /Users/theomachado/Library/CloudStorage/OneDrive-UniversityofFlorida/Spring 2026/FIN4453 - OFC/Mini-projects/outputs/chapter4_working_capital_schedule_usdm.csv
ppe_csv: /Users/theomachado/Library/CloudStorage/OneDrive-UniversityofFlorida/Spring 2026/FIN4453 - OFC/Mini-projects/outputs/chapter4_ppe_schedule_usdm.csv
equity_csv: /Users/theomachado/Library/CloudStorage/OneDrive-UniversityofFlorida/Spring 2026/FIN4453 - OFC/Mini-projects/outputs/chapter4_equity_schedule_usdm.csv
debt_csv: /Users/theomachado/Library/CloudStorage/OneDrive-UniversityofFlorida/Spring 2026/FIN4453 - OFC/Mini-projects/outputs/chapter4_debt_schedule_usdm.csv
balance_sheet_csv: /Users/theomachado/Library/CloudStorage/OneDrive-UniversityofFlorida/Spring 2026/FIN4453 - OFC/Mini-projects/outputs/chapter4_balance_sheet_usdm.csv
balance_check_csv: /Users/theomachado/Library/CloudStorage/OneDrive-UniversityofFlorida/Spring 2026/FIN4453 - OFC/Mini-projects/outputs/chapter4_balance_check_report.csv