English
Appearance
English
Appearance
In the last two sessions (15 and 16) you build one complete process, of the kind you now do in SAS or SQL: the monthly portfolio report. It runs for any month end of the history, from the raw data to one Excel file, and it can be run again next month without changes.
Each lecture adds a part that the project reuses: functions (4), dictionaries as lookup tables (6), text cleaning and regular expressions (7, 8), reading files and handling bad records (9), modules and Git (10), classes (11, 12), databases (13) and pandas (14 - 16). The homework of session 16 is the first part of the project.
Write report.py. Run as
python report.py 2026-09-30it writes out/portfolio_report_2026-09-30.xlsx and out/portfolio_2026-09-30.png, and prints a short summary with the checks. With 2026-08-31 (or any month end from 2025-10-31 to 2026-09-30) it makes the report of that month.
All in data/ of the homework repository (the sample data):
| Source | What you take from it |
|---|---|
portfolio.db (SQLite) | the tables clients, loans (the client of each loan, product, currency, dates), rating_scale, ratings_history - with SQL and parameters (session 13) |
balances_monthly.csv | the outstanding and the days past due of each loan at each month end |
client_register.csv | the names, EIKs, IBANs and cities of the clients (messy text - sessions 7, 8) |
fx_rates.xlsx | the EUR exchange rates of each working day, one sheet per month (sessions 9, 14) |
regions_bg.csv | the region codes and names (cp1251 - session 7) |
balances_monthly.csv: the rows of the report date (and all months up to it, for the trend).fx_rates.xlsx: the EUR / USD rate of the last working day on or before the report date (all sheets; n/a if the month is not in the file).client_register.csv: the register, for the data quality checks.Use your module homework_nomenclature.py of session 12 (copy it next to report.py). At least:
Every failed check is one row of a rejects table: the dataset, the id, the column, the value, the reason. A row that fails is reported, never dropped silently. The report says how many rows were rejected; the loans that pass go on.
The sample data is mostly clean, so most checks find nothing. Test each check: make a copy of a table, break it on purpose (a product leasing, a negative balance, a client id C9999) and see that the row appears in the rejects.
One DataFrame with a row per loan at the report date: the loan, its balance and days past due, the client's segment and region, the rating and grade, the PD (the middle of the band), the EL (PD x LGD x outstanding, LGD 0.45 - a parameter), the DPD bucket (current, 1-30, 31-90, 90+), and the age of the loan in months.
The tables of the report:
| Sheet | Content |
|---|---|
summary | report date, number of loans and clients, outstanding, EL, EL / outstanding, the share of 90+, the EUR / USD rate, the number of rejected rows |
segment x dpd | outstanding by segment and DPD bucket, with totals |
segment x grade | outstanding by segment and grade, the grades in the order of the scale |
migration | the rating migration matrix: previous month end -> report date, all ratings |
trend | loans and outstanding for every month end up to the report date, with the change from the previous month |
top clients | the 10 largest clients: id, name (from the register), segment, grade, outstanding, share of the portfolio |
rejects | the table of step 2 |
data quality | per dataset and column: rows, missing values, rejected values |
pd.ExcelWriter, session 16): readable widths, a number format for the amounts, a title row on the summary.The project lives in a folder project/ of your copy of the homework repository (session 10): report.py, homework_nomenclature.py, any other module of yours, a README.md (what the report does, how to run it, what each sheet means). out/ is not committed. Commit after each step that works - the history should show the steps.
For the report date 2026-09-30 (the numbers of sessions 4, 6, 13 and 15):
For 2026-08-31: outstanding 133,492,549.54 and EL 1,093,088.84.
python report.py 2026-09-30 runs from a fresh terminal, in the virtual environment, without errors.? or :name).rejects sheet, with its reason.main().README.md explains how to run it.pd.date_range(freq="ME")), and one sheet that compares them.argparse, Self-study of session 10).