Add demo-03: CSV data processor with intentional code smells

- Add employee_data_processor.py: functional but ugly code with god function, globals, hardcoded values, mixed concerns
- Add employees.csv: sample data with 10 records (4 invalid for testing)
- Generates report.json and report.html output files
- Perfect candidate for refactoring demo
This commit is contained in:
Benjamin Hackl 2026-01-15 19:01:07 +01:00
commit 2f2be8e1a0
4 changed files with 265 additions and 0 deletions

View file

@ -0,0 +1,172 @@
#!/usr/bin/env python3
import csv
import json
import datetime
import re
processed_records = []
skipped_records = []
total_salary = 0
dept_count = {}
def process_employee_data():
global processed_records, skipped_records, total_salary, dept_count
file_path = "employees.csv"
json_output_path = "report.json"
html_output_path = "report.html"
try:
with open(file_path, 'r') as f:
reader = csv.DictReader(f)
rows = list(reader)
print(f"Reading file: {file_path}")
print(f"Total rows in CSV: {len(rows)}")
for row in rows:
name = row['name']
email = row['email']
dept = row['department']
salary = row['salary']
hire_date = row['hire_date']
# Validation part 1 - email
if not email or '@' not in email or '.' not in email.split('@')[1]:
print(f"WARNING: Invalid email for {name}: {email}")
skipped_records.append({'name': name, 'reason': 'Invalid email'})
continue
# Validation part 2 - salary
try:
sal = int(salary)
except:
print(f"WARNING: Invalid salary for {name}: {salary}")
skipped_records.append({'name': name, 'reason': 'Invalid salary'})
continue
if sal < 0:
print(f"WARNING: Negative salary for {name}: {sal}")
skipped_records.append({'name': name, 'reason': 'Negative salary'})
continue
# Validation part 3 - department
valid_depts = ['ENG', 'SALES', 'MKTG', 'HR']
if dept not in valid_depts:
print(f"WARNING: Invalid department for {name}: {dept}")
skipped_records.append({'name': name, 'reason': 'Invalid department'})
continue
# Validation part 4 - hire_date
try:
dt = datetime.datetime.strptime(hire_date, '%Y-%m-%d')
except:
print(f"WARNING: Invalid hire_date for {name}: {hire_date}")
skipped_records.append({'name': name, 'reason': 'Invalid hire_date'})
continue
# Transformations
if dept == 'ENG':
full_dept = 'Engineering'
elif dept == 'SALES':
full_dept = 'Sales'
elif dept == 'MKTG':
full_dept = 'Marketing'
elif dept == 'HR':
full_dept = 'Human Resources'
else:
full_dept = dept
annual_salary = sal * 12
years_experience = (datetime.datetime.now() - dt).days / 365.25
processed_records.append({
'name': name,
'email': email,
'department': full_dept,
'annual_salary': annual_salary,
'hire_date': hire_date,
'years_experience': round(years_experience, 2)
})
total_salary += annual_salary
if full_dept not in dept_count:
dept_count[full_dept] = 0
dept_count[full_dept] += 1
print(f"\nProcessed {len(processed_records)} valid records")
print(f"Skipped {len(skipped_records)} invalid records")
print(f"Total annual payroll: ${total_salary:,}")
print("\nEmployees by department:")
for d in dept_count:
print(f" {d}: {dept_count[d]}")
# Generate JSON output
json_data = {
'summary': {
'total_employees': len(processed_records),
'skipped_records': len(skipped_records),
'total_annual_payroll': total_salary,
'employees_by_department': dept_count
},
'employees': processed_records,
'skipped': skipped_records
}
with open(json_output_path, 'w') as jf:
json.dump(json_data, jf, indent=2)
print(f"\nJSON report written to: {json_output_path}")
# Generate HTML output
html_content = "<html><head><title>Employee Report</title></head><body>"
html_content += "<h1>Employee Data Report</h1>"
html_content += "<h2>Summary</h2>"
html_content += "<ul>"
html_content += f"<li>Total Employees: {len(processed_records)}</li>"
html_content += f"<li>Skipped Records: {len(skipped_records)}</li>"
html_content += f"<li>Total Annual Payroll: ${total_salary:,}</li>"
html_content += "</ul>"
html_content += "<h2>Employees by Department</h2>"
html_content += "<ul>"
for d in dept_count:
html_content += f"<li>{d}: {dept_count[d]}</li>"
html_content += "</ul>"
html_content += "<h2>Employee Details</h2>"
html_content += "<table border='1' cellpadding='5'>"
html_content += "<tr><th>Name</th><th>Email</th><th>Department</th><th>Annual Salary</th><th>Hire Date</th><th>Years Exp</th></tr>"
for emp in processed_records:
html_content += "<tr>"
html_content += f"<td>{emp['name']}</td>"
html_content += f"<td>{emp['email']}</td>"
html_content += f"<td>{emp['department']}</td>"
html_content += f"<td>${emp['annual_salary']:,}</td>"
html_content += f"<td>{emp['hire_date']}</td>"
html_content += f"<td>{emp['years_experience']}</td>"
html_content += "</tr>"
html_content += "</table>"
html_content += "<h2>Skipped Records</h2>"
html_content += "<ul>"
for skipped in skipped_records:
html_content += f"<li>{skipped['name']}: {skipped['reason']}</li>"
html_content += "</ul>"
html_content += "</body></html>"
with open(html_output_path, 'w') as hf:
hf.write(html_content)
print(f"HTML report written to: {html_output_path}")
print("\nProcessing complete!")
except FileNotFoundError:
print(f"ERROR: File not found: {file_path}")
except Exception as e:
print(f"ERROR: Something went wrong: {e}")
if __name__ == "__main__":
process_employee_data()

11
demo-03/employees.csv Normal file
View file

@ -0,0 +1,11 @@
name,email,department,salary,hire_date
Alice Smith,alice@example.com,ENG,75000,2023-01-15
Bob Jones,bob@example.com,SALES,-50000,2023-02-01
Charlie,charlie@bademail,MKTG,65000,2023-03-01
Diana Prince,diana@example.com,ENG,82000,2022-05-20
Eve Wilson,eve@example.com,HR,58000,2023-04-10
Frank Miller,frank@example.com,INVALID,70000,2023-01-25
Grace Lee,grace@example.com,SALES,0,2023-06-01
Henry Davis,henry@example.com,ENG,90000,invalid_date
Ivy Chen,ivy@example.com,MKTG,62000,2023-07-15
Jack Brown,jack@example.com,HR,55000,2023-08-01
1 name email department salary hire_date
2 Alice Smith alice@example.com ENG 75000 2023-01-15
3 Bob Jones bob@example.com SALES -50000 2023-02-01
4 Charlie charlie@bademail MKTG 65000 2023-03-01
5 Diana Prince diana@example.com ENG 82000 2022-05-20
6 Eve Wilson eve@example.com HR 58000 2023-04-10
7 Frank Miller frank@example.com INVALID 70000 2023-01-25
8 Grace Lee grace@example.com SALES 0 2023-06-01
9 Henry Davis henry@example.com ENG 90000 invalid_date
10 Ivy Chen ivy@example.com MKTG 62000 2023-07-15
11 Jack Brown jack@example.com HR 55000 2023-08-01

1
demo-03/report.html Normal file
View file

@ -0,0 +1 @@
<html><head><title>Employee Report</title></head><body><h1>Employee Data Report</h1><h2>Summary</h2><ul><li>Total Employees: 6</li><li>Skipped Records: 4</li><li>Total Annual Payroll: $3,984,000</li></ul><h2>Employees by Department</h2><ul><li>Engineering: 2</li><li>Human Resources: 2</li><li>Sales: 1</li><li>Marketing: 1</li></ul><h2>Employee Details</h2><table border='1' cellpadding='5'><tr><th>Name</th><th>Email</th><th>Department</th><th>Annual Salary</th><th>Hire Date</th><th>Years Exp</th></tr><tr><td>Alice Smith</td><td>alice@example.com</td><td>Engineering</td><td>$900,000</td><td>2023-01-15</td><td>3.0</td></tr><tr><td>Diana Prince</td><td>diana@example.com</td><td>Engineering</td><td>$984,000</td><td>2022-05-20</td><td>3.66</td></tr><tr><td>Eve Wilson</td><td>eve@example.com</td><td>Human Resources</td><td>$696,000</td><td>2023-04-10</td><td>2.77</td></tr><tr><td>Grace Lee</td><td>grace@example.com</td><td>Sales</td><td>$0</td><td>2023-06-01</td><td>2.63</td></tr><tr><td>Ivy Chen</td><td>ivy@example.com</td><td>Marketing</td><td>$744,000</td><td>2023-07-15</td><td>2.51</td></tr><tr><td>Jack Brown</td><td>jack@example.com</td><td>Human Resources</td><td>$660,000</td><td>2023-08-01</td><td>2.46</td></tr></table><h2>Skipped Records</h2><ul><li>Bob Jones: Negative salary</li><li>Charlie: Invalid email</li><li>Frank Miller: Invalid department</li><li>Henry Davis: Invalid hire_date</li></ul></body></html>

81
demo-03/report.json Normal file
View file

@ -0,0 +1,81 @@
{
"summary": {
"total_employees": 6,
"skipped_records": 4,
"total_annual_payroll": 3984000,
"employees_by_department": {
"Engineering": 2,
"Human Resources": 2,
"Sales": 1,
"Marketing": 1
}
},
"employees": [
{
"name": "Alice Smith",
"email": "alice@example.com",
"department": "Engineering",
"annual_salary": 900000,
"hire_date": "2023-01-15",
"years_experience": 3.0
},
{
"name": "Diana Prince",
"email": "diana@example.com",
"department": "Engineering",
"annual_salary": 984000,
"hire_date": "2022-05-20",
"years_experience": 3.66
},
{
"name": "Eve Wilson",
"email": "eve@example.com",
"department": "Human Resources",
"annual_salary": 696000,
"hire_date": "2023-04-10",
"years_experience": 2.77
},
{
"name": "Grace Lee",
"email": "grace@example.com",
"department": "Sales",
"annual_salary": 0,
"hire_date": "2023-06-01",
"years_experience": 2.63
},
{
"name": "Ivy Chen",
"email": "ivy@example.com",
"department": "Marketing",
"annual_salary": 744000,
"hire_date": "2023-07-15",
"years_experience": 2.51
},
{
"name": "Jack Brown",
"email": "jack@example.com",
"department": "Human Resources",
"annual_salary": 660000,
"hire_date": "2023-08-01",
"years_experience": 2.46
}
],
"skipped": [
{
"name": "Bob Jones",
"reason": "Negative salary"
},
{
"name": "Charlie",
"reason": "Invalid email"
},
{
"name": "Frank Miller",
"reason": "Invalid department"
},
{
"name": "Henry Davis",
"reason": "Invalid hire_date"
}
]
}