Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions apps/api/alembic/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

# Import all module models here so Base.metadata is fully populated for autogenerate.
from logica.ai.agents.models import ( # noqa: F401
AgentConfig,
CodeIntegrityAlert,
CurriculumPlan,
CurriculumPlanItem,
Expand Down Expand Up @@ -41,11 +40,7 @@
)
from logica.modules.notifications.models import Notification # noqa: F401
from logica.modules.observability.models import ErrorLog # noqa: F401
from logica.modules.progress.models import ( # noqa: F401
AcademicPeriod,
Badge,
StudentBadge,
)
from logica.modules.progress.models import Badge, StudentBadge # noqa: F401
from logica.modules.reports.models import ReportJob # noqa: F401
from logica.modules.rubrics.models import RubricItem, RubricRun # noqa: F401
from logica.modules.users.models import Institution, PasswordResetToken, User # noqa: F401
Expand Down
61 changes: 61 additions & 0 deletions apps/api/alembic/versions/024_quita_order_lines.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""quita el tipo de ejercicio order_lines

Revision ID: 024
Revises: 023
Create Date: 2026-07-30 00:00:00.000000

"""

from collections.abc import Sequence

from alembic import op

revision: str = "024"
down_revision: str | None = "877d0f5d3ae7"
branch_labels: Sequence[str] | None = None
depends_on: Sequence[str] | None = None

_OLD_VALUES = (
"true_false",
"multiple_choice",
"fill_code",
"find_error",
"trace_variables",
"order_lines",
"argued_response",
"live_code",
)
_NEW_VALUES = (
"true_false",
"multiple_choice",
"fill_code",
"find_error",
"trace_variables",
"argued_response",
"live_code",
)


def upgrade() -> None:
# Postgres no permite DROP VALUE de un enum, así que hay que recrear el
# tipo. La tabla `exercises` está vacía en producción a la fecha de esta
# migración, así que no hace falta backfill de filas con `order_lines`.
op.execute("ALTER TYPE exercise_type RENAME TO exercise_type_old")
new_values = ", ".join(f"'{v}'" for v in _NEW_VALUES)
op.execute(f"CREATE TYPE exercise_type AS ENUM ({new_values})")
op.execute(
"ALTER TABLE exercises ALTER COLUMN type TYPE exercise_type "
"USING type::text::exercise_type"
)
op.execute("DROP TYPE exercise_type_old")


def downgrade() -> None:
op.execute("ALTER TYPE exercise_type RENAME TO exercise_type_old")
old_values = ", ".join(f"'{v}'" for v in _OLD_VALUES)
op.execute(f"CREATE TYPE exercise_type AS ENUM ({old_values})")
op.execute(
"ALTER TABLE exercises ALTER COLUMN type TYPE exercise_type "
"USING type::text::exercise_type"
)
op.execute("DROP TYPE exercise_type_old")
53 changes: 53 additions & 0 deletions apps/api/alembic/versions/025_quita_periodos_academicos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""quita periodos académicos

Revision ID: 025
Revises: 024
Create Date: 2026-07-30 00:10:00.000000

"""

from collections.abc import Sequence

import sqlalchemy as sa
from alembic import op

revision: str = "025"
down_revision: str | None = "024"
branch_labels: Sequence[str] | None = None
depends_on: Sequence[str] | None = None


def upgrade() -> None:
# `curriculum_plan_items.period_id` era la única otra FK hacia
# `academic_periods` — apuntaba a una tabla inerte de la Fase 15 que ningún
# servicio leía ni escribía (ver `content/models.py::Topic.estimated_sessions`),
# así que se quita junto con la sección de periodos, sin backfill.
op.drop_column("curriculum_plan_items", "period_id")
op.drop_column("report_jobs", "period_id")
op.drop_table("academic_periods")


def downgrade() -> None:
op.create_table(
"academic_periods",
sa.Column("id", sa.UUID(), nullable=False),
sa.Column("institution_id", sa.UUID(), nullable=False),
sa.Column("name", sa.String(length=100), nullable=False),
sa.Column("start_date", sa.Date(), nullable=False),
sa.Column("end_date", sa.Date(), nullable=False),
sa.Column(
"created_at",
sa.DateTime(timezone=True),
server_default=sa.text("now()"),
nullable=False,
),
sa.Column(
"updated_at",
sa.DateTime(timezone=True),
server_default=sa.text("now()"),
nullable=False,
),
sa.PrimaryKeyConstraint("id"),
)
op.add_column("report_jobs", sa.Column("period_id", sa.UUID(), nullable=True))
op.add_column("curriculum_plan_items", sa.Column("period_id", sa.UUID(), nullable=True))
64 changes: 64 additions & 0 deletions apps/api/alembic/versions/026_quita_agent_configs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"""quita el interruptor por grupo de agentes IA

Revision ID: 026
Revises: 025
Create Date: 2026-07-30 00:20:00.000000

"""

from collections.abc import Sequence

import sqlalchemy as sa
from alembic import op

revision: str = "026"
down_revision: str | None = "025"
branch_labels: Sequence[str] | None = None
depends_on: Sequence[str] | None = None


def upgrade() -> None:
# Se quita solo el interruptor on/off por grupo (RF-30): los agentes en sí
# (AgentName, TASK_TIERS, prompts) siguen intactos y ahora quedan siempre
# activos — ver `docstring` de `AgentName` en ai/agents/models.py.
op.drop_table("agent_configs")
op.execute("DROP TYPE agent_name")


def downgrade() -> None:
# `sa.Enum(...)` en la columna de abajo crea el tipo `agent_name` en
# Postgres automáticamente al correr `create_table` — no hace falta un
# `CREATE TYPE` manual aparte.
op.create_table(
"agent_configs",
sa.Column("id", sa.UUID(), nullable=False),
sa.Column("group_id", sa.UUID(), nullable=False),
sa.Column(
"agent_name",
sa.Enum(
"progressive_hint",
"exercise_generation",
"grading_suggestion",
"summarize_group",
"code_integrity",
"guide_generation",
name="agent_name",
),
nullable=False,
),
sa.Column("enabled", sa.Boolean(), nullable=False),
sa.Column(
"created_at",
sa.DateTime(timezone=True),
server_default=sa.text("now()"),
nullable=False,
),
sa.Column(
"updated_at",
sa.DateTime(timezone=True),
server_default=sa.text("now()"),
nullable=False,
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("group_id", "agent_name", name="uq_agent_config_group"),
)
25 changes: 25 additions & 0 deletions apps/api/alembic/versions/027_porcentaje_evaluacion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""agrega weight_percent a evaluations

Revision ID: 027
Revises: 026
Create Date: 2026-07-31 00:00:00.000000

"""

from collections.abc import Sequence

import sqlalchemy as sa
from alembic import op

revision: str = "027"
down_revision: str | None = "026"
branch_labels: Sequence[str] | None = None
depends_on: Sequence[str] | None = None


def upgrade() -> None:
op.add_column("evaluations", sa.Column("weight_percent", sa.Float(), nullable=True))


def downgrade() -> None:
op.drop_column("evaluations", "weight_percent")
47 changes: 47 additions & 0 deletions apps/api/alembic/versions/028_asignaciones_examen_taller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""asignaciones: agrega examen (evaluation_id) y taller (guide_id)

Revision ID: 028
Revises: 027
Create Date: 2026-07-31 00:00:00.000000

"""

from collections.abc import Sequence

import sqlalchemy as sa
from alembic import op

revision: str = "028"
down_revision: str | None = "027"
branch_labels: Sequence[str] | None = None
depends_on: Sequence[str] | None = None


def upgrade() -> None:
op.add_column("assignments", sa.Column("evaluation_id", sa.UUID(), nullable=True))
op.add_column("assignments", sa.Column("guide_id", sa.UUID(), nullable=True))
op.create_foreign_key(
"fk_assignments_evaluation_id", "assignments", "evaluations", ["evaluation_id"], ["id"]
)
op.create_foreign_key("fk_assignments_guide_id", "assignments", "guides", ["guide_id"], ["id"])

op.drop_constraint("ck_assignment_topic_xor_exercise", "assignments", type_="check")
op.create_check_constraint(
"ck_assignment_exactly_one_target",
"assignments",
"(topic_id IS NOT NULL)::int + (exercise_id IS NOT NULL)::int + "
"(evaluation_id IS NOT NULL)::int + (guide_id IS NOT NULL)::int = 1",
)


def downgrade() -> None:
op.drop_constraint("ck_assignment_exactly_one_target", "assignments", type_="check")
op.create_check_constraint(
"ck_assignment_topic_xor_exercise",
"assignments",
"(topic_id IS NULL) <> (exercise_id IS NULL)",
)
op.drop_constraint("fk_assignments_guide_id", "assignments", type_="foreignkey")
op.drop_constraint("fk_assignments_evaluation_id", "assignments", type_="foreignkey")
op.drop_column("assignments", "guide_id")
op.drop_column("assignments", "evaluation_id")
2 changes: 2 additions & 0 deletions apps/api/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ dependencies = [
"prometheus-fastapi-instrumentator>=7.0",
"pyyaml>=6.0",
"slowapi>=0.1.10",
"pypdf>=5.0",
"python-docx>=1.1",
]

[dependency-groups]
Expand Down
18 changes: 0 additions & 18 deletions apps/api/scripts/seed.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,24 +278,6 @@ async def get_or_create_exercise(
"expected_trace": [{"contador": 2}, {"contador": 4}, {"contador": 6}],
},
)
await get_or_create_exercise(
pseint.id,
"Ordenar: cálculo de un promedio",
ExerciseType.order_lines,
{
"statement": (
"Ordena las líneas para que el algoritmo calcule correctamente "
"el promedio de dos notas."
),
"lines": [
"Escribir promedio",
"promedio <- (n1 + n2) / 2",
"Leer n1, n2",
"Definir n1, n2, promedio Como Real",
],
"correct_order": [3, 2, 1, 0],
},
)
await get_or_create_exercise(
pseint.id,
"Argumenta: Mientras vs. Repetir",
Expand Down
4 changes: 1 addition & 3 deletions apps/api/src/logica/ai/agents/code_integrity.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@
from redis.asyncio import Redis
from sqlalchemy.ext.asyncio import AsyncSession

from logica.ai.agents.config_service import ensure_agent_enabled
from logica.ai.agents.models import AgentName, CodeIntegrityAlert
from logica.ai.agents.models import CodeIntegrityAlert
from logica.ai.agents.repository import create_code_integrity_alert
from logica.ai.harness.structured import complete_structured
from logica.core.errors import NotFoundError, PermissionDeniedError, ValidationDomainError
Expand Down Expand Up @@ -56,7 +55,6 @@ async def check_integrity(
if evaluation is None or evaluation.institution_id != teacher.institution_id:
raise NotFoundError("Evaluación no encontrada")
await get_group_with_access(db, teacher, evaluation.group_id)
await ensure_agent_enabled(db, evaluation.group_id, AgentName.code_integrity)

answer = await get_answer_by_id(db, answer_id)
if answer is None:
Expand Down
22 changes: 0 additions & 22 deletions apps/api/src/logica/ai/agents/config_repository.py

This file was deleted.

Loading
Loading