Skip to content

Commit

Permalink
Merge pull request #134 from Opetushallitus/OK-855-add-learning-outcomes
Browse files Browse the repository at this point in the history
OK-855: create learning outcomes in Europass export XML
  • Loading branch information
pkalliok authored Feb 26, 2025
2 parents 205e106 + 54a01c4 commit a2e30f4
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 17 deletions.
2 changes: 1 addition & 1 deletion europass-publisher/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
<dependency>
<groupId>fi.oph.kouta</groupId>
<artifactId>kouta-external</artifactId>
<version>0.4-SNAPSHOT</version>
<version>0.5-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>fi.vm.sade</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@ package fi.oph.kouta.europass

import scala.xml._
import org.json4s._
import fi.oph.kouta.external.domain.indexed.{KoulutusIndexed, ToteutusIndexed}
import fi.oph.kouta.external.domain.indexed.{
KoulutusIndexed,
ToteutusIndexed,
TutkintoNimike,
AmmatillinenKoulutusMetadataIndexed,
KorkeakoulutusKoulutusMetadataIndexed,
ErikoislaakariKoulutusMetadataIndexed
}
import fi.oph.kouta.external.domain.Kielistetty

object EuropassConversion {
implicit val formats = DefaultFormats
Expand All @@ -25,6 +33,9 @@ object EuropassConversion {
def tulosUrl(oid: String): String =
"https://rdf.oph.fi/koulutus-tulos/" ++ oid

def tutkintoUrl(koodi: String): String =
"https://rdf.oph.fi/tutkintonimike/" ++ koodi

def iscedfUrl(koodi: String): String =
"http://data.europa.eu/snb/isced-f/" ++ koodi

Expand All @@ -39,21 +50,54 @@ object EuropassConversion {
<loq:title xmlns:loq="http://data.europa.eu/snb/model/ap/loq-constraints/"
language={lang}>{nimi}</loq:title>

def nimetAsElmXml(nimet: Kielistetty): List[Elem] =
nimet.keys.map{lang => nimiAsElmXml(lang.name, nimet(lang))}.toList

def toteutusAsElmXml(toteutus: ToteutusIndexed): Elem = {
val oid: String = toteutus.oid.map(_.toString).getOrElse("")
val langs = List("en", "fi", "sv")
<loq:learningOpportunity id={toteutusUrl(oid)}
xmlns:loq="http://data.europa.eu/snb/model/ap/loq-constraints/">
{nimetAsElmXml(toteutus.nimi)}
{langs.map(konfoUrl(_, oid))}
<loq:learningAchievementSpecification
idref={koulutusUrl((toteutus.koulutusOid.map(_.toString).getOrElse("")))}/>
{toteutus.tarjoajat.map{t =>
<loq:providedBy idref={organisaatioUrl(t.oid.toString)}/>}}
{toteutus.nimi.keys.map{lang =>
nimiAsElmXml(lang.name, toteutus.nimi(lang))}}
</loq:learningOpportunity>
}

def koulutusToTutkintonimikkeet(koulutus: KoulutusIndexed): Seq[TutkintoNimike] =
koulutus.metadata match {
case None => Seq.empty
case Some(md) => md match {
case m: AmmatillinenKoulutusMetadataIndexed => m.tutkintonimike
case m: KorkeakoulutusKoulutusMetadataIndexed => m.tutkintonimike
case m: ErikoislaakariKoulutusMetadataIndexed => m.tutkintonimike
case _ => Seq.empty
}
}

def koulutusTuloksetAsElmXml(koulutus: KoulutusIndexed): List[Elem] = {
val oid: String = koulutus.oid.map(_.toString).getOrElse("")
koulutusToTutkintonimikkeet(koulutus) match {
case empty if empty.isEmpty =>
List(
<loq:learningOutcome id={tulosUrl(oid)}
xmlns:loq="http://data.europa.eu/snb/model/ap/loq-constraints/">
{nimetAsElmXml(koulutus.nimi)}
</loq:learningOutcome>
)
case nonempty =>
nonempty.map{tutkintonimike: TutkintoNimike =>
<loq:learningOutcome id={tutkintoUrl(tutkintonimike.koodiUri)}
xmlns:loq="http://data.europa.eu/snb/model/ap/loq-constraints/">
{nimetAsElmXml(tutkintonimike.nimi)}
</loq:learningOutcome>
}.toList
}
}

def koulutusalaToIscedfCode(koulutusala: String): Option[String] = koulutusala match {
case ka if ka.startsWith("kansallinenkoulutusluokitus2016koulutusalataso") =>
// The kkl2016 and isced2013 codesets have exactly equal codes
Expand All @@ -69,7 +113,7 @@ object EuropassConversion {
val oid: String = koulutus.oid.map(_.toString).getOrElse("")
<loq:learningAchievementSpecification id={koulutusUrl(oid)}
xmlns:loq="http://data.europa.eu/snb/model/ap/loq-constraints/">
{koulutus.nimi.keys.map(lang => nimiAsElmXml(lang.name, koulutus.nimi(lang)))}
{nimetAsElmXml(koulutus.nimi)}
{koulutus.koulutuskoodienAlatJaAsteet
.flatMap(_.koulutusalaKoodiUrit)
.union(koulutus.metadata.map(_.koulutusala).getOrElse(List()).map(_.koodiUri))
Expand All @@ -78,7 +122,8 @@ object EuropassConversion {
.map(iscedfAsElmXml)}
{koulutus.kielivalinta.map{lang =>
<loq:language uri={langCodes.getOrElse(lang.name, "")}/>}}
<loq:learningOutcome idref={tulosUrl(oid)}/>
{koulutusTuloksetAsElmXml(koulutus).map{tulos =>
<loq:learningOutcome idref={tulos \@ "id"}/>}}
</loq:learningAchievementSpecification>
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,25 @@ object Publisher extends Logging {
dest.write("</loq:learningOpportunityReferences>\n")
}

def tuloksetToFile(dest: BufferedWriter, koulutusStream: Stream[KoulutusIndexed]) = {
val tulosXmlStream = koulutusStream.flatMap(EuropassConversion.koulutusTuloksetAsElmXml)
dest.write("<loq:learningOutcomeReferences>\n")
foreachWithLogging(
tulosXmlStream,
"koulutuksen tulokset",
{tulos => dest.write(tulos.toString)}
)
dest.write("</loq:learningOutcomeReferences>\n")
}

def koulutustarjontaToFile(dest: BufferedWriter) = {
val toteutusStream = ElasticClient.listPublished(None)
val koulutusStream = koulutusDependentsOfToteutukset(toteutusStream)
dest.write("<loq:Courses xsdVersion=\"3.1.0\"\n" +
"xmlns:loq=\"http://data.europa.eu/snb/model/ap/loq-constraints/\">\n")
toteutuksetToFile(dest, toteutusStream)
koulutuksetToFile(dest, koulutusDependentsOfToteutukset(toteutusStream))
koulutuksetToFile(dest, koulutusStream)
tuloksetToFile(dest, koulutusStream)
dest.write("\n</loq:Courses>")
// dest.close()
}
Expand Down
1 change: 1 addition & 0 deletions europass-publisher/src/test/resources/koulutus-1293.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"tila":"julkaistu","johtaaTutkintoon":true,"eqf":[{"koodiUri":"eqf_7","nimi":{"fi":"Taso 7","sv":"Nivå 7","en":"Level 7"}}],"teemakuva":"https://konfo-files.opintopolku.fi/koulutus-teemakuva/1.2.246.562.13.00000000000000001293/abc6c512-f777-47f3-8330-9538b35ecb3a.png","koulutukset":[{"koodiUri":"koulutus_733299#12","nimi":{"en":"Master of Social Sciences, Other or Unknown Field","fi":"Yhteiskuntat. maist., muu tai tuntematon pääaineryhmä","sv":"Samh. mag., annat eller okänt huvudämne"}}],"tarjoajat":[{"oid":"1.2.246.562.10.91392558028","nimi":{"sv":"Jyväskylä universitet","fi":"Jyväskylän yliopisto","en":"University of Jyväskylä"},"paikkakunta":{"koodiUri":"kunta_179","nimi":{"sv":"Jyväskylä","fi":"Jyväskylä"}}}],"toteutukset":[{"tila":"arkistoitu","tarjoajat":[{"oid":"1.2.246.562.10.62835091836","nimi":{"sv":"Humanistis-yhteiskuntatieteellinen tiedekunta","fi":"Humanistis-yhteiskuntatieteellinen tiedekunta","en":"Faculty of Humanities and Social Sciences"},"paikkakunta":{"koodiUri":"kunta_179","nimi":{"sv":"Jyväskylä","fi":"Jyväskylä"}}}],"modified":"2023-03-06T14:08:11","nimi":{"en":"Doctoral Programme of the Department of Social Sciences and Philosophy, autumn 2023","fi":"Yhteiskuntatieteiden ja filosofian laitoksen tohtoriohjelma, syksy 2023 - POISTETTAVA"},"muokkaaja":{"oid":"1.2.246.562.24.26829672334","nimi":"Teemu Helander-Testi"},"oid":"1.2.246.562.17.00000000000000011649","organisaatiot":["1.2.246.562.10.62835091836"],"organisaatio":{"oid":"1.2.246.562.10.62835091836","nimi":{"sv":"Humanistis-yhteiskuntatieteellinen tiedekunta","fi":"Humanistis-yhteiskuntatieteellinen tiedekunta","en":"Faculty of Humanities and Social Sciences"},"paikkakunta":{"koodiUri":"kunta_179","nimi":{"sv":"Jyväskylä","fi":"Jyväskylä"}}},"formatoituModified":{"fi":"6.3.2023 klo 14:08","sv":"6.3.2023 kl. 14:08","en":"Mar. 6, 2023 at 02:08 PM UTC+2"}},{"tila":"arkistoitu","tarjoajat":[{"oid":"1.2.246.562.10.62835091836","nimi":{"sv":"Humanistis-yhteiskuntatieteellinen tiedekunta","fi":"Humanistis-yhteiskuntatieteellinen tiedekunta","en":"Faculty of Humanities and Social Sciences"},"paikkakunta":{"koodiUri":"kunta_179","nimi":{"sv":"Jyväskylä","fi":"Jyväskylä"}}}],"modified":"2023-10-27T08:10:27","nimi":{"fi":"Yhteiskuntatieteiden ja filosofian maisteriohjelma, yhteiskuntatieteiden maisteri (2 v), syksy 2023"},"muokkaaja":{"oid":"1.2.246.562.24.99730307673","nimi":"Aleksander Kytölä-Testi"},"oid":"1.2.246.562.17.00000000000000011243","organisaatiot":["1.2.246.562.10.62835091836"],"organisaatio":{"oid":"1.2.246.562.10.62835091836","nimi":{"sv":"Humanistis-yhteiskuntatieteellinen tiedekunta","fi":"Humanistis-yhteiskuntatieteellinen tiedekunta","en":"Faculty of Humanities and Social Sciences"},"paikkakunta":{"koodiUri":"kunta_179","nimi":{"sv":"Jyväskylä","fi":"Jyväskylä"}}},"formatoituModified":{"fi":"27.10.2023 klo 08:10","sv":"27.10.2023 kl. 08:10","en":"Oct. 27, 2023 at 08:10 AM UTC+3"}},{"tila":"julkaistu","tarjoajat":[{"oid":"1.2.246.562.10.62835091836","nimi":{"sv":"Humanistis-yhteiskuntatieteellinen tiedekunta","fi":"Humanistis-yhteiskuntatieteellinen tiedekunta","en":"Faculty of Humanities and Social Sciences"},"paikkakunta":{"koodiUri":"kunta_179","nimi":{"sv":"Jyväskylä","fi":"Jyväskylä"}}}],"modified":"2024-10-28T21:18:10","nimi":{"fi":"Yhteiskuntatieteiden ja filosofian maisteriohjelma, yhteiskuntatieteiden maisteri (2 v), syksy 2025"},"muokkaaja":{"oid":"1.2.246.562.24.10635019841","nimi":"Oili Ylitalo-Testi"},"oid":"1.2.246.562.17.00000000000000028375","organisaatiot":["1.2.246.562.10.62835091836"],"organisaatio":{"oid":"1.2.246.562.10.62835091836","nimi":{"sv":"Humanistis-yhteiskuntatieteellinen tiedekunta","fi":"Humanistis-yhteiskuntatieteellinen tiedekunta","en":"Faculty of Humanities and Social Sciences"},"paikkakunta":{"koodiUri":"kunta_179","nimi":{"sv":"Jyväskylä","fi":"Jyväskylä"}}},"formatoituModified":{"fi":"28.10.2024 klo 21:18","sv":"28.10.2024 kl. 21:18","en":"Oct. 28, 2024 at 09:18 PM UTC+2"}},{"tila":"arkistoitu","tarjoajat":[{"oid":"1.2.246.562.10.62835091836","nimi":{"sv":"Humanistis-yhteiskuntatieteellinen tiedekunta","fi":"Humanistis-yhteiskuntatieteellinen tiedekunta","en":"Faculty of Humanities and Social Sciences"},"paikkakunta":{"koodiUri":"kunta_179","nimi":{"sv":"Jyväskylä","fi":"Jyväskylä"}}}],"modified":"2024-11-01T10:36:39","nimi":{"fi":"Yhteiskuntatieteiden ja filosofian maisteriohjelma, yhteiskuntatieteiden maisteri (2 v), syksy 2024"},"muokkaaja":{"oid":"1.2.246.562.24.10635019841","nimi":"Oili Ylitalo-Testi"},"oid":"1.2.246.562.17.00000000000000021139","organisaatiot":["1.2.246.562.10.62835091836"],"organisaatio":{"oid":"1.2.246.562.10.62835091836","nimi":{"sv":"Humanistis-yhteiskuntatieteellinen tiedekunta","fi":"Humanistis-yhteiskuntatieteellinen tiedekunta","en":"Faculty of Humanities and Social Sciences"},"paikkakunta":{"koodiUri":"kunta_179","nimi":{"sv":"Jyväskylä","fi":"Jyväskylä"}}},"formatoituModified":{"fi":"1.11.2024 klo 10:36","sv":"1.11.2024 kl. 10:36","en":"Nov. 1, 2024 at 10:36 AM UTC+2"}},{"tila":"arkistoitu","tarjoajat":[{"oid":"1.2.246.562.10.62835091836","nimi":{"sv":"Humanistis-yhteiskuntatieteellinen tiedekunta","fi":"Humanistis-yhteiskuntatieteellinen tiedekunta","en":"Faculty of Humanities and Social Sciences"},"paikkakunta":{"koodiUri":"kunta_179","nimi":{"sv":"Jyväskylä","fi":"Jyväskylä"}}}],"modified":"2022-12-16T14:57:09","nimi":{"fi":"Yhteiskuntatieteiden ja filosofian maisteriohjelma, yhteiskuntatieteiden maisteri (2 v)"},"muokkaaja":{"oid":"1.2.246.562.24.99730307673","nimi":"Aleksander Kytölä-Testi"},"oid":"1.2.246.562.17.00000000000000002372","organisaatiot":["1.2.246.562.10.62835091836"],"organisaatio":{"oid":"1.2.246.562.10.62835091836","nimi":{"sv":"Humanistis-yhteiskuntatieteellinen tiedekunta","fi":"Humanistis-yhteiskuntatieteellinen tiedekunta","en":"Faculty of Humanities and Social Sciences"},"paikkakunta":{"koodiUri":"kunta_179","nimi":{"sv":"Jyväskylä","fi":"Jyväskylä"}}},"formatoituModified":{"fi":"16.12.2022 klo 14:57","sv":"16.12.2022 kl. 14:57","en":"Dec. 16, 2022 at 02:57 PM UTC+2"}}],"koulutuskoodienAlatJaAsteet":[{"koulutusKoodiUri":"koulutus_733299#12","koulutusalaKoodiUrit":["kansallinenkoulutusluokitus2016koulutusalataso1_03","kansallinenkoulutusluokitus2016koulutusalataso2_031","kansallinenkoulutusluokitus2016koulutusalataso3_0310","okmohjauksenala_4"],"koulutusasteKoodiUrit":["kansallinenkoulutusluokitus2016koulutusastetaso1_7","kansallinenkoulutusluokitus2016koulutusastetaso2_72"]}],"esikatselu":false,"modified":"2023-02-01T10:30:27","nimi":{"fi":"Yhteiskuntatieteet ja filosofia, maisteri (2 v)"},"muokkaaja":{"oid":"1.2.246.562.24.60097552624","nimi":"Sade Lehti-Testi"},"oid":"1.2.246.562.13.00000000000000001293","nqf":[{"koodiUri":"nqf_7","nimi":{"fi":"Taso 7","en":"Level 7","sv":"Nivå 7"}}],"kielivalinta":["fi"],"haut":["1.2.246.562.29.00000000000000054531","1.2.246.562.29.00000000000000002821","1.2.246.562.29.00000000000000017098","1.2.246.562.29.00000000000000028337","1.2.246.562.29.00000000000000036124"],"koulutustyyppiPath":"kk/yo","timestamp":1738848511270,"julkinen":false,"organisaatiot":["1.2.246.562.10.62835091836","1.2.246.562.10.91392558028"],"organisaatio":{"oid":"1.2.246.562.10.62835091836","nimi":{"sv":"Humanistis-yhteiskuntatieteellinen tiedekunta","fi":"Humanistis-yhteiskuntatieteellinen tiedekunta","en":"Faculty of Humanities and Social Sciences"},"paikkakunta":{"koodiUri":"kunta_179","nimi":{"sv":"Jyväskylä","fi":"Jyväskylä"}}},"metadata":{"tyyppi":"yo","kuvaus":{"fi":"<p>Yhteiskuntatieteiden ja filosofian opinnoissa opit ymmärtämään ja selittämään yhteiskuntaa, ihmistä ja inhimillistä toimintaa. Opit ottamaan kantaa yhteiskunnallisiin kysymyksiin ja ratkaisemaan niitä. Osallistut uutta tietoa luovaan toimintaan ja sovellat tietoa itsenäisesti ja yhteistyössä muiden kanssa. Opinnot tarjoavat mahdollisuuden pohtia rakentavasti omaa itseäsi ja paikkaasi maailmassa. Tutkintosi alana voi olla filosofia, politiikka, sosiologia tai yhteiskuntapolitiikka. Meiltä valmistuneet toimivat esimerkiksi kehittämis-, asiantuntija- ja hallintotehtävissä sekä tutkijoina ja opettajina.</p>"},"lisatiedot":[],"koulutusala":[{"koodiUri":"kansallinenkoulutusluokitus2016koulutusalataso2_031#1","nimi":{"en":"Social and behavioural sciences","fi":"Yhteiskuntatieteet","sv":"Samhällsvetenskaper"}}],"tutkintonimike":[{"koodiUri":"tutkintonimikekk_339#2","nimi":{"fi":"Yhteiskuntatieteiden maisteri","en":"Master of Social Sciences","sv":"Magister i samhällsvetenskaper"}}],"opintojenLaajuusNumero":120.0,"opintojenLaajuusyksikko":{"koodiUri":"opintojenlaajuusyksikko_2#1","nimi":{"en":"ECTS credits","fi":"opintopistettä","sv":"studiepoäng"}},"isMuokkaajaOphVirkailija":false},"formatoituModified":{"fi":"1.2.2023 klo 10:30","sv":"1.2.2023 kl. 10:30","en":"Feb. 1, 2023 at 10:30 AM UTC+2"},"koulutustyyppi":"yo"}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class ConversionSpec extends ScalatraFlatSpec with KoutaJsonFormats {
read[KoulutusIndexed](resource("koulutus-example-1.json"))
lazy val koulutusWithoutKoulutusKoodi =
read[KoulutusIndexed](resource("koulutus-8162.json"))
lazy val koulutusWithTutkintonimike =
read[KoulutusIndexed](resource("koulutus-1293.json"))

"example_koulutus" should "have correct fields" in {
assert(example_koulutus.oid.getOrElse("").toString
Expand All @@ -41,6 +43,8 @@ class ConversionSpec extends ScalatraFlatSpec with KoutaJsonFormats {
== "nimi fi")
assert((koulutusXml \ "ISCEDFCode")(1) \@ "uri"
== "http://data.europa.eu/snb/isced-f/02")
assert((koulutusXml \ "learningOutcome")(0) \@ "idref"
== "https://rdf.oph.fi/koulutus-tulos/1.2.246.562.13.00000000000000000006")
}

"example_toteutus" should "have correct fields" in {
Expand Down Expand Up @@ -88,4 +92,15 @@ class ConversionSpec extends ScalatraFlatSpec with KoutaJsonFormats {
assert(koulutusXml \ "ISCEDFCode" \@ "uri" ==
"http://data.europa.eu/snb/isced-f/023")
}

"koulutus 1293" should "have learning outcomes from tutkintonimikkeet" in {
val tulosXml: List[Elem] =
EuropassConversion.koulutusTuloksetAsElmXml(koulutusWithTutkintonimike)
assert(tulosXml(0) \@ "id"
== "https://rdf.oph.fi/tutkintonimike/tutkintonimikekk_339#2")
assert((tulosXml(0) \ "title")(0).text
== "Yhteiskuntatieteiden maisteri")
assert((tulosXml(0) \ "title")(2) \@ "language" == "sv")
}

}
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
package fi.oph.kouta.europass.test

import org.json4s.jackson.Serialization.{read, write}
import org.scalatra.test.scalatest.ScalatraFlatSpec
import scala.concurrent.duration._
import scala.concurrent.Await
import scala.io.Source
import java.io._

import fi.oph.kouta.external.util.KoutaJsonFormats
import fi.oph.kouta.external.domain.indexed.KoulutusIndexed
import fi.oph.kouta.europass.Publisher
import fi.oph.kouta.europass.ElasticClient
import fi.oph.kouta.domain.Amm

class PublisherSpec extends ScalatraFlatSpec with ElasticFixture {
class PublisherSpec extends ScalatraFlatSpec with ElasticFixture with KoutaJsonFormats {

def resource(filename: String) = Source.fromResource(filename).bufferedReader

"toteutusToFile" should "create correct toteutusXml from ElasticSearch" in {
val writer = new StringWriter()
Expand Down Expand Up @@ -41,7 +46,25 @@ class PublisherSpec extends ScalatraFlatSpec with ElasticFixture {
))
}

"koulutustarjontaToFile" should "have both toteutukset and koulutukset" in {
"tuloksetToFile" should "have information from tutkintonimike when available" in {
val koulutusWithTutkintonimike =
read[KoulutusIndexed](resource("koulutus-1293.json"))
val koulutusWithoutKoulutusKoodi =
read[KoulutusIndexed](resource("koulutus-8162.json"))
val writer = new StringWriter()
val bwriter = new BufferedWriter(writer)
Publisher.tuloksetToFile(bwriter,
Stream(koulutusWithTutkintonimike, koulutusWithoutKoulutusKoodi))
bwriter.close()
assert(writer.toString.contains(
"<loq:learningOutcome id=\"https://rdf.oph.fi/tutkintonimike/tutkintonimikekk_339#2\""
))
assert(writer.toString.contains(
"<loq:learningOutcome id=\"https://rdf.oph.fi/koulutus-tulos/1.2.246.562.13.00000000000000008162\""
))
}

"koulutustarjontaToFile" should "have all kinds of objects" in {
val writer = new StringWriter()
val bwriter = new BufferedWriter(writer)
Publisher.koulutustarjontaToFile(bwriter)
Expand All @@ -54,6 +77,9 @@ class PublisherSpec extends ScalatraFlatSpec with ElasticFixture {
"<loq:learningAchievementSpecification id=\"https://rdf.oph.fi/koulutus/1.2.246.562.13.00000000000000000001\""
))
assert(content.contains("http://data.europa.eu/snb/isced-f/02"))
assert(content.contains(
"<loq:learningOutcome id=\"https://rdf.oph.fi/tutkintonimike/tutkintonimikkeet_02\""
))
}

"koulutustarjontaAsFile" should "return XML filename" in {
Expand Down
2 changes: 1 addition & 1 deletion kouta-external/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>fi.oph.kouta</groupId>
<artifactId>kouta-external</artifactId>
<version>0.4-SNAPSHOT</version>
<version>0.5-SNAPSHOT</version>
<name>kouta-external</name>

<parent>
Expand Down
Loading

0 comments on commit a2e30f4

Please sign in to comment.