---
# System prepended metadata

title: Feedback Release

---

# Feedback Release

```python=
'''
MODULE DOCSTRING
----------------

'''

import os
import sys
import csv
import time
import re
from dataclasses import dataclass, fields
from typing import List, Dict, Tuple
from yagmail import SMTP
from mdutils.mdutils import MdUtils as MD
from pyhtml import html, head, body, p, br, i, b, a

ITERATION_MAPPING: Dict[int, Tuple[int, int]] = {
    0: (30, 10),
    1: (15, 15),
    2: (12, 8),
    3: (5, 5)
}

emailer: SMTP = SMTP(os.environ.get('EMAIL'), os.environ.get('PASSWORD'))

@dataclass
class Student:
    '''
    CLASS DOCSTRING
    ---------------

    '''

    zid: str
    name: str
    tute: str
    group: str

@dataclass
class Feedback:
    '''
    CLASS DOCSTRING
    ---------------

    '''

    commits: Dict[str, Dict[str, str]]
    merge_reqs: Dict[str, Dict[str, str]]
    tdd: Dict[str, Dict[str, str]]
    issue_board: Dict[str, Dict[str, str]]
    comms: Dict[str, Dict[str, str]]
    check_ins: Dict[str, Dict[str, str]]
    minutes: Dict[str, Dict[str, str]]

@dataclass
class IterationDetails:
    '''
    CLASS DOCSTRING
    ---------------

    '''

    milestone: int
    git_percentage: int
    project_percentage: int


    def __init__(self) -> None:
        '''
        CLASS METHOD DOCSTRING
        ---------------

        '''

        self.milestone = int(input('Enter the milestone: '))
        if self.milestone not in ITERATION_MAPPING:
            raise ValueError('Invalid milestone')
        self.git_percentage, self.project_percentage = ITERATION_MAPPING[self.milestone]

def generate_html_content(recipient_name: str) -> str:
    '''
    Generates HTML content for the message body of the personalised email being sent to the
    recipient

    Parameters
    ----------
    recipient_name : str
        name of the recipient receiving the email

    Returns
    -------
    str
        HTML string with no extra spaces or new lines that can be directly used to write the
        message body of the email
    '''

    content = str(
        html(
            head(),
            body(
                p(f"Hi {recipient_name}!"),
                p("PFA your individual feedback. You will need to open this markdown file in VS Code and open its preview to be able to easily read the file."),
                p(
                    "Cheers",
                    br(),
                    "Chitrakshi"
                ),
                p(
                    i(
                        "P.S. If you have any concerns about your marking, feel free to ",
                        a(href="https://teams.microsoft.com/l/chat/0/0?users=c.gosain@unsw.edu.au")("contact me on Teams"),
                        "."
                    )
                ),
                p(
                    b(
                        "This is an auto-generated email, please do not reply."
                    )
                )
            )
        )
    )

    return re.sub(r'(\s{2}|\n)', '', content)


def process_data(file_path: str) -> List[Tuple[Student, Feedback]]:
    '''
    Reads the csv file and processes the feedback based for every student based on predefined
    categories

    Parameters
    ----------
    file_path : str
        name of the .csv file containing the feedback data

    Returns
    -------
    List[Tuple[Student, Feedback]]
        list of tuples of student's zid and their corresponding feedback
    '''

    data: List[Tuple[Student, Feedback]] = []

    with open(file_path) as spreadsheet:
        reader = csv.reader(spreadsheet)

        for row in reader:
            zid, name, tute_group, *marks_and_comments = row
            tute, group = tute_group.split('_')
            categories = [field.name for field in fields(Feedback)]
            marks_and_comments = [(mark, comments) for mark, comments in zip(marks_and_comments[::2], marks_and_comments[1::2])]
            category_data = {category: {'mark': mark, 'comments': comments} for category, (mark, comments) in zip(categories, marks_and_comments)}

            student = Student(zid, name, tute, group)
            feedback = Feedback(**category_data)
            data.append((student, feedback))

    return data

def create_markdown(iteration: IterationDetails, student_info: Student, marking_data: Dict[str, Dict[str, Dict[str, str]]]) -> None:
    '''
    DESCRIPTION
    -----------

    Parameters
    ----------
    iteration : IterationDetails
    student_info : Student

    marking_data : Dict[str, Dict[str, Dict[str, str]]]

    '''

    if len(student_info.zid) != 7:
        raise Exception(f"Could not create markdown for {student_info.zid}, {student_info.name} from {student_info.tute}_{student_info.group} due to an invalid zid")

    with open(f"{student_info.tute}/IT{iteration.milestone}/{student_info.group}.md", 'r') as source_file:
        source_content = source_file.read()

    table_text = [
        'General', 'Information',
        'zID', student_info.zid,
        'NAME', student_info.name,
        'COURSE', 'COMP1531',
        'ITERATION', iteration.milestone,
        'CLASS', student_info.tute,
        'GROUP', student_info.group
    ]

    categories = {
        'git': {
            'header': f"Git Practices ({iteration.git_percentage}%)",
            'commits': 'Commits',
            'merge_reqs': 'Merge Requests',
            'tdd': 'Tests before implementation',
        },
        'project': {
            'header': f"Project Management & Teamwork ({iteration.project_percentage}%)",
            'issue_board': 'Gitlab board',
            'comms': 'Communications',
            'check_ins': 'Check in attendance',
            'minutes': 'Standups/meetings/minutes',
        }
    }

    md_file = MD(
        file_name=f"individuals/{student_info.zid}",
        title=f"COMP1531 Iteration {iteration.milestone} Feedback"
    )
    md_file.new_table(columns=2, rows=7, text=table_text, text_align='left')
    md_file.new_header(level=1, title='INDIVIDUAL MARK DISTRIBUTION')
    md_file.write(source_content)
    md_file.new_header(level=2, title='INDIVIDUALLY MARKED COMPONENTS')

    for category_data in categories.values():
        md_file.new_header(level=3, title=category_data['header'])
        for category_key, category_value in category_data.items():
            if category_key in marking_data:
                md_file.new_header(
                    level=4,
                    title=f"{category_value}: {marking_data[category_key]['mark']}"
                )
                comments = marking_data[category_key]['comments'].split(';')
                for comment in comments:
                    md_file.new_line(comment)

    md_file.new_line("Note: marks will be visible to you directly in 'give'. If you have any questions about the manual mark and need to discuss your marks with me, feel free to get in touch on Teams")
    md_file.new_line("P.S. this file was auto-generated from an excel sheet, if you believe there was an error or a typo, please bring it to my attention ASAP and I'll fix it, thanks!")
    md_file.new_line()

    md_file.create_md_file()

def send_feedback_email(iteration: IterationDetails, student: Student) -> None:
    '''
    DESCRIPTION
    -----------

    Parameters
    ----------
    iteration : IterationDetails
    student : Student

    '''

    recipient_email = f"z{student.zid}@ad.unsw.edu.au"
    subject = f"COMP1531 Iteration {iteration.milestone} individual feedback"
    message_body = generate_html_content(student.name)
    attachment_file = f"individuals/{student.zid}.md"

    try:
        emailer.send(
            to=recipient_email,
            subject=subject,
            contents=message_body,
            attachments=attachment_file
        )

        print(f"Email sent to {recipient_email}")
    except:
        print(f"Could not send email for {student.zid}, {student.name} from {student.tute}_{student.group}")


def main() -> None:
    '''
    DESCRIPTION
    -----------

    '''

    try:
        iteration = IterationDetails()
    except ValueError as err:
        sys.exit(err)

    data = process_data('23t3_my_marking.csv')

    for student, feedback in data:
        print(f"Starting {student.zid}, {student.name} from {student.tute}_{student.group}")
        try:
            create_markdown(iteration, student, feedback.__dict__)
            send_feedback_email(iteration, student)
            time.sleep(5)
        except Exception as err:
            sys.exit(err)

if __name__ == "__main__":
    main()

```