Skip to content

Commit

Permalink
Merge pull request #466 from open-source-uc/dev
Browse files Browse the repository at this point in the history
Actualizar `main`
  • Loading branch information
kovaxis authored Jul 19, 2024
2 parents 57fcd9b + f9b3119 commit cf76167
Show file tree
Hide file tree
Showing 20 changed files with 260 additions and 143 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ Este es el hogar para el desarrollo del nuevo Planner de Ingeniería UC, hecho p

Tras varios años en ideación, este proyecto se lanzó como [una propuesta conjunta](https://drive.google.com/file/d/1IxAJ8cCzDkayPwnju5kgc2oKc7g9fvwf/view) entre la Consejería Académica de Ingeniería y Open Source UC, con el propósito de reemplazar el [actual planner de Ingeniería](https://planner.ing.puc.cl/). La propuesta, tras ser aprobada por la Escuela de Ingeniería, dió comienzo al proyecto en modalidad de marcha blanca. A principios del 2023, y con un MVP listo, la Dirección de Pregrado oficialmente aprobó la continuación del desarrollo del proyecto.

## ¿Cómo agregar funcionalidades o modificar Mallas ING?

1. **Hacer un fork** de este repositorio.
2. Realizar los cambios en tu repo forkeado, trabajando en la rama `dev`.
3. **Crear un Pull Request (PR)** desde la rama `dev` de tu repo hacia la rama `dev` de este repo.
4. Esperar a que el equipo del Nuevo Planner + OSUC acepte tu solicitud.
5. Una vez aceptado, el equipo del Nuevo Planner + OSUC abre un **PR** desde `dev` hacia `main` en este mismo repositorio.
6. Luego, el equipo del Nuevo Planner + OSUC podrá hacer un **PR** desde `main` hacia `main` del repositorio controlado por la universidad: [spavea/mallas.ing.uc.cl](https://github.com/spavea/mallas.ing.uc.cl).
7. Finalmente, solo queda esperar a que la universidad apruebe los cambios. El deploy se hará automáticamente al recibir la aprobación 🚀

## Instalación y desarrollo

El proyecto está configurado para ser desarrollado en [Visual Studio Code](https://code.visualstudio.com/) con [Dev Containers](https://code.visualstudio.com/docs/remote/containers). Puedes [instalar VSCode aquí](https://code.visualstudio.com/download). Existen 2 maneras de correr Dev Containers: GitHub Codespaces y localmente.
Expand Down
10 changes: 3 additions & 7 deletions backend/app/plan/validation/courses/simplify.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,9 +242,7 @@ def _anihil_rule(
new.clear()
new.append(Const(value=not op.neutral))
anihilate[0] = True
if anihilate[0]:
return True
return False
return bool(anihilate[0])


def anihil(expr: Operator) -> Expr:
Expand All @@ -255,10 +253,8 @@ def anihil(expr: Operator) -> Expr:


def _ident_rule(ctx: None, op: Operator, new: list[Expr], child: Expr) -> bool:
if isinstance(child, Const) and child.value == op.neutral:
# Skip this child, since it adds nothing to the expression
return True
return False
# Skip this child, since it adds nothing to the expression
return isinstance(child, Const) and child.value == op.neutral


def ident(expr: Operator) -> Expr:
Expand Down
4 changes: 1 addition & 3 deletions backend/app/plan/validation/courses/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,4 @@ def is_course_indirectly_available(courseinfo: CourseInfo, code: str):
info = courseinfo.try_course(code)
if info is None:
return False
if courseinfo.is_available(info.canonical_equiv):
return True
return False
return courseinfo.is_available(info.canonical_equiv)
2 changes: 1 addition & 1 deletion backend/app/sync/curriculums/scrape/translate.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def __init__(
if bloque.CodSigla is not None:
equivalents: list[str] = [bloque.CodSigla]
if bloque.Equivalencias is not None:
for equivalent in bloque.Equivalencias.Cursos:
for equivalent in bloque.Equivalencias.Cursos or []:
if equivalent.Sigla is not None:
equivalents.append(equivalent.Sigla)
if len(equivalents) > 1:
Expand Down
12 changes: 6 additions & 6 deletions backend/app/sync/curriculums/siding.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ def _filter_relevant_cyears(cyears: StringArray | None) -> bool:
if cyears is None:
return False
cyears.strings.string = [
cyear for cyear in cyears.strings.string if cyear >= IGNORE_CYEARS_BEFORE
cyear for cyear in cyears.strings.string or [] if cyear >= IGNORE_CYEARS_BEFORE
]
return len(cyears.strings.string) > 0

Expand All @@ -161,7 +161,7 @@ async def _fetch_siding_plans(siding: SidingInfo):
for major in siding.majors:
if major.Curriculum is None:
continue
for cyear_str in major.Curriculum.strings.string:
for cyear_str in major.Curriculum.strings.string or []:
cyear = cyear_from_str(cyear_str)
if cyear is None:
log.error(
Expand All @@ -184,7 +184,7 @@ async def _fetch_siding_plans(siding: SidingInfo):
for minor in siding.minors:
if minor.Curriculum is None:
continue
for cyear_str in minor.Curriculum.strings.string:
for cyear_str in minor.Curriculum.strings.string or []:
cyear = cyear_from_str(cyear_str)
if cyear is None:
log.error(
Expand All @@ -207,7 +207,7 @@ async def _fetch_siding_plans(siding: SidingInfo):
for title in siding.titles:
if title.Curriculum is None:
continue
for cyear_str in title.Curriculum.strings.string:
for cyear_str in title.Curriculum.strings.string or []:
cyear = cyear_from_str(cyear_str)
if cyear is None:
log.error(
Expand Down Expand Up @@ -278,7 +278,7 @@ def translate_siding(
codes = [main_code]
if raw_block.Equivalencias is not None:
# Add equivalences to the list
for curso in raw_block.Equivalencias.Cursos:
for curso in raw_block.Equivalencias.Cursos or []:
if curso.Sigla is not None and curso.Sigla != main_code:
codes.append(curso.Sigla)
list_code = (
Expand Down Expand Up @@ -385,7 +385,7 @@ def _fill_in_c2022_titles(courses: dict[str, CourseDetails], siding: SidingInfo)
if title.Curriculum is None:
continue
cyears = title.Curriculum.strings.string
if "C2020" in cyears and "C2022" not in cyears:
if cyears and "C2020" in cyears and "C2022" not in cyears:
cyears.append("C2022")
siding.plans["C2022"].plans[title.CodTitulo] = siding.plans["C2020"].plans[
title.CodTitulo
Expand Down
106 changes: 51 additions & 55 deletions backend/app/sync/siding/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@
from decimal import Decimal
from io import BytesIO
from pathlib import Path
from typing import Annotated, Any, Final, Generic, Literal, TypeVar
from typing import Annotated, Any, Final, Literal, TypeVar

import httpx
import zeep
from pydantic import BaseModel, ConstrainedStr, Field, parse_obj_as
from pydantic.generics import GenericModel
from zeep import AsyncClient
from zeep.transports import AsyncTransport

Expand All @@ -20,7 +19,7 @@


class StringArrayInner(BaseModel):
string: list[str]
string: list[str] | None


class StringArray(BaseModel):
Expand Down Expand Up @@ -80,7 +79,7 @@ class Curso(BaseModel):


class ListaCursos(BaseModel):
Cursos: list[Curso]
Cursos: list[Curso] | None


class Restriccion(BaseModel):
Expand All @@ -89,11 +88,11 @@ class Restriccion(BaseModel):


class ListaRestricciones(BaseModel):
Restricciones: list[Restriccion]
Restricciones: list[Restriccion] | None


class ListaRequisitos(BaseModel):
Cursos: list[Curso]
Cursos: list[Curso] | None


class BloqueMalla(BaseModel):
Expand Down Expand Up @@ -210,22 +209,19 @@ class SeleccionEstudiante(BaseModel):
T = TypeVar("T")


class NullableList(GenericModel, Generic[T]):
"""
SIDING has the horrible tendency to return `None` instead of empty lists.
This patches that.
"""

__root__: list[T] | None

def to_list(self) -> list[T]:
return [] if self.__root__ is None else self.__root__
def parse_nullable_list(ty: type[T], value: Any) -> list[T]: # noqa: ANN401
if value is None:
return []
return parse_obj_as(
list[ty],
value,
)


def decode_cyears(cyears: StringArray | None) -> list[str]:
if cyears is None:
return []
return cyears.strings.string
return cyears.strings.string or []


class SoapClient:
Expand Down Expand Up @@ -356,38 +352,38 @@ async def get_majors() -> list[Major]:
# with open("log.txt", "a") as f:
# print(resp.content, file=f)

return parse_obj_as(
NullableList[Major],
return parse_nullable_list(
Major,
await client.call_endpoint("getListadoMajor", {}),
).to_list()
)


async def get_minors() -> list[Minor]:
"""
Obtain a global list of all minors.
"""
return parse_obj_as(
NullableList[Minor],
return parse_nullable_list(
Minor,
await client.call_endpoint("getListadoMinor", {}),
).to_list()
)


async def get_titles() -> list[Titulo]:
"""
Obtain a global list of all titles.
"""
return parse_obj_as(
NullableList[Titulo],
return parse_nullable_list(
Titulo,
await client.call_endpoint("getListadoTitulo", {}),
).to_list()
)


async def get_minors_for_major(major_code: str) -> list[Minor]:
"""
Obtain a list of minors that are a valid choice for each major.
"""
return parse_obj_as(
list[Minor],
return parse_nullable_list(
Minor,
await client.call_endpoint("getMajorMinorAsociado", {"CodMajor": major_code}),
)

Expand All @@ -396,26 +392,26 @@ async def get_courses_for_spec(study_spec: PlanEstudios) -> list[Curso]:
"""
Get pretty much all the courses that are available to a certain study spec.
"""
return parse_obj_as(
NullableList[Curso],
return parse_nullable_list(
Curso,
await client.call_endpoint(
"getConcentracionCursos",
study_spec.dict(),
),
).to_list()
)


async def get_curriculum_for_spec(study_spec: PlanEstudios) -> list[BloqueMalla]:
"""
Get a list of curriculum blocks for the given spec.
"""
return parse_obj_as(
NullableList[BloqueMalla],
return parse_nullable_list(
BloqueMalla,
await client.call_endpoint(
"getMallaSugerida",
study_spec.dict(),
),
).to_list()
)


async def get_equivalencies(course_code: str, study_spec: PlanEstudios) -> list[Curso]:
Expand All @@ -429,16 +425,16 @@ async def get_equivalencies(course_code: str, study_spec: PlanEstudios) -> list[
For example, 'FIS1514' has 3 equivalencies, including 'ICE1514'.
However, 'ICE1514' has zero equivalencies.
"""
return parse_obj_as(
NullableList[Curso],
return parse_nullable_list(
Curso,
await client.call_endpoint(
"getCursoEquivalente",
{
"Sigla": course_code,
}
| study_spec.dict(),
),
).to_list()
)


async def get_requirements(course_code: str, study_spec: PlanEstudios) -> list[Curso]:
Expand All @@ -448,16 +444,16 @@ async def get_requirements(course_code: str, study_spec: PlanEstudios) -> list[C
represented as a logical expression and not as a list.
These requirements are only a heuristic.
"""
return parse_obj_as(
NullableList[Curso],
return parse_nullable_list(
Curso,
await client.call_endpoint(
"getRequisito",
{
"Sigla": course_code,
}
| study_spec.dict(),
),
).to_list()
)


async def get_restrictions(
Expand All @@ -469,16 +465,16 @@ async def get_restrictions(
This is actually broken, Banner restrictions are represented as a logical
expression and not as a list.
"""
return parse_obj_as(
NullableList[Restriccion],
return parse_nullable_list(
Restriccion,
await client.call_endpoint(
"getRestriccion",
{
"Sigla": course_code,
}
| study_spec.dict(),
),
).to_list()
)


async def get_predefined_list(list_code: str) -> list[Curso]:
Expand All @@ -487,10 +483,10 @@ async def get_predefined_list(list_code: str) -> list[Curso]:
Returns `null` if the list is empty.
"""

return parse_obj_as(
NullableList[Curso],
return parse_nullable_list(
Curso,
await client.call_endpoint("getListaPredefinida", {"CodLista": list_code}),
).to_list()
)


async def get_student_info(rut: Rut) -> InfoEstudiante:
Expand All @@ -507,10 +503,10 @@ async def get_student_done_courses(rut: Rut) -> list[CursoHecho]:
"""
Get the information associated with the given student, by RUT.
"""
return parse_obj_as(
NullableList[CursoHecho],
return parse_nullable_list(
CursoHecho,
await client.call_endpoint("getCursosHechos", {"rut": rut}),
).to_list()
)


async def get_student_current_courses(rut: Rut) -> list[CursoInscrito]:
Expand All @@ -519,10 +515,10 @@ async def get_student_current_courses(rut: Rut) -> list[CursoInscrito]:
Not sure when exactly are current courses converted into past courses.
"""
return parse_obj_as(
NullableList[CursoInscrito],
return parse_nullable_list(
CursoInscrito,
await client.call_endpoint("getCargaAcademica", {"rut": rut}),
).to_list()
)


async def get_student_selections(rut: Rut) -> list[SeleccionEstudiante]:
Expand All @@ -531,10 +527,10 @@ async def get_student_selections(rut: Rut) -> list[SeleccionEstudiante]:
Not sure when exactly are current courses converted into past courses.
"""
return parse_obj_as(
NullableList[SeleccionEstudiante],
return parse_nullable_list(
SeleccionEstudiante,
await client.call_endpoint("getSeleccionesEstudiante", {"rut": rut}),
).to_list()
)


async def get_current_period() -> AcademicPeriod:
Expand Down
2 changes: 1 addition & 1 deletion backend/app/sync/siding/translate.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def _decode_curriculum_versions(input: StringArray | None) -> list[str]:
# too unreliable
logging.warning("null curriculum version list")
return []
return input.strings.string
return input.strings.string or []


def _decode_period(period: str) -> tuple[int, int]:
Expand Down
Loading

0 comments on commit cf76167

Please sign in to comment.