This is an automated email from the ASF dual-hosted git repository.

sergeykamov pushed a commit to branch NLPCRAFT-491
in repository https://gitbox.apache.org/repos/asf/incubator-nlpcraft.git

commit e0a631d3d3a0ad72376aac9527f297bcc0829a5e
Author: Sergey Kamov <skhdlem...@gmail.com>
AuthorDate: Fri Apr 8 14:03:25 2022 +0300

    WIP.
---
 .../nlpcraft/examples/pizzeria/PizzeriaModel.scala | 28 ++++++----
 .../pizzeria/components/ElementExtender.scala      | 64 ++++++++++++----------
 .../src/main/resources/pizzeria_model.yaml         |  4 +-
 .../examples/pizzeria/PizzeriaModelSpec.scala      | 36 ++++++++++--
 .../pizzeria/cli/PizzeriaModelClientCli.scala      | 12 +++-
 .../pizzeria/cli/PizzeriaModelServer.scala         |  9 ++-
 6 files changed, 103 insertions(+), 50 deletions(-)

diff --git 
a/nlpcraft-examples/pizzeria/src/main/java/org/apache/nlpcraft/examples/pizzeria/PizzeriaModel.scala
 
b/nlpcraft-examples/pizzeria/src/main/java/org/apache/nlpcraft/examples/pizzeria/PizzeriaModel.scala
index e9498fd7..9d8020d4 100644
--- 
a/nlpcraft-examples/pizzeria/src/main/java/org/apache/nlpcraft/examples/pizzeria/PizzeriaModel.scala
+++ 
b/nlpcraft-examples/pizzeria/src/main/java/org/apache/nlpcraft/examples/pizzeria/PizzeriaModel.scala
@@ -46,16 +46,20 @@ import org.apache.nlpcraft.examples.pizzeria.PizzeriaModel.*
 /**
   *
   */
-class PizzeriaModel extends NCModelAdapter (new 
NCModelConfig("nlpcraft.pizzeria.ex", "Pizzeria Example Model", "1.0"), 
PizzeriaModelPipeline.PIPELINE) with LazyLogging:
-    private def withLog(im: NCIntentMatch, body: PizzeriaOrder => NCResult): 
NCResult =
-        val usrId = im.getContext.getRequest.getUserId
-        val data = im.getContext.getConversation.getData
-        var o: PizzeriaOrder = data.get(usrId)
-
-        if o == null then
-            o = new PizzeriaOrder()
+class PizzeriaModel extends NCModelAdapter(new 
NCModelConfig("nlpcraft.pizzeria.ex", "Pizzeria Example Model", "1.0"), 
PizzeriaModelPipeline.PIPELINE) with LazyLogging:
+    private def getOrder(ctx: NCContext): PizzeriaOrder =
+        val data = ctx.getConversation.getData
+        val usrId = ctx.getRequest.getUserId
+        val o: PizzeriaOrder = data.get(usrId)
+
+        if o != null then o
+        else
+            val o = new PizzeriaOrder()
             data.put(usrId, o)
+            o
 
+    private def withLog(im: NCIntentMatch, body: PizzeriaOrder => NCResult): 
NCResult =
+        val o = getOrder(im.getContext)
         val initState = o.getState.toString
         val initDesc = o.getDesc
 
@@ -88,7 +92,7 @@ class PizzeriaModel extends NCModelAdapter (new 
NCModelConfig("nlpcraft.pizzeria
     private def doShowMenu() =
         NCResult(
             "There are accessible for order: margherita, carbonara and 
marinara. Sizes: large, medium or small. " +
-            "Also there are tea, green tea, coffee and cola.",
+            "Also there are tea, coffee and cola.",
             ASK_RESULT
         )
 
@@ -236,4 +240,8 @@ class PizzeriaModel extends NCModelAdapter (new 
NCModelConfig("nlpcraft.pizzeria
             // If order in progress and has pizza with unknown size, it 
doesn't depend on dialog state.
             if !o.isEmpty && o.setPizzaNoSize(extractPizzaSize(size)) then 
askIsReadyOrAskSpecify(o)
             else throw UNEXPECTED_REQUEST
-    )
\ No newline at end of file
+    )
+
+    override def onRejection(im: NCIntentMatch, e: NCRejection): NCResult =
+        // TODO: improve logic after 
https://issues.apache.org/jira/browse/NLPCRAFT-495 ticket resolving.
+        if im == null || getOrder(im.getContext).isEmpty then doShowMenu() 
else throw e
\ No newline at end of file
diff --git 
a/nlpcraft-examples/pizzeria/src/main/java/org/apache/nlpcraft/examples/pizzeria/components/ElementExtender.scala
 
b/nlpcraft-examples/pizzeria/src/main/java/org/apache/nlpcraft/examples/pizzeria/components/ElementExtender.scala
index 69f4a409..6bc1089f 100644
--- 
a/nlpcraft-examples/pizzeria/src/main/java/org/apache/nlpcraft/examples/pizzeria/components/ElementExtender.scala
+++ 
b/nlpcraft-examples/pizzeria/src/main/java/org/apache/nlpcraft/examples/pizzeria/components/ElementExtender.scala
@@ -24,6 +24,7 @@ import java.util.List as JList
 import scala.collection.mutable
 import scala.jdk.CollectionConverters.*
 import com.typesafe.scalalogging.LazyLogging
+import org.apache.nlpcraft.NCResultType.ASK_DIALOG
 
 /**
   *
@@ -34,55 +35,62 @@ case class EntityData(id: String, property: String)
 
 /**
   * Element extender.
-  * For each 'main' element it tries to find related extra element and to make 
new complex element instead of this pair.
-  * This new element has:
-  * 1. Same ID as main element, also all main element properties copied into 
this new element's properties.
-  * 2. Tokens from both elements.
-  * 3. Configured property from extra element copied into new element's 
properties.
+  * For each 'main' element it tries to find related extra element and convert 
this pair to new complex element.
+  * New element:
+  * 1. Gets same ID as main element, also all main element properties copied 
into this new one.
+  * 2. Gets tokens from both elements.
+  * 3. Configured extra element property copied into new element's properties.
   *
   * Note that it is simple example implementation.
   * It just tries to unite nearest neighbours and doesn't check intermediate 
words, order correctness etc.
   */
 object ElementExtender:
-    case class EntityHolder(element: NCEntity):
+    case class EntityHolder(entity: NCEntity):
         lazy val position: Double =
-            val toks = element.getTokens.asScala
+            val toks = entity.getTokens.asScala
             (toks.head.getIndex + toks.last.getIndex) / 2.0
-
-    private def toTokens(e: NCEntity): mutable.Seq[NCToken] = 
e.getTokens.asScala
+    private def extract(e: NCEntity): mutable.Seq[NCToken] = 
e.getTokens.asScala
 
 import ElementExtender.*
 
 /**
   *
-  * @param mainSeq
-  * @param extra
+  * @param mainDataSeq
+  * @param extraData
   */
-case class ElementExtender(mainSeq: Seq[EntityData], extra: EntityData) 
extends NCEntityMapper with LazyLogging:
+case class ElementExtender(mainDataSeq: Seq[EntityData], extraData: 
EntityData) extends NCEntityMapper with LazyLogging:
     override def map(req: NCRequest, cfg: NCModelConfig, entities: 
JList[NCEntity]): JList[NCEntity] =
-        def combine(m: NCEntity, mProp: String, e: NCEntity): NCEntity =
+        def combine(mainEnt: NCEntity, mainProp: String, extraEnt: NCEntity): 
NCEntity =
             new NCPropertyMapAdapter with NCEntity:
-                m.keysSet().forEach(k => put(k, m.get(k)))
-                put[String](mProp, e.get[String](extra.property).toLowerCase)
-                override val getTokens: JList[NCToken] = (toTokens(m) ++ 
toTokens(e)).sortBy(_.getIndex).asJava
+                mainEnt.keysSet().forEach(k => put(k, mainEnt.get(k)))
+                put[String](mainProp, 
extraEnt.get[String](extraData.property).toLowerCase)
+                override val getTokens: JList[NCToken] = (extract(mainEnt) ++ 
extract(extraEnt)).sortBy(_.getIndex).asJava
                 override val getRequestId: String = req.getRequestId
-                override val getId: String = m.getId
+                override val getId: String = mainEnt.getId
 
         val es = entities.asScala
-        val mainById = mainSeq.map(p => p.id -> p).toMap
-        val mainHs = mutable.HashSet.empty ++ es.filter(e => 
mainById.contains(e.getId)).map(p => EntityHolder(p))
-        val extraHs = es.filter(_.getId == extra.id).map(p => EntityHolder(p))
+        val mainById = mainDataSeq.map(p => p.id -> p).toMap
+        val main = mutable.HashSet.empty ++ es.filter(e => 
mainById.contains(e.getId)).map(p => EntityHolder(p))
+        val extra = es.filter(_.getId == extraData.id).map(p => 
EntityHolder(p))
 
-        if mainHs.nonEmpty && mainHs.size >= extraHs.size then
+        if main.nonEmpty && extra.nonEmpty && main.size >= extra.size then
+            val used = (main.map(_.entity) ++ extra.map(_.entity)).toSet
             val main2Extra = mutable.HashMap.empty[NCEntity, NCEntity]
 
-            for (e <- extraHs)
-                val m = mainHs.minBy(m => Math.abs(m.position - e.position))
-                mainHs -= m
-                main2Extra += m.element -> e.element
+            for (e <- extra)
+                val m = main.minBy(m => Math.abs(m.position - e.position))
+                main -= m
+                main2Extra += m.entity -> e.entity
+
+            val unrelatedEs = es.filter(e => !used.contains(e))
+            val artificialEs = for ((m, e) <- main2Extra) yield combine(m, 
mainById(m.getId).property, e)
+            val unused = main.map(_.entity)
+
+            val res = (unrelatedEs ++ artificialEs ++ 
unused).sortBy(extract(_).head.getIndex)
 
-            val newEs = for ((m, e) <- main2Extra) yield combine(m, 
mainById(m.getId).property, e)
-            val used = (mainHs.map(_.element) ++ extraHs.map(_.element)).toSet
+            def str(es: mutable.Buffer[NCEntity]) =
+                es.map(e => 
s"id=${e.getId}(${extract(e).map(_.getIndex).mkString("[", ",", 
"]")})").mkString("{", ", ", "}")
+            logger.debug(s"Elements mapped [input=${str(es)}, 
output=${str(res)}]")
 
-            (es.filter(e => !used.contains(e)) ++ mainHs.map(_.element) ++ 
newEs).sortBy(toTokens(_).head.getIndex).asJava
+            res.asJava
         else entities
\ No newline at end of file
diff --git a/nlpcraft-examples/pizzeria/src/main/resources/pizzeria_model.yaml 
b/nlpcraft-examples/pizzeria/src/main/resources/pizzeria_model.yaml
index 25676fb0..589adab2 100644
--- a/nlpcraft-examples/pizzeria/src/main/resources/pizzeria_model.yaml
+++ b/nlpcraft-examples/pizzeria/src/main/resources/pizzeria_model.yaml
@@ -34,14 +34,13 @@ elements:
     description: "Kinds of drinks."
     values:
       "tea": [ ]
-      "green tea": [ ]
       "coffee": [ ]
       "cola": ["{coca|cola|coca cola|cocacola|coca-cola}"]
 
   - id: "ord:yes"
     description: "Conformation (yes)."
     synonyms:
-      - "{yes|yeah|right|fine|nice|excellent|good|correct|sure}"
+      - "{yes|yeah|right|fine|nice|excellent|good|correct|sure|ok}"
       - "{you are|_} {correct|right}"
 
   - id: "ord:no"
@@ -70,3 +69,4 @@ elements:
     synonyms:
       - "{menu|carte|card}"
       - "{products|goods|food|_} list"
+      - "{hi|help|hallo}"
\ No newline at end of file
diff --git 
a/nlpcraft-examples/pizzeria/src/test/java/org/apache/nlpcraft/examples/pizzeria/PizzeriaModelSpec.scala
 
b/nlpcraft-examples/pizzeria/src/test/java/org/apache/nlpcraft/examples/pizzeria/PizzeriaModelSpec.scala
index 1478a693..109f2069 100644
--- 
a/nlpcraft-examples/pizzeria/src/test/java/org/apache/nlpcraft/examples/pizzeria/PizzeriaModelSpec.scala
+++ 
b/nlpcraft-examples/pizzeria/src/test/java/org/apache/nlpcraft/examples/pizzeria/PizzeriaModelSpec.scala
@@ -25,6 +25,7 @@ import org.junit.jupiter.api.*
 import scala.language.implicitConversions
 import scala.util.Using
 import scala.collection.mutable
+
 /**
   *
   */
@@ -67,7 +68,7 @@ class PizzeriaModelSpec:
                 case None => // No-op.
             println()
 
-        require(errs.isEmpty)
+        require(errs.isEmpty, s"There are ${errs.size} errors above.")
 
     private def dialog(exp: PizzeriaOrder, reqs: (String, NCResultType)*): 
Unit =
         val testMsgs = mutable.ArrayBuffer.empty[String]
@@ -114,10 +115,10 @@ class PizzeriaModelSpec:
     def test(): Unit =
         given Conversion[String, (String, NCResultType)] with
             def apply(txt: String): (String, NCResultType) = (txt, ASK_DIALOG)
-            
+
         dialog(
-            new Builder().withDrink("tea", 1).build,
-            "One tea",
+            new Builder().withDrink("tea", 2).build,
+            "Two tea",
             "yes",
             "yes" -> ASK_RESULT
         )
@@ -176,12 +177,35 @@ class PizzeriaModelSpec:
         dialog(
             new Builder().
                 withPizza("margherita", "small", 2).
-                withPizza("marinara", "small", 3).
-                withDrink("tea", 1).
+                withPizza("marinara", "small", 1).
+                withDrink("tea", 3).
                 build,
             "margherita two, marinara and three tea",
             "small",
             "small",
             "yes",
             "yes" -> ASK_RESULT
+        )
+
+        dialog(
+            new Builder().
+                withPizza("margherita", "small", 2).
+                withPizza("marinara", "large", 1).
+                withDrink("cola", 3).
+                build,
+            "small margherita two, marinara big one and three cola",
+            "yes",
+            "yes" -> ASK_RESULT
+        )
+
+        dialog(
+            new Builder().
+                withPizza("margherita", "small", 1).
+                withPizza("marinara", "large", 2).
+                withDrink("coffee", 2).
+                build,
+            "small margherita, 2 marinara and 2 coffee",
+            "large",
+            "yes",
+            "yes" -> ASK_RESULT
         )
\ No newline at end of file
diff --git 
a/nlpcraft-examples/pizzeria/src/test/java/org/apache/nlpcraft/examples/pizzeria/cli/PizzeriaModelClientCli.scala
 
b/nlpcraft-examples/pizzeria/src/test/java/org/apache/nlpcraft/examples/pizzeria/cli/PizzeriaModelClientCli.scala
index 91c31f96..3690a84e 100644
--- 
a/nlpcraft-examples/pizzeria/src/test/java/org/apache/nlpcraft/examples/pizzeria/cli/PizzeriaModelClientCli.scala
+++ 
b/nlpcraft-examples/pizzeria/src/test/java/org/apache/nlpcraft/examples/pizzeria/cli/PizzeriaModelClientCli.scala
@@ -28,6 +28,14 @@ import java.net.http.HttpRequest.*
 import java.net.http.HttpResponse.*
 import scala.util.Using
 
+/**
+  * Use it for Pizzeria Model test.
+  *  - Run model server (PizzeriaModelServer) as application.
+  *  - Run client CLI (PizzeriaModelClientCli) as application.
+  *  Order pizza via CLI client.
+  *
+  *  Note that it supports only one default test user and only one user 
session at the same time.
+  */
 object PizzeriaModelClientCli extends LazyLogging :
     private val client = HttpClient.newHttpClient()
 
@@ -51,7 +59,8 @@ object PizzeriaModelClientCli extends LazyLogging :
     def main(args: Array[String]): Unit =
         println("Application started.")
 
-        // Clears possible saved sessions.tea
+        // Clears possible saved sessions.
+        // it is necessary because this test client/server implementation 
supports only one session for same test user.
         ask("stop")
 
         var applStarted = true
@@ -66,7 +75,6 @@ object PizzeriaModelClientCli extends LazyLogging :
 
             try
                 var in = scala.io.StdIn.readLine()
-
                 if in != null then
                     in = in.trim
                     if in.nonEmpty then println(ask(in))
diff --git 
a/nlpcraft-examples/pizzeria/src/test/java/org/apache/nlpcraft/examples/pizzeria/cli/PizzeriaModelServer.scala
 
b/nlpcraft-examples/pizzeria/src/test/java/org/apache/nlpcraft/examples/pizzeria/cli/PizzeriaModelServer.scala
index ba9ad12c..aa8fd12a 100644
--- 
a/nlpcraft-examples/pizzeria/src/test/java/org/apache/nlpcraft/examples/pizzeria/cli/PizzeriaModelServer.scala
+++ 
b/nlpcraft-examples/pizzeria/src/test/java/org/apache/nlpcraft/examples/pizzeria/cli/PizzeriaModelServer.scala
@@ -27,7 +27,12 @@ import java.net.InetSocketAddress
 import scala.util.Using
 
 /**
-  *
+  * Use it for Pizzeria Model test.
+  *  - Run model server (PizzeriaModelServer) as application.
+  *  - Run client CLI (PizzeriaModelClientCli) as application.
+  *  Order pizza via CLI client.
+  *  
+  *  Note that it supports only one default test user and only one user 
session at the same time.  
   */
 object PizzeriaModelServer:
     private val host = "localhost"
@@ -56,7 +61,7 @@ object PizzeriaModelServer:
                             case "POST" => Using.resource(new 
BufferedReader(new InputStreamReader(e.getRequestBody))) { _.readLine }
                             case _ => throw new Exception(s"Unsupported 
request method: ${e.getRequestMethod}")
 
-                    if req == null || req.isEmpty then Exception(s"Empty 
request")
+                    if req == null || req.isEmpty then Exception(s"Empty 
request.")
 
                     val resp = nlpClient.ask(req, null, "userId")
                     val prompt = if resp.getType == ASK_DIALOG then "(Your 
should answer on the model's question below)\n" else ""

Reply via email to