Project Review Scheduler – Real-World Implementation Report

Executive Summary

Project accountability, transparency, and timely reviews are necessary for business success in technical environments. However, our company still relies on spreadsheets and manual reminders and is prone to error, delay, and miscommunication. The Project Review Scheduler is a Python-based solution developed in Jupyter Notebook to automate the review process, assign reviewers based on department and workload, and notify them ahead of due dates via email. With scalability in mind, it simulated but realistic sample data, secure credential storage, and structured outputs. This report outlines the system’s motivation, structure, data model, script logic, and benefits.

Problem Statement / Motivation

Manual project review workflows are inefficient. In our tech company, reviews often fall through the cracks because there is no centralized scheduling system. Department leads manually track which projects need reviews and email potential reviewers, often at the last minute. This leads to reviewer fatigue, uneven workloads, missed deadlines, and a lack of audit history. We need a repeatable, automated, and scalable process that ensures project reviews happen on time and that responsibilities are assigned and documented.

Project Goals

- Automate the identification of projects that require review based on review frequency 
  and last review date.

- Assign reviewers based on current workload and department alignment.

- Send review assignment emails to reviewers with due dates and relevant notes.

- Maintain logs of all review assignments for accountability and audit.s

- Store reviewer and project data in structured CSV files for interoperability.

- Generate professional documentation and entity-relationship diagrams (ERDs).

System Overview / Architecture

This system is implemented using Python in a Jupyter Notebook environment. Project and reviewer data are stored in CSV files. The core script uses Pandas for data manipulation, Faker for generating realistic demo data, dotenv for secure email credential loading, and smtplib to send emails to reviewers.

Tools:

-  Python 3.x

-  Jupyter Notebook

-  pandas, faker, smtplib, dotenv

-  Draw.io for ERD creation

Data Flow

image.png

Entity-Relationship Diagram (ERD)

This system follows a normalized relational structure:

Tables:

    Projects: Each project is reviewed on a scheduled frequency.

        - project_id (PK)

        - project_name, start_date, last_review_date, review_frequency_years, next_review_date, status

        - Reviewers: Users eligible to review projects.

        - reviewer_id (PK), name, email, department_id (FK), current_load

    Departments: Organizational groups reviewers belong to.

        - department_id (PK), department_name

    Reviews: Core junction table linking projects and reviewers.

        - review_id (PK), project_id (FK), reviewer_id (FK), status_id (FK), scheduled_date, 
          completion_date,review_notes, assigned_date

    ReviewStatus: Lookup values for review status.

        - status_id (PK), status_name

    ReviewHistory: Audit trail of review actions.

        - history_id (PK), review_id (FK), action, timestamp, performed_by

image.png

Key Libraries

# Import Libraries
import pandas as pd
from faker import Faker
import random
from datetime import timedelta, datetime
from dotenv import load_dotenv
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import os


fake = Faker()
Faker.seed(42)
random.seed(42)


Generate Projects, Reviewers dataFrameCSV files

# === Generate Projects ===
def generate_projects(n=50):
    projects = []
    departments = ['Engineering', 'QA', 'HR', 'Finance', 'IT']
    for i in range(1, n + 1):
        start_date = fake.date_between(start_date='-2y', end_date='-6m')
        last_review = start_date + timedelta(days=random.randint(180, 365))
        frequency = random.choice([1, 2])
        projects.append({
            "Project ID": f"P{i:03}",
            "Project Name": fake.bs().title(),
            "Start Date": start_date,
            "Last Review Date": last_review,
            "Review Frequency (Years)": frequency,
            "Department": random.choice(departments)
        })
    return pd.DataFrame(projects)

# === Generate Reviewers ===
def generate_reviewers(n=10):
    departments = ['Engineering', 'QA', 'HR', 'Finance', 'IT']
    reviewers = []
    for i in range(1, n + 1):
        reviewers.append({
            "Reviewer ID": f"R{i:03}",
            "Name": fake.name(),
            "Email": fake.email(),
            "Department": random.choice(departments),
            "Current Load": random.randint(0, 5)
        })
    return pd.DataFrame(reviewers)



# Generate Data
projects_df = generate_projects()
reviewers_df = generate_reviewers()


# Save to CSV
projects_df.to_csv("projects.csv", index=False)
reviewers_df.to_csv("reviewers.csv", index=False)


print("Data generated and saved as CS")

Data generated and saved as CS

Display reviewers_df

reviewers_df
Reviewer ID Name Email Department Current Load
0 R001 Andre Rivera Engineering 2
1 R002 Timothy Duncan HR 1
2 R003 Brent Jordan Engineering 1
3 R004 Victoria Garcia IT 0
4 R005 Connor West Engineering 5
5 R006 Angela Morton Finance 0
6 R007 Tammy Allison IT 1
7 R008 Carmen Smith QA 5
8 R009 Michael Cross Finance 4
9 R010 Lauren Daniels QA 2

Display projects_df

projects_df['Last Review Date'] = pd.to_datetime(projects_df['Last Review Date'])

projects_df.head(5)
Project ID Project Name Start Date Last Review Date Review Frequency (Years) Department
0 P001 Utilize Efficient Action-Items 2024-08-09 2025-07-18 1 Engineering
1 P002 Extend Sticky Methodologies 2023-10-10 2024-06-16 1 QA
2 P003 Incubate E-Business Applications 2024-10-22 2025-05-25 1 IT
3 P004 Utilize E-Business E-Tailers 2024-03-04 2024-09-22 2 Engineering
4 P005 Expedite Proactive Schemas 2023-10-17 2024-04-21 1 QA

Script Walkthrough

Optimized Reviewer Assignment Function:

The system uses a custom reviewer assignment algorithm to manage the review process. This algorithm evaluates each project’s next required review date and matches it to the reviewer with the lowest current workload in the same department. The algorithm selects from the full reviewer pool if no departmental reviewer is available. The goal is to balance reviewer load while ensuring department relevance and review timeliness.

def assign_reviewers_optimized(projects_df, reviewers_df):
    assignments = []
    today = pd.to_datetime("today")

    # Calculate next review date
    projects_df['Next Review Date'] = projects_df['Last Review Date'] + pd.to_timedelta(
        projects_df['Review Frequency (Years)'] * 365, unit='D')

    # Filter projects that are due for review
    due_projects = projects_df[projects_df['Next Review Date'] <= today].copy()
    due_projects = due_projects.sort_values(by='Next Review Date')

    # Sort reviewers by current load
    reviewers_df = reviewers_df.sort_values(by='Current Load').copy().reset_index(drop=True)
    assigned_reviewers = set()

    for _, project in due_projects.iterrows():
        # Filter reviewers by department and not already assigned
        dept_reviewers = reviewers_df[
            (reviewers_df['Department'] == project['Department']) &
            (~reviewers_df['Reviewer ID'].isin(assigned_reviewers))
        ]

        # Fallback to unassigned from all departments
        if dept_reviewers.empty:
            dept_reviewers = reviewers_df[~reviewers_df['Reviewer ID'].isin(assigned_reviewers)]

        if dept_reviewers.empty:
            continue  # No available reviewers left

        selected = dept_reviewers.iloc[0]
        assigned_reviewers.add(selected['Reviewer ID'])

        assignments.append({
            'Project ID': project['Project ID'],
            'Project Name': project['Project Name'],
            'Reviewer ID': selected['Reviewer ID'],
            'Reviewer Name': selected['Name'],
            'Email': selected['Email'],
            'Department': selected['Department'],
            'Scheduled Date': project['Next Review Date'],
            'Status': "Due",
            'Review Notes': f"Scheduled review for {project['Project Name']}"
        })

    return pd.DataFrame(assignments)
    

Main Workflow:

The script loads environment variables, generates project and reviewer data, and executes the assign_reviewers_optimized function. This step connects due projects to the most appropriate available reviewers. The resulting assignments DataFrame is then displayed for inspection. If no projects are due, a message is shown instead. This provides immediate feedback and ensures that the pipeline works end-to-end before any emails are sent or CSVs are generated.

# Assign reviewers to due projects
assignments_df = assign_reviewers_optimized(projects_df, reviewers_df)
from IPython.display import display
if not assignments_df.empty:
    display(assignments_df.head(10))
else:
    print("No projects are due for review.")
Project ID Project Name Reviewer ID Reviewer Name Email Department Scheduled Date Status Review Notes
0 P041 Matrix Strategic Infrastructures R003 Brent Jordan Engineering 2025-02-10 Due Scheduled review for Matrix Strategic Infrastr…
1 P010 Transform Synergistic Convergence R010 Lauren Daniels QA 2025-03-14 Due Scheduled review for Transform Synergistic Con…
2 P005 Expedite Proactive Schemas R008 Carmen Smith QA 2025-04-21 Due Scheduled review for Expedite Proactive Schemas
# Create a CSV file for assignments

assignments_df.to_csv("reviewer_assignments.csv", index=False)
print("reviewer_assignments.csv created")
reviewer_assignments.csv created

Security

To manage sensitive credentials securely, this project uses the python-dotenv library. This package allows developers to store environment variables (like email addresses and app passwords) in a separate .env file rather than hardcoding them into the script. By using load_dotenv(), these values are loaded into the runtime environment, protecting credentials from exposure in source control and making the script more portable and secure.

from dotenv import load_dotenv
import os

load_dotenv()
print(os.getenv("EMAIL_ADDRESS"))

cynthiamcginnis2020@gmail.com

Python Email Configuration Code

This Python code is part of an email automation setup. It uses modules to build and send an email and loads sensitive credentials securely using environment variables.



from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import smtplib
import os
from dotenv import load_dotenv

# Load credentials from .env
load_dotenv(dotenv_path=".env")
EMAIL_ADDRESS = os.getenv("EMAIL_ADDRESS")
EMAIL_PASSWORD = os.getenv("EMAIL_PASSWORD")
AUDIT_EMAIL = os.getenv("AUDIT_RECIPIENT")

Loading Environment Variables with python-dotenv

This code snippet demonstrates how to load and access environment variables stored in a .env file using the python-dotenv package.

A .env file is a plain text file that stores environment variables—key-value pairs that configure your application without hardcoding sensitive or configurable data directly into your source code.

from dotenv import load_dotenv
load_dotenv(".env")  # or load_dotenv() if .env is in the same folder
import os

print("Audit email:", os.getenv("AUDIT_RECIPIENT"))
Audit email: cynthiamcginnis2020@gmail.com

📧 Python Email Automation with .env Credentials and SMTP

This script demonstrates how to securely send emails using Gmail’s SMTP server, environment variables, and pandas DataFrame iteration for bulk email assignments.

from dotenv import load_dotenv
import os
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

# === Load environment variables ===
load_dotenv(".env")
EMAIL_ADDRESS = os.getenv("EMAIL_ADDRESS")
EMAIL_PASSWORD = os.getenv("EMAIL_PASSWORD")
AUDIT_EMAIL = os.getenv("AUDIT_RECIPIENT")

# === Start SMTP session ===
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
    server.login(EMAIL_ADDRESS, EMAIL_PASSWORD)

    # === 1. Send audit/test email ===
    audit_subject = "📋 Audit Review Assignment Notification"
    audit_body = f"""
Hi,

This is a test notification to confirm that the project review email system is working correctly.

Regards,  
Project Review Scheduler
"""

    audit_msg = MIMEMultipart()
    audit_msg["From"] = EMAIL_ADDRESS
    audit_msg["To"] = AUDIT_EMAIL
    audit_msg["Subject"] = audit_subject
    audit_msg.attach(MIMEText(audit_body, "plain"))

    server.sendmail(EMAIL_ADDRESS, AUDIT_EMAIL, audit_msg.as_string())
    print(f" Audit email sent to {AUDIT_EMAIL}\n---\n{audit_body}\n---")

    # === 2. Send reviewer assignment emails from assignments_df ===
    for _, row in assignments_df.iterrows():
        try:
            subject = " Review Assignment Notification"
            body = f"""
Hi {row['Reviewer Name']},

You have been assigned to review the following project:

- Project: {row['Project Name']}
- Scheduled Review Date: {row['Scheduled Date'].strftime('%Y-%m-%d')}
- Notes: {row['Review Notes']}

Please complete your review by the due date.

Regards,  
Project Review Scheduler
"""

            msg = MIMEMultipart()
            msg["From"] = EMAIL_ADDRESS
            msg["To"] = row["Email"]
            msg["Subject"] = subject
            msg.attach(MIMEText(body, "plain"))

            server.sendmail(EMAIL_ADDRESS, row["Email"], msg.as_string())

            # Print message body for review
            print(f"\n Email sent to {row['Reviewer Name']} <{row['Email']}>\n---\n{body}\n---")

        except Exception as e:
            print(f" Failed to send to {row['Email']}: {e}")
 Audit email sent to cynthiamcginnis2020@gmail.com
---

Hi,

This is a test notification to confirm that the project review email system is working correctly.

Regards,  
Project Review Scheduler

---

 Email sent to Brent Jordan <ujenkins@example.org>
---

Hi Brent Jordan,

You have been assigned to review the following project:

- Project: Matrix Strategic Infrastructures
- Scheduled Review Date: 2025-02-10
- Notes: Scheduled review for Matrix Strategic Infrastructures

Please complete your review by the due date.

Regards,  
Project Review Scheduler

---

 Email sent to Lauren Daniels <brian97@example.net>
---

Hi Lauren Daniels,

You have been assigned to review the following project:

- Project: Transform Synergistic Convergence
- Scheduled Review Date: 2025-03-14
- Notes: Scheduled review for Transform Synergistic Convergence

Please complete your review by the due date.

Regards,  
Project Review Scheduler

---

 Email sent to Carmen Smith <ybaker@example.com>
---

Hi Carmen Smith,

You have been assigned to review the following project:

- Project: Expedite Proactive Schemas
- Scheduled Review Date: 2025-04-21
- Notes: Scheduled review for Expedite Proactive Schemas

Please complete your review by the due date.

Regards,  
Project Review Scheduler

---