Session 1 of 4

Your First Day at Jolly Associates

Python Fundamentals through Financial Statements
⏱️ 2.5 Hoursβ€’6 Topics + Quizβ€’Client: Infosys Limited

Congratulations on joining Jolly Associates as an Article Assistant. The firm has just been appointed as statutory auditors of Infosys Limited for FY 2023-24. Your Audit Senior, Kritika, wastes no time β€” "Here's the annual report. I need the key ratios computed, materiality determined, and a preliminary analytical review by end of day. And yes, we're going to do all of this in Python. Welcome aboard."

🎬 Instructor Demo β€” Watch First

5-Layer Fraud Detection in 1 Second

Run this script that analyses 500 journal entries using Benford's Law, TDS compliance checks, suspicious narration scanning, statistical outlier detection, and structural red flags. By Session 4, you will build this yourself.

demo_fraud_detection.py
Output
Let's Begin β€” Topic by Topic
1

The Engagement Begins

Variables & Data Types β€” Storing Financial Data

Kritika hands you the Infosys Annual Report for FY 2023-24 and says β€” "First things first. Let's get the key numbers from the financial statements into Python. Think of variables as labelled boxes where you store data β€” just like how we label working paper files."

πŸ“– Core Concept: Variables & Data Types

In Python, a variable stores a value you can use later. Python automatically determines the data type:

  • int β€” Whole numbers (e.g., Revenue: 153670)
  • float β€” Decimals (e.g., EPS: 63.38)
  • str β€” Text in quotes (e.g., "Infosys Limited")
  • bool β€” Logical values: True or False

Key syntax in the code below:

  • # β€” Comments (ignored by Python, notes for humans)
  • print() β€” Display output to screen
  • type() β€” Check a variable's data type: type(revenue) β†’ <class 'int'>
  • f"..." β€” f-strings embed variables inside text: f"Revenue: {revenue}"
  • {revenue:,} β€” Format with commas: 153,670
  • "=" * 50 β€” String repetition: prints = fifty times
  • \n β€” Newline character inside strings for line breaks

Every financial figure you'll encounter during articleship β€” revenue, assets, liabilities, ratios β€” is just a number that can be stored in a variable. Instead of scribbling on paper or manually entering into Excel, you define it once in Python and use it everywhere.

topic1_variables.py
Output

✏️ Exercise β€” Extend the Engagement File

Add the following Balance Sheet figures to the code above and print them in the same formatted style:

  1. total_assets = 117281 (β‚Ή Crores)
  2. total_equity = 78847 (β‚Ή Crores)
  3. current_assets = 53633 (β‚Ή Crores)
  4. current_liabilities = 32585 (β‚Ή Crores)
  5. total_debt = 3428 (β‚Ή Crores)

Also print the data type of total_assets to confirm it's an int.

Attempts: 0/3
Correct Solution

πŸ† Challenge

Can you also add a variable working_capital calculated as current_assets - current_liabilities and print it? What data type do you think the result will be?

πŸ”’ Complete the exercise first
⏱️00:00
Correct Solution
2

The Senior Needs Ratios β€” Now

Arithmetic Operators & Formatted Output

Kritika checks her watch β€” "Good, the data is in. Now I need you to compute these 8 key financial ratios. CA KS Jolly wants them in the review meeting at 4 PM. And format them properly β€” two decimal places, with percentage signs. He's particular about presentation."

πŸ“– Core Concept: Arithmetic Operators & Formatted Output

Arithmetic operators work just like a calculator:

  • + - * β€” Addition, subtraction, multiplication
  • / β€” Division (always returns float, even 10/2 β†’ 5.0)
  • // β€” Floor division (rounds down)   % β€” Modulus (remainder)   ** β€” Power
  • round(value, dp) β€” Round to dp decimal places

Key syntax in the code below:

  • Chained arithmetic: net_profit / revenue * 100 β€” Python follows BODMAS/PEMDAS order
  • Derived variables: capital_employed = total_assets - current_liabilities β€” build new values from existing ones
  • f"NPM: {npm:.2f}%" β€” :.2f = 2 decimal places; :.4f = 4 decimal places
  • {value:,.0f} β€” Comma separator with 0 decimals: 1,537
  • All ratios use / β€” results are always float, which is why :.2f formatting is needed

Ratio analysis is the backbone of SA 520 (Analytical Procedures). Every CA computes these ratios during audit. Doing it manually in a calculator is tedious and error-prone. In Python, you define the formula once and it computes instantly β€” even for 100 companies.

topic2_ratios.py
Output

✏️ Exercise β€” Add More Ratios

Extend the report above to include:

  1. Operating Profit Margin = ebit / revenue * 100
  2. Working Capital = current_assets - current_liabilities
  3. Proprietary Ratio = total_equity / total_assets (shows what proportion of assets are funded by equity)

Format each to 2 decimal places with appropriate labels.

Attempts: 0/3
Correct Solution

πŸ† Challenge β€” DuPont Decomposition

Compute the DuPont 3-factor ROE: ROE = NPM Γ— Asset Turnover Γ— Equity Multiplier, where Equity Multiplier = total_assets / total_equity. Print all 3 components and verify that their product equals the ROE you calculated directly. Do they match?

πŸ”’ Complete the exercise first
⏱️00:00
Correct Solution
3

Five Years of Infosys

Lists β€” Collections of Financial Data

Kritika pulls up a comparative statement β€” "Single-year ratios are a start, but for SA 520 analytical procedures we need trend data. Here are Infosys's last 5 years of key figures. Store them and let's analyse the trends."

πŸ“– Core Concept: Lists

A list is an ordered collection of values in square brackets []:

  • Indexing: revenue[0] β†’ first item, revenue[-1] β†’ last item
  • Slicing: revenue[2:4] β†’ items at index 2 and 3 (end excluded); years[-3:] β†’ last 3 items

Key syntax in the code below:

  • max(revenue) / min(revenue) β€” Largest / smallest value in a list
  • sum(revenue) β€” Total of all values; len(revenue) β€” Count of items
  • revenue.index(max(revenue)) β€” Find the position of a value (used to match with the year)
  • sum(revenue) // len(revenue) β€” Floor division for integer average
  • revenue.copy() β€” Create an independent copy of a list
  • .append(170000) β€” Add a new item to the end of a list
  • {revenue[0]:,} β€” Format a list element with commas: 90,791

Trend analysis requires storing multiple years of data. Lists are the natural structure for time-series financial data β€” revenue over 5 years, quarterly profits, monthly cash flows. Once data is in a list, Python can instantly find the best year, worst year, averages, and more.

topic3_lists.py
Output

✏️ Exercise β€” Profit Trend Analysis

  1. Using the net_profit list, find and print the year with the highest net profit
  2. Calculate the 5-year average net profit using sum() and len()
  3. Create a new list total_assets = [75973, 84124, 95015, 110888, 117281] and print the most recent 3 years' values
  4. Use sorted() to print the revenue list in ascending order (without modifying the original)
Attempts: 0/3
Correct Solution

πŸ† Challenge β€” Net Profit Margin List

Can you compute the Net Profit Margin for each year? You'll need to divide each item in net_profit by the corresponding item in revenue. Hint: you might need a loop (covered next topic), but see if you can do it for just 2-3 years using indexing: npm_fy24 = net_profit[4] / revenue[4] * 100

πŸ”’ Complete the exercise first
⏱️00:00
Correct Solution
4

Materiality Assessment

Conditionals β€” Decision-Making in Code

Kritika opens SA 320 (Materiality in Planning and Performing an Audit) β€” "Before we test any account, we need to determine materiality. There are multiple benchmarks, and we need to pick the right one based on the entity's characteristics. This is a decision-making problem β€” perfect for conditionals."

πŸ“– Core Concept: Conditionals (if / elif / else)

Conditionals let Python make decisions β€” run different code based on a condition:

  • if condition: β€” Runs the indented block if condition is True
  • elif another: β€” Checked only if the if above was False
  • else: β€” Runs if nothing above was True

Key syntax in the code below:

  • Comparison: > < >= <= == (equals) != (not equals)
  • Logical: and (both must be True), or (either can be True)
  • Colon : β€” Required after every if / elif / else
  • Indentation β€” The 4-space indent defines what belongs inside each branch
  • min(a, b, c) β€” Selects the smallest benchmark (SA 320: conservative approach)
  • abs(pbt_change) β€” Absolute value: ignores sign (βˆ’12.5 β†’ 12.5)
  • {value:,.0f} β€” Comma-separated format with zero decimals

SA 320 requires auditors to determine materiality using benchmarks like % of revenue, % of PBT, or % of total assets β€” and then apply professional judgment to select the most appropriate one. Conditional logic in Python mirrors this exact decision process. You can also use it for audit sampling thresholds, risk categorization, and CARO reporting triggers.

topic4_materiality.py
Output

✏️ Exercise β€” CARO Reporting Check

Under CARO 2020, certain conditions trigger specific reporting. Write conditional logic for:

  1. If total_debt > 0 and de_ratio > 1.0, print "CARO Flag: High leverage β€” report under Clause 3(ix)"
  2. If current_ratio < 1.0, print "Going concern risk β€” consider SA 570"
  3. Otherwise, print "No CARO flags triggered"

Test with Infosys data (total_debt = 3428, total_equity = 78847, current_ratio = 1.65).

Attempts: 0/3
Correct Solution

πŸ† Challenge β€” Multi-Level Materiality

Create a function (or just use nested if) that takes any account_balance and classifies it into 5 levels: "Highly Material" (>2Γ— materiality), "Material", "Significant", "Insignificant" (>trivial but

πŸ”’ Complete the exercise first
⏱️00:00
Correct Solution
5

Analytical Review Loop

For Loops β€” Processing All Years Automatically

Kritika looks at your ratio calculations β€” "Nice work for FY24. But SA 520 requires us to look at trends over multiple periods. Computing YoY growth for each year manually? That's what loops are for. Write it once, and Python processes every year automatically."

πŸ“– Core Concept: For Loops

A for loop repeats code for each item in a sequence β€” no copy-paste needed:

  • for i in range(len(revenue)): β€” Loop by index (access [i] and [i-1])
  • range(len(revenue)) β€” Generates 0, 1, 2, 3, 4 for a 5-item list

Key syntax in the code below:

  • growth_rates = [] then .append(growth) β€” Build a new list inside a loop (accumulator pattern)
  • if i == 0: inside loop β€” Handle the base year differently (no prior year to compare)
  • enumerate(years) β€” Gives both index i and value year in one loop
  • f"{years[i]:<14}" β€” Left-align text to 14 chars; {revenue[i]:>10,} β€” Right-align to 10 with commas
  • {growth:>9.1f}% β€” Right-align, 1 decimal place
  • "β–ˆ" * int(npm) β€” Visual bar chart using string repetition
  • sum(growth_rates)/len(growth_rates) β€” Average of the accumulated list

Analytical procedures under SA 520 require comparing data across periods to identify unusual trends. A loop lets you compute YoY changes, growth rates, and variance flags for every account, every year β€” instantly. What takes 30 minutes of Excel formulas takes 5 lines of Python.

topic5_loops.py
Output

✏️ Exercise β€” Total Assets Growth + Flags

Using the data total_assets = [75973, 84124, 95015, 110888, 117281]:

  1. Write a loop to compute and print YoY growth for total assets (same format as revenue above)
  2. Flag any year where asset growth exceeds 15% with "⚠️ Significant increase β€” investigate per SA 315"
  3. After the loop, print the average annual asset growth rate
Attempts: 0/3
Correct Solution

πŸ† Challenge β€” Ratio Trend Table

Using both revenue and net_profit lists, compute and print a complete table showing NPM, and year-on-year change in NPM (in percentage points) for all 5 years. Flag any year where NPM dropped by more than 1 percentage point.

πŸ”’ Complete the exercise first
⏱️00:00
Correct Solution
πŸ”₯ Session Capstone

Analytical Memo Generator

Combining Everything into a Real Audit Deliverable

Kritika looks at the clock β€” "CA KS Jolly's review meeting is in 30 minutes. I need a complete Preliminary Analytical Review Memo β€” 5-year trends, ratios, YoY changes, and anomaly flags. Let's combine everything we've learned into one deliverable. This is what goes into the audit file."

capstone_memo.py
Output

✏️ Exercise β€” Enhance the Memo

Add a new section to the memo β€” "5. CURRENT RATIO TREND" β€” that loops through all 5 years, computes the Current Ratio for each year, and flags any year where it drops below 1.5. Also add a section showing the Working Capital trend (Current Assets - Current Liabilities) for all 5 years.

Attempts: 0/3
Correct Solution

πŸ† Challenge β€” Complete Analytical Review

Add sections for: (a) Asset Turnover trend (Revenue / Total Assets for each year), (b) ROE trend for all 5 years, (c) A "Conclusion" section that counts total anomalies and prints a final risk assessment: "High Risk" if >3 anomalies, "Moderate Risk" if 1-3, "Low Risk" if 0. Make the memo partner-review ready!

πŸ”’ Complete the exercise first
⏱️00:00
Correct Solution
Session 1 Quiz

πŸ“ Test Your Knowledge

15 questions covering Python fundamentals applied to financial analysis. You need at least 60% (9/15) to pass. Take your time β€” check the code editors above if you need to.

Question 1 of 15
What is the data type of the variable revenue in the statement revenue = 153670?
Since 153670 has no decimal point, Python stores it as an int (integer). If it were 153670.0, it would be a float. There is no number type in Python.
Question 2 of 15
What will be the output of the following code?
pbt = 34189 tax = 7956 net_profit = pbt - tax print(type(net_profit))
Subtracting two int values gives an int. The type() function returns the data type, not the value. So the output is <class 'int'>.
Question 3 of 15
Which of the following correctly computes Net Profit Margin and rounds it to 2 decimal places?
Net Profit Margin = (Net Profit / Revenue) Γ— 100. Option B correctly applies the formula AND rounds to 2 decimal places. Option A forgets to multiply by 100. Option D has the formula inverted.
Question 4 of 15
What does the / operator return in Python when dividing two integers?
In Python 3, the / operator always returns a float, even when dividing evenly. For example, 10 / 2 returns 5.0, not 5. Use // for integer (floor) division.
Question 5 of 15
What will this f-string output?
ratio = 1.64537 print(f"Current Ratio: {ratio:.2f}")
:.2f in an f-string formats the number to 2 decimal places with rounding. 1.64537 rounds to 1.65 (since the third decimal is 5, it rounds up).
Question 6 of 15
Given revenue = [90791, 100472, 121641, 146767, 153670], what does revenue[-2] return?
Negative indexing counts from the end. [-1] is the last item (153670), so [-2] is the second-to-last: 146767 (FY23 revenue).
Question 7 of 15
What does revenue[1:4] return from the list [90791, 100472, 121641, 146767, 153670]?
Slicing [1:4] returns items at index 1, 2, 3 (the end index 4 is excluded). So it returns [100472, 121641, 146767] β€” three items starting from index 1.
Question 8 of 15
Which code correctly computes the average of a list of 5 revenue figures?
sum(revenue) adds all elements, and len(revenue) gives the count. Dividing them gives the average. Option A fails because you can't divide a list by a number. Option C uses mean() which doesn't exist in base Python (it's in NumPy). Option D uses incorrect syntax.
Question 9 of 15
Under SA 320, you set overall materiality as the lower of two benchmarks. Which Python code does this?
min() returns the smaller of the values β€” exactly what you need when selecting the lower benchmark for materiality. There is no built-in lower() function for numbers in Python.
Question 10 of 15
What will this code print?
current_ratio = 0.85 if current_ratio >= 2: print("Strong") elif current_ratio >= 1: print("Adequate") else: print("Weak")
0.85 is not β‰₯ 2, so the first condition fails. 0.85 is not β‰₯ 1, so the second condition also fails. The else block executes, printing Weak. This is how an auditor would assess liquidity risk.
Question 11 of 15
Which condition correctly flags an account that is both above performance materiality and shows more than 20% year-on-year change?
When both conditions must be true, use and. The or operator would flag when either condition is met. & is a bitwise operator and behaves differently from and for booleans.
Question 12 of 15
What will this loop print?
values = [10, 20, 30] total = 0 for v in values: total = total + v print(total)
This is the "accumulator pattern." total starts at 0, then adds each value: 0+10=10, 10+20=30, 30+30=60. The print is outside the loop (not indented), so it prints the final total: 60.
Question 13 of 15
To compute YoY growth, you need to compare each year with the previous year. Which loop correctly starts from the second element?
range(1, len(revenue)) starts i at 1, so you can access both revenue[i] (current year) and revenue[i-1] (prior year) without going out of bounds. Starting at 0 would make revenue[-1] refer to the last element, which is wrong.
Question 14 of 15
This code has a bug. What's wrong?
revenue = 153670 if revenue > 100000 print("Large company")
Python requires a colon : at the end of if, elif, else, for, and def statements. The correct line would be if revenue > 100000:. This is one of the most common syntax errors beginners encounter.
Question 15 of 15
You want to build a list of growth rates inside a loop. Which approach is correct?
You must create an empty list before the loop, then append() to it inside the loop. If you create it inside the loop (Option B), it resets to empty on each iteration. Option C fails because you can't + a list and a number directly. Option D causes an IndexError.