
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.

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.

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

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.
- Compilar y distribuir los valores duplicados de los datos necesarios en una hoja de cálculo.
- 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.
- Usar la API de hojas de cálculo para obtener los datos.
- Usar pandas para refinar los datos.
- Crear documentos en formato hwp o hwpx usando plantillas en HWP y pywin32.
- 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

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:

- 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.

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:
- Configurar el uso de la API
- Configurar la pantalla de consentimiento de OAuth
- Aprobar credenciales para aplicaciones de escritorio
- Instalar la biblioteca cliente de Google
- 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.

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.

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

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.

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.
댓글을 불러오는 중...