Skip to content

Commit

Permalink
wip: Luokalle jääneiden massaluovutuskysely
Browse files Browse the repository at this point in the history
  • Loading branch information
ilkkahanninen committed Jan 20, 2025
1 parent 350e99c commit 0ee455c
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import fi.oph.koski.koskiuser.KoskiSpecificSession
import fi.oph.koski.localization.LocalizationReader
import fi.oph.koski.raportit.{DataSheet, ExcelWriter, OppilaitosRaporttiResponse}
import fi.oph.koski.util.CsvFormatter
import org.json4s.JValue
import org.json4s.jackson.JsonMethods
import software.amazon.awssdk.core.internal.sync.FileContentStreamProvider
import software.amazon.awssdk.http.ContentStreamProvider

Expand Down Expand Up @@ -57,6 +59,9 @@ case class QueryResultWriter(
def putJson[T: TypeTag](name: String, obj: T)(implicit user: KoskiSpecificSession): Unit =
putJson(name, JsonSerializer.write(obj))

def putJson(name: String, json: JValue): Unit =
putJson(name, JsonMethods.pretty(json))

def createCsv[T <: Product](name: String, partitionSize: Option[Long])(implicit manager: Using.Manager): CsvStream[T] =
manager(new CsvStream[T](s"$queryId-$name", partitionSize, { (file, partition) =>
results.putFile(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package fi.oph.koski.massaluovutus.luokallejaaneet

import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.node.JsonNodeFactory
import com.github.fge.jsonpatch.JsonPatch
import fi.oph.koski.config.KoskiApplication
import fi.oph.koski.db.PostgresDriverWithJsonSupport.plainAPI._
import fi.oph.koski.db.{DB, DatabaseConverters, QueryMethods}
import fi.oph.koski.history.OpiskeluoikeusHistoryPatch
import fi.oph.koski.http.HttpStatus
import fi.oph.koski.json.JsonSerializer
import fi.oph.koski.koskiuser.{AccessType, KoskiSpecificSession, Rooli}
import fi.oph.koski.log.Logging
import fi.oph.koski.massaluovutus.MassaluovutusUtils.defaultOrganisaatio
import fi.oph.koski.massaluovutus.{MassaluovutusQueryParameters, QueryFormat, QueryResultWriter}
import fi.oph.koski.schema.annotation.EnumValues
import fi.oph.scalaschema.annotation.Description
import org.json4s.jackson.JsonMethods
import org.json4s.{JArray, JValue}

case class MassaluovutusQueryLuokalleJaaneet (
@EnumValues(Set("luokallejaaneet"))
`type`: String = "luokallejaaneet",
@EnumValues(Set(QueryFormat.json))
format: String = QueryFormat.json,
@Description("Kyselyyn otettavan koulutustoimijan tai oppilaitoksen oid. Jos ei ole annettu, päätellään käyttäjän käyttöoikeuksista.")
organisaatioOid: Option[String],
) extends MassaluovutusQueryParameters with DatabaseConverters with Logging {
override def run(application: KoskiApplication, writer: QueryResultWriter)(implicit user: KoskiSpecificSession): Either[String, Unit] = {
val oppilaitosOids = application.organisaatioService.organisaationAlaisetOrganisaatiot(organisaatioOid.get)
val opiskeluoikeusOids = haeLuokalleJäämisenSisältävätOpiskeluoikeusOidit(application.raportointiDatabase.db, oppilaitosOids)

logger.info(s"Opiskeluoikeus oids: $organisaatioOid -> ${opiskeluoikeusOids}")

opiskeluoikeusOids.foreach { opiskeluoikeusOid =>
application.historyRepository
.findByOpiskeluoikeusOid(opiskeluoikeusOid)
.map { patches =>
patches
.foldLeft(LuokalleJääntiAccumulator()) { (acc, diff) => acc.next(diff) }
.matches
.foreach { case (luokka, oo) =>
writer.putJson(s"${opiskeluoikeusOid}_luokka_$luokka", oo)
}
}
}

Right(Unit)
}

override def queryAllowed(application: KoskiApplication)(implicit user: KoskiSpecificSession): Boolean =
user.hasGlobalReadAccess || (
organisaatioOid.exists(user.organisationOids(AccessType.read).contains)
&& user.sensitiveDataAllowed(Set(Rooli.LUOTTAMUKSELLINEN_KAIKKI_TIEDOT))
)

override def fillAndValidate(implicit user: KoskiSpecificSession): Either[HttpStatus, MassaluovutusQueryLuokalleJaaneet] =
if (organisaatioOid.isEmpty) {
defaultOrganisaatio.map(o => copy(organisaatioOid = Some(o)))
} else {
Right(this)
}

private def haeLuokalleJäämisenSisältävätOpiskeluoikeusOidit(raportointiDb: DB, oppilaitosOids: Seq[String]): Seq[String] =
QueryMethods.runDbSync(raportointiDb, sql"""
SELECT DISTINCT r_opiskeluoikeus.opiskeluoikeus_oid
FROM r_opiskeluoikeus
JOIN r_paatason_suoritus ON r_paatason_suoritus.opiskeluoikeus_oid = r_opiskeluoikeus.opiskeluoikeus_oid
WHERE koulutusmuoto = 'perusopetus'
AND jaa_luokalle
AND oppilaitos_oid = any($oppilaitosOids)
""".as[String])
}

case class LuokalleJääntiAccumulator(
opiskeluoikeus: JsonNode = JsonNodeFactory.instance.objectNode(),
invalidHistory: Boolean = false,
matches: Map[String, JValue] = Map(),
) {
def next(diff: OpiskeluoikeusHistoryPatch): LuokalleJääntiAccumulator = {
try {
val oo = JsonPatch.fromJson(JsonMethods.asJsonNode(diff.muutos)).apply(opiskeluoikeus)
LuokalleJääntiAccumulator(
oo,
invalidHistory,
newMathes(oo),
)
} catch {
case _: Exception => LuokalleJääntiAccumulator(invalidHistory = true)
}

}

private def newMathes(oo: JsonNode): Map[String, JValue] =
jääLuokalleLuokilla(oo).foldLeft(Map() : Map[String, JValue]) { (acc, m) =>
val (luokka, ooJson) = m
if (acc.keySet.contains(luokka)) {
acc
} else {
acc + (luokka -> ooJson)
}
}

private def jääLuokalleLuokilla(oo: JsonNode): List[(String, JValue)] =
suoritukset(oo)
.arr
.filter { s => JsonSerializer.extract[Option[Boolean]](s \ "jääLuokalle").getOrElse(false) }
.map { s => (
JsonSerializer.extract[String](s \ "koulutusmoduuli" \ "tunniste" \ "koodiarvo"),
s
) }

private def suoritukset(oo: JsonNode): JArray = (JsonMethods.fromJsonNode(oo) \ "suoritukset").asInstanceOf[JArray]
}

0 comments on commit 0ee455c

Please sign in to comment.