Automatización del trabajo escolar - Automatización de documentos del Comité de Educación para la Vida

힘센캥거루
2024년 12월 22일
62
python

Automatización del trabajo escolar - Automatización de documentos del Comité de Educación para la Vida-1

Soñé con ser maestro para educar, pero dedicamos demasiado tiempo a cosas que no son educación.

Por ello, en este artículo quiero presentar la automatización del manejo de asistencia en el Comité de Educación para la Vida.

El proceso de guiar directamente a los estudiantes no se puede automatizar, pero el proceso de documentación sí.

Los temas muy largos se han sustituido por enlaces.

1. Comité de Educación para la Vida

Originalmente, los estudiantes del Comité de Educación para la Vida pasaban por el siguiente proceso de gestión.

Automatización del trabajo escolar - Automatización de documentos del Comité de Educación para la Vida-2

Por cada estudiante se necesitan al menos 4 documentos, así que para 10 estudiantes serían 40.

Entre ellas, la asistencia era una parte bastante complicada.

Se debía registrar individualmente las ausencias no justificadas de cada estudiante en el documento.

Automatización del trabajo escolar - Automatización de documentos del Comité de Educación para la Vida-3

Originalmente, recibía un documento de Word con la asistencia de cada estudiante del tutor y los verificaba uno por uno manualmente.

Debido a que este proceso era tan tedioso, decidí terminarlo todo de una sola vez con hojas de cálculo y python.

2. Idea

Automatización del trabajo escolar - Automatización de documentos del Comité de Educación para la Vida-4

En la programación, la idea en sí misma es más importante que la habilidad.

Pensemos en cómo automatizar el proceso de creación de documentos.

  1. Compilar y distribuir los valores duplicados de los datos necesarios en una hoja de cálculo.
  2. Recibir del tutor la cantidad de días de ausencia no justificada de los estudiantes del Comité de Educación para la Vida y la opinión del tutor. Antes esto se hacía en papel, ahora se recopila en una hoja de cálculo.
  3. Usar la API de hojas de cálculo para obtener los datos.
  4. Usar pandas para refinar los datos.
  5. Crear documentos en formato hwp o hwpx usando plantillas en HWP y pywin32.
  6. Obtener confirmación del asunto de los tutores y estudiantes, y escanear todo para aprobación.

A través de este proceso, se automatizó la generación de documentos.

3. Recopilación de datos necesarios

Automatización del trabajo escolar - Automatización de documentos del Comité de Educación para la Vida-5

Antes de empezar, es tiempo de revisar qué datos necesitan cada uno de los documentos.

  • Declaración del estudiante
    • Nombre, Número de estudiante, Género, Título del asunto, Ausencias no justificadas del estudiante, Nombre del estudiante
  • Certificado de hechos (escrito por el tutor)
    • Nombre del estudiante, Número de estudiante, Género, Título del asunto, Nombre del tutor
  • Informe de investigación del caso del estudiante
    • Nombre, Número de estudiante, Género, Título del asunto, Fecha y hora del incidente, Lugar del incidente, Estudiantes involucrados, Detalles del caso, Reglamento escolar relacionado, Fecha de redacción, Maestro redactor
  • Solicitud de asistencia y presentación de opiniones al Comité de Educación para la Vida
    • Fecha y lugar de celebración, Nombre, Número de estudiante, Fecha y hora del incidente, Lugar del incidente, Acto infractor, Normativa relacionada, Fecha de redacción
  • Nota de opinión escrita
    • Nombre del estudiante relacionado Nombre, Número de estudiante, Nombre del tutor, Relación con el estudiante
  • Sobre postal
    • Nombre del estudiante, Código postal, Dirección

Los datos comunes necesarios para estos documentos se han destacado en negrita.

Si se hace en una hoja de cálculo, se pueden configurar eliminando duplicados de datos en las columnas de índice.

Hagamos la hoja.

4. Crear hoja de cálculo y configurar API

La hoja se ha creado de la siguiente manera:

Automatización del trabajo escolar - Automatización de documentos del Comité de Educación para la Vida-6

  • Información personal del estudiante
    • Número de estudiante, Nombre, Apellido
  • Contenido
    • Número de asunto, Fecha del incidente, Ausencias no justificadas, Opinión del tutor, Fecha de redacción, Fecha del Comité de Educación para la Vida

La razón de la ausencia de detalles del asunto, actos infractores y reglamentaciones escolares aplicables se debe a que este asunto está vinculado a la asistencia.

Ahora debemos configurar la API para acceder a la hoja de cálculo.

Automatización del trabajo escolar - Automatización de documentos del Comité de Educación para la Vida-7

Originalmente quería cubrir hasta la configuración de la API, pero parecía que tardaría semanas en escribir el artículo, así que lo cambié por un enlace.

Para la configuración de la API de la hoja de cálculo, consulta la documentación oficial o el velog de junsugi.

El proceso general de configuración de la API de la hoja de cálculo es el siguiente:

  1. Configurar el uso de la API
  2. Configurar la pantalla de consentimiento de OAuth
  3. Aprobar credenciales para aplicaciones de escritorio
  4. Instalar la biblioteca cliente de Google
  5. Uso

5. Configuración del documento en Hangul

Antes de usar python, es necesario configurar el documento en Hangul.

Dado que consumiría mucho tiempo procesar todo solo con la codificación, planeo asignar posiciones para los datos utilizando campos de entrada.

Automatización del trabajo escolar - Automatización de documentos del Comité de Educación para la Vida-8

Ingrese el nombre de cada campo en la posición donde se ingresarán los datos del documento, como el informe de investigación de casos del estudiante y la declaración del estudiante.

Lo importante es que el nombre del campo coincida con el nombre de la columna de la hoja de cálculo.

Asigné formatos a los documentos como se muestra a continuación.

Automatización del trabajo escolar - Automatización de documentos del Comité de Educación para la Vida-9

Si encuentras alguna dificultad, consulta la Tistory de codificación cotidiana.

6. Código Python

Primero, instala las bibliotecas necesarias usando pip.

pip install pywin32 pandas google-api-python-client google-auth-httplib2 google-auth-oauthlib

Luego, crea un archivo py o ipynb e importe los módulos necesarios.

Por experiencia, es más cómodo tener las variables necesarias para la gestión en la parte superior, así que coloque cosas como la ruta o el nombre del tutor allí.

Si es mucho, también es buena idea gestionarlo con un archivo json.

import win32com.client as win32
import pandas as pd
import time
import pathlib
import datetime as dt
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError

hwp = win32.gencache.EnsureDispatch('HWPFrame.HwpObject')
hwp.RegisterModule('FilePathCheckDLL', 'SecurityModule')

rootpath = pathlib.Path.cwd()/"school"/"guidance"
errorfilepath = rootpath / "Informe de errores.txt"
beforefilespath = rootpath / "format" / "before"
rulesdfpath = rootpath / "Lista del Comité de Educación.xlsx"
addressdfpath = rootpath / "format" / "info.xlsx"
phonedfpath = rootpath / "format" / "phoneNumber.xlsx"
lastsavepath = rootpath / "created"
backuppath = rootpath / "backup"
lastsavepath.mkdir(exist_ok=True)
        
teachers = {301:"Kim", 302:"Park", 303: "Nam", 304:"Sang"} 

Tomé el código necesario para la hoja de cálculo de la documentación oficial.

Parece complicado al verlo, pero simplemente toma la id de la hoja y el rango de datos para devolverlos.

def main(spreadID, spreadRange):
    SCOPES = ["https://www.googleapis.com/auth/spreadsheets"]

    creds = None
    
    if pathlib.Path(f"{rootpath}/jsonFiles/token.json").exists():
        creds = Credentials.from_authorized_user_file(f"{rootpath}/jsonFiles/token.json", SCOPES)

    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                f"{rootpath}/jsonFiles/credentials.json", SCOPES
            )
            creds = flow.run_local_server(port=0)
            
        with open(f"{rootpath}/jsonFiles/token.json", "w") as token:
            token.write(creds.to_json())

    try:
        service = build("sheets", "v4", credentials=creds)

        sheet = service.spreadsheets()
        result = (
            sheet.values()
            .get(spreadsheetId=spreadID, range=spreadRange)
            .execute()
        )
        values = result.get("values", [])

        if not values:
            print("No data found.")
            return
    
        return values
    except HttpError as err:
        print(err)

Ahora es el turno de procesar los datos devueltos en un dataframe.

Descarga los datos de la hoja, elimina los datos innecesarios y procesa los datos.

Todos los reglamentos escolares aplicables y las direcciones de los estudiantes se emparejaron utilizando el número del estudiante como clave.

def load_checkdata():
    # Llamar la hoja de cálculo y procesar datos
    SPREADSHEET_ID = "Su clave de API de hoja de cálculo"
    SPREADRANGE = "Nombre de la hoja!A8:V200"
    sheet = main(SPREADSHEET_ID, SPREADRANGE)
    df_origin = pd.DataFrame(sheet[1:], columns=sheet[0])
    df_origin.drop("Nº", axis=1, inplace=True)
    df_origin.reset_index(drop=True, inplace=True)
    df_origin.dropna(subset="Número de estudiante", inplace=True)
    df_origin = df_origin.loc[df_origin['Número de estudiante'] != ""]
    df_origin.reset_index(drop=True, inplace=True)
    df_origin.fillna("", inplace=True)
    df_origin["Título del asunto"] = "Falta de asistencia"
    df_origin["Lugar del incidente"] = "No aplica"
    df_origin["Reglamento escolar"] = "Artículo 1: estudiantes con ausencias injustificadas habituales"
    df_origin["Acto infractor"] = "Falta de asistencia"
    df_origin["Estado de cierre"] = df_origin["Estado de cierre"].str.replace("TRUE", "Finalizado")
    df_origin.rename(columns={"Opinión del tutor":"Detalles del asunto"},inplace=True)
    
    # A veces el número de estudiante no se reconoce, así que se convierte a número
    excel["Número de estudiante"] = pd.to_numeric(excel["Número de estudiante"])
    excel.sort_values("Número de estudiante", inplace=True)
    excel.reset_index(drop=True, inplace=True)

    # Ingresar nombre del tutor
    excel["Tutor"]="Tutor"
    for student in range(len(excel)):
        studentClass = int(excel.loc[student, "Número de estudiante"])//100
        excel.loc[student, "Tutor"] = teachers[studentClass]

    excel = excel.dropna(subset=["Número de estudiante"])
    excel = excel.drop(excel[excel['Estado de cierre']=="Finalizado"].index)
    
    # Dirección basada en el número de estudiante
    address = pd.read_excel(addressdfpath)
    phone = pd.read_excel(phonedfpath)
    rules = pd.read_excel(rulesdfpath, sheet_name="Sheet2")
    
    excel = pd.merge(excel, address, on="Número de estudiante", how="left")
    excel = pd.merge(excel, phone, on="Número de estudiante", how="left")
    
    # Ingresar elementos infractores
    add_columns = ["Elementos infractores"]
    excel[add_columns] = ""
    for i in range(len(excel)):
        # Recopilar elementos infractores
        student_rules = []
        for j in range(len(rules)):
            if rules.loc[j, "Contenido"] in excel.loc[i, "Reglamento escolar"]:
                student_rules.append(rules['Elemento'][j])
        excel.loc[i, "Elementos infractores"] = ", ".join(student_rules)
    
    excel.reset_index(drop=True, inplace=True)
    excel['Número de estudiante']=excel["Número de estudiante"].astype(int)
    if len(excel) < 1 :
        return 

    # Hacer un respaldo en caso de que el documento original se dañe
    backupfilename = dt.datetime.now().strftime("%Y%m%d %H")
    excel.to_excel(backuppath / f"{backupfilename}_backup.xlsx")

    return excel

Con esto se obtiene un DataFrame con la dirección del estudiante, código postal y demás datos.

El número de columnas del DataFrame es considerable, pero no importa ya que la memoria es suficiente.

Los códigos que se usan frecuentemente los he modularizado.

# Copiar la página tantas veces como el número de filas de la hoja
def copypage(name, df):
    name.SetActive_XHwpDocument()
    if len(df) > 1:
        hwp.MovePos(3)
        hwp.Run('SelectAll')
        hwp.Run('Copy')
        hwp.MovePos(3)
        for i in range(len(df)-1):
            hwp.MovePos(3)
            hwp.Run('Paste')
            hwp.MovePos(3)  
        hwp.Run('DeleteBack')
    hwp.Run('DeleteBack')

# Ingresar texto en campos de un documento de Hangul específico
# Activa el documento de Hangul con el nombre name e ingresa datos de df en los campos
def fillpage(name, df, field_lst : list):
    name.SetActive_XHwpDocument()
    for page in range(len(df)):
            for field in field_lst :  
                hwp.PutFieldText(f'{field}{{{{{page}}}}}', df[field].iloc[page])

Ahora solo queda crear los documentos en Hangul utilizando estos datos.

Usa load_checkdata para obtener los datos y pywin32 para ingresar los valores adecuados en los campos de cada documento.

Lamento que el código sea un poco extenso.

Si te resulta difícil leerlo debido a que es un mal código, por favor toma solo mi idea como referencia.

def start():
    # Primero carga los datos de Excel.
    excel_check = load_checkdata()
    if excel is None:
        return
        
    if len(excel) < 1 :
        return

    # Abre todos los archivos en la carpeta de los formatos disponibles
    beforefiles = list(beforefilespath.iterdir())
    for file in beforefiles:
        hwp.Run('FileNew')        
        hwp.Open(file)

    # Asignar nombres de variable
    offical = hwp.XHwpDocuments.Item(1)
    report_check = hwp.XHwpDocuments.Item(2)
    student_excuse = hwp.XHwpDocuments.Item(3)
    confire = hwp.XHwpDocuments.Item(4)
    mail = hwp.XHwpDocuments.Item(5)
    demand = hwp.XHwpDocuments.Item(6)
    
    # Crear documento oficial
    offical.SetActive_XHwpDocument()
    dateDict = {0: 'Lun', 1:'Mar', 2:'Mié', 3:'Jue', 4:'Vie', 5:'Sáb', 6:'Dom'}
    date_origin = excel['Fecha del Comité de Educación'].iloc[0]
    date = dt.datetime.strptime(str(date_origin), '%Y년 %m월 %d일')
    dow = dateDict[date.weekday()]
    date_strf = dt.datetime.strftime(date, '%Y.%m.%d.')
    
    once = ["Fecha del Comité de Educación", "Día", "Asunto", "Número de personas"]
    tables = ["Actos infractores", "Número de estudiante", "Nombre", "Número de asunto"]
    guidanceType = excel["Actos infractores"].unique()
    for one in once : 
        if one == "Día":
            hwp.PutFieldText(f'{one}{{{{{0}}}}}', dow)
            continue
        elif one =="Asunto":
            if len(guidanceType) == 1:
                    hwp.PutFieldText(f'{one}{{{{{0}}}}}', f"{guidanceType[0]} 1 caso")
            else :
                hwp.PutFieldText(f'{one}{{{{{0}}}}}', f"{guidanceType[0]} y {len(guidanceType)-1} más")
            continue
        elif one =="Número de personas":
            hwp.PutFieldText(f'{one}{{{{{0}}}}}', len(excel))
            continue

        hwp.PutFieldText(f'{one}{{{{{0}}}}}', date_strf)

    for student in range(len(excel)):
        for table in tables :
            if table == "Número de estudiante":
                studentGrade = int(excel['Número de estudiante'].iloc[student])//10000
                studentClass = int(excel['Número de estudiante'].iloc[student])//100 - studentGrade*100
                hwp.PutFieldText(f'Número de estudiante{{{{{student}}}}}', f"{studentGrade}-{studentClass}")
                continue
            if table == "Nombre":
                studentName = excel['Nombre'].iloc[student]
                hwp.PutFieldText(f'Nombre{{{{{student}}}}}', f"{studentName[0]}{(len(studentName)-1)*'O'}")
                continue
            hwp.PutFieldText(f'{table}{{{{{student}}}}}', excel[table].iloc[student])
    hwp.SaveAs(lastsavepath / beforefiles[0].name)

    # Crear informe de investigación del caso
    report_check.SetActive_XHwpDocument()
    field_list = [i for i in hwp.GetFieldList().split('\x02')]
    copypage(report_check, excel_check)
    fillpage(report_check, excel_check, field_list)
    hwp.SaveAs(lastsavepath / beforefiles[1].name)

    # Crear declaración propia
    student_excuse.SetActive_XHwpDocument()
    field_list = [i for i in hwp.GetFieldList().split('\x02')]
    copypage(student_excuse, excel_check)
    fillpage(student_excuse, excel_check, field_list)
    hwp.Run('DeleteBack')
    hwp.SaveAs(lastsavepath / beforefiles[2].name)

    # Crear certificado de recepción
    confire.SetActive_XHwpDocument()
    field_list = [i for i in hwp.GetFieldList().split('\x02')]
    copypage(confire, excel)
    fillpage(confire, excel, field_list)
    hwp.SaveAs(lastsavepath / beforefiles[3].name)
    
    # Crear formato de sobres
    mail.SetActive_XHwpDocument()
    field_list = [i for i in hwp.GetFieldList().split('\x02')]
    copypage(mail, excel)
    fillpage(mail, excel, field_list)
    for page in range(len(excel)):
        hwp.PutFieldText(f'Código postal{{{{{page}}}}}', (" ").join(list(str(int(excel["Código postal"].iloc[page])))))
    hwp.SaveAs(lastsavepath / beforefiles[4].name)

    # Crear formulario de orientación y solicitud de asistencia
    demand.SetActive_XHwpDocument()
    field_list = [i for i in hwp.GetFieldList().split('\x02')]
    copypage(demand, excel)
    fillpage(demand, excel, field_list)
    todayDate = dt.date.today().strftime("%Y년 %m월 %d일")
    for page in range(len(excel)):
        hwp.PutFieldText(f'Fecha de redacción{{{{{page}}}}}', todayDate)
    hwp.SaveAs(lastsavepath / beforefiles[5].name)
    hwp.Quit()

Al crear los archivos y recibir el contenido de la asistencia del tutor a través de la hoja de cálculo, el proceso se completa de una sola vez ejecutando la función start().

Por supuesto, todavía es necesario verificar personalmente los hechos con los estudiantes, pero el tiempo consumido en la creación de documentos se reduce significativamente.

Con este método, se puede automatizar todo el proceso documental del documento preliminar del Comité de Educación para la Vida, documentos posteriores, y la solicitud de educación especial.

7. Conclusión

Automatización del trabajo escolar - Automatización de documentos del Comité de Educación para la Vida-10

Algunos podrían preguntarse esto.

¿No se tarda más tiempo en escribir el código?

Por supuesto, se tomó el mismo tiempo en aprender y escribir el código que el tiempo que se habría gastado escribiendo documentos para el Comité de Educación para la Vida.

Sin embargo, el primero no contribuye al desarrollo personal, mientras que el segundo es de gran ayuda.

Además, podía dedicar más tiempo a preparar las clases.

Automatización del trabajo escolar - Automatización de documentos del Comité de Educación para la Vida-11

Lamentablemente, todavía no tengo la expectativa de que las oficinas educativas o el Ministerio de Educación desarrollen este tipo de programas para los maestros.

Probablemente no se podrá esperar en el futuro.

Por eso debemos ser autosuficientes.

Espero que este artículo sea una idea para alguien.

댓글을 불러오는 중...