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


The following commit(s) were added to refs/heads/NLPCRAFT-491 by this push:
     new 96ef59e  WIP.
96ef59e is described below

commit 96ef59e88dbeefb0313b83fa1de56eea089e96f4
Author: Sergey Kamov <[email protected]>
AuthorDate: Sat Apr 2 12:21:21 2022 +0300

    WIP.
---
 .../org/apache/nlpcraft/examples/order/Order.scala |  44 ++++--
 .../nlpcraft/examples/order/OrderModel.scala       | 151 +++++++++++++--------
 .../order/src/main/resources/order_model.yaml      |   6 +
 .../nlpcraft/examples/order/OrderModelCli.scala    |  34 +++++
 .../nlpcraft/examples/order/OrderModelSpec.scala   |   2 +-
 5 files changed, 172 insertions(+), 65 deletions(-)

diff --git 
a/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/Order.scala
 
b/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/Order.scala
index e9d4aa9..2611ea6 100644
--- 
a/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/Order.scala
+++ 
b/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/Order.scala
@@ -23,8 +23,14 @@ private enum PizzaSize:
     case SMALL, MEDIUM, LARGE
 
 object Order:
-    def getPizzaSizeKinds: String = 
PizzaSize.values.map(_.toString.toLowerCase).mkString(", ")
-    def pizza2Str(name: String, size: PizzaSize): String = s"$name 
${size.toString.toLowerCase} size"
+    private val allPizzaSizeKinds: String = 
withComma(PizzaSize.values.map(_.toString.toLowerCase))
+
+    private def withComma(iter: Iterable[String]): String = iter.mkString(", ")
+    private def pizza2Str(name: String, size: PizzaSize): String =
+        if size != null then s"$name ${size.toString.toLowerCase} size" else 
name
+    private def seq2Str[T](name: String, seq: Seq[T], toStr: T => String = (t: 
T) => t.toString): String =
+        if seq.nonEmpty then s"$name: ${withComma(seq.map(toStr))}." else ""
+    private def norm(s: String) = s.trim.replaceAll("(?m)^[ \t]*\r?\n", "")
 
 import Order.*
 
@@ -59,19 +65,37 @@ class Order:
             "Order is empty. Please order some pizza or drinks."
         else
             pizza.find { (_, size) => size == null } match
-                case Some((name, _)) => s"Please specify $name size? It can be 
$getPizzaSizeKinds"
+                case Some((name, _)) => s"Please specify $name size? It can be 
$allPizzaSizeKinds"
                 case None => throw new AssertionError("Invalid state")
 
     def ask2Confirm(): String =
         require(isValid())
-        s"Let me specify your order.\n${this.toString()}\nIs it correct?"
-
+        norm(
+            s"""
+           |Let me specify your order.
+           |${seq2Str("Pizza", pizza.toSeq, pizza2Str)}
+           |${seq2Str("Drinks", drinks.toSeq)}
+           |Is it correct?
+           """.stripMargin
+        )
+
+    def getState(): String =
+        norm(
+            s"""
+               |Current order state: '${if inProgress() then "in progress" 
else "empty"}'.
+               |${seq2Str("Pizza", pizza.toSeq, pizza2Str)}
+               |${seq2Str("Drinks", drinks.toSeq)}
+           """.stripMargin
+        )
+    
     override def toString(): String =
-        val ps = if pizza.nonEmpty then s"Pizza: ${pizza.map { (name, sz) => 
pizza2Str(name, sz) }.mkString(", ")}. " else ""
-        val ds = if drinks.nonEmpty then s"Drinks: ${drinks.mkString(", ")}. " 
else ""
-
-        s"$ps$ds"
+        norm(
+            s"""
+           |${seq2Str("Pizza", pizza.toSeq, pizza2Str)}
+           |${seq2Str("Drinks", drinks.toSeq)}
+           """.stripMargin
+        ).replaceAll("\n", " ")
 
     def clear(): Unit =
         pizza.clear()
-        drinks.clear()
\ No newline at end of file
+        drinks.clear()
diff --git 
a/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/OrderModel.scala
 
b/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/OrderModel.scala
index 48253a6..28acfad 100644
--- 
a/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/OrderModel.scala
+++ 
b/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/OrderModel.scala
@@ -17,73 +17,118 @@
 
 package org.apache.nlpcraft.examples.order
 
+import com.typesafe.scalalogging.LazyLogging
 import org.apache.nlpcraft.*
 import org.apache.nlpcraft.internal.util.NCResourceReader
 import org.apache.nlpcraft.nlp.*
-import org.apache.nlpcraft.nlp.entity.parser.semantic.NCSemanticEntityParser
 import org.apache.nlpcraft.nlp.entity.parser.*
-import org.apache.nlpcraft.nlp.token.enricher.NCEnStopWordsTokenEnricher
-import org.apache.nlpcraft.nlp.token.parser.NCOpenNLPTokenParser
+
 import scala.collection.mutable
 import org.apache.nlpcraft.NCResultType.*
+
 import scala.jdk.CollectionConverters.*
 
+object OrderModel extends LazyLogging:
+    private def extractPizzaKind(e: NCEntity): String = 
e.get[String]("ord:pizza:kind:value")
+    private def extractPizzaSize(e: NCEntity): PizzaSize = 
PizzaSize.valueOf(e.get[String]("ord:pizza:size:value").toUpperCase)
+    private def extractDrink(e: NCEntity): String = 
e.get[String]("ord:drink:value")
+    private def isStopWord(t: NCToken): Boolean = t.get[Boolean]("stopword")
+    private def confirmOrSpecify(ord: Order): NCResult =
+        NCResult(if ord.isValid() then ord.ask2Confirm() else 
ord.ask2Specify(), ASK_DIALOG)
+    private def continue(ord: Order): NCResult =
+        NCResult(if ord.isValid() then s"OK, please continue your order: $ord" 
else ord.ask2Specify(), ASK_DIALOG)
+
+    private def log(o: Order): Unit = logger.info(o.getState())
+    private def onStart(im: NCIntentMatch, o: Order): Unit =
+        logger.info(s"Initial state before request: 
${im.getContext.getRequest.getText}")
+    private def onFinish(o: Order): Unit =
+        logger.info(s"Result state")
+        logger.info(o.getState())
+
+    private def withRelations(
+        ents: Seq[NCEntity], relations: Seq[NCEntity], skip: Seq[NCEntity], 
allToks: Seq[NCToken]
+    ): Map[NCEntity, NCEntity] =
+        case class IdxHolder(from: Int, to: Int)
+
+        def getIndexes(e: NCEntity): IdxHolder =
+            val toks = e.getTokens.asScala
+            IdxHolder(toks.head.getIndex, toks.last.getIndex)
+
+        val skipIdxs = skip.flatMap(_.getTokens.asScala.map(_.getIndex)).toSet
+        val used = mutable.ArrayBuffer.empty[NCEntity]
+
+        def areNeighbours(i1: Int, i2: Int): Boolean =
+            if i2 == i1 + 1 then true else Range(i1 + 1, i2).forall(i => 
isStopWord(allToks(i)) || skipIdxs.contains(i))
+
+        ents.map(e => {
+            val eIdxs = getIndexes(e)
+            val r = relations.filter(r => !used.contains(r)).find(r => {
+                val rIdxs = getIndexes(r)
+                areNeighbours(rIdxs.to, eIdxs.from) || areNeighbours(eIdxs.to, 
rIdxs.from)
+            }).orNull
+
+            if r != null then used += r
+            e -> r
+        }).toMap
+
+import org.apache.nlpcraft.examples.order.OrderModel.*
 /**
   *
   */
-class OrderModel extends NCModelAdapter(
+class OrderModel extends NCModelAdapter (
     new NCModelConfig("nlpcraft.order.ex", "Order Example Model", "1.0"),
     new NCPipelineBuilder().withSemantic("en", "order_model.yaml").build()
-):
+) with LazyLogging:
     private val ords = mutable.HashMap.empty[String, Order]
 
     private def getOrder(im: NCIntentMatch): Order = 
ords.getOrElseUpdate(im.getContext.getRequest.getUserId, new Order)
 
-    private def extractPizzaKind(e: NCEntity): String = 
e.get[String]("ord:pizza:kind:value")
-    private def extractPizzaSize(e: NCEntity): PizzaSize = 
PizzaSize.valueOf(e.get[String]("ord:pizza:size:value").toUpperCase)
-    private def extractDrink(e: NCEntity): String = 
e.get[String]("ord:drink:value")
-
-    private def confirmOrSpecify(ord: Order): NCResult =
-        NCResult(if ord.isValid() then ord.ask2Confirm() else 
ord.ask2Specify(), ASK_DIALOG)
-
-    private def getAvgPosition(e: NCEntity): Double =
-        val toks = e.getTokens.asScala
-        (toks.head.getIndex + toks.last.getIndex) / 2.0
-
     @NCIntent("intent=confirm term(confirm)={has(ent_groups, 'confirm')}")
     def onConfirm(im: NCIntentMatch, @NCIntentTerm("confirm") confirm: 
NCEntity): NCResult =
         val ord = getOrder(im)
+        onStart(im, ord)
         val dlg = im.getContext.getConversation.getDialogFlow
 
         def cancelAll(): NCResult =
             ord.clear()
             NCResult("Order canceled. We are ready for new orders.", 
ASK_RESULT)
 
-        // 'stop' command.
-        if !dlg.isEmpty && dlg.get(dlg .size() - 1).getIntentMatch.getIntentId 
== "ord:stop" then
-            confirm.getId match
-                case "ord:confirm:yes" => cancelAll()
-                case "ord:confirm:no" => confirmOrSpecify(ord)
-                case _ => throw new AssertionError()
-        // 'confirm' command.
-        else
-            if !ord.inProgress() then throw new NCRejection("No orders in 
progress.")
-
-            confirm.getId match
-                case "ord:confirm:yes" =>
-                    if ord.isValid() then
-                        println(s"Done: $ord")
-                        ord.clear()
-                        NCResult("Congratulations. Your order executed. You 
can start make new orders.", ASK_RESULT)
-                    else
-                        NCResult(ord.ask2Specify(), ASK_DIALOG)
-                case "ord:confirm:no" => cancelAll()
-                case _ => throw new AssertionError()
+        val res=
+            // 'stop' command.
+            if !dlg.isEmpty && dlg.get(dlg.size() - 
1).getIntentMatch.getIntentId == "ord:stop" then
+                confirm.getId match
+                    case "ord:confirm:yes" => cancelAll()
+                    case "ord:confirm:no" => confirmOrSpecify(ord)
+                    case "ord:confirm:continue" => continue(ord)
+                    case _ => throw new AssertionError()
+            // 'confirm' command.
+            else
+                if !ord.inProgress() then throw new NCRejection("No orders in 
progress.")
+
+                confirm.getId match
+                    case "ord:confirm:yes" =>
+                        if ord.isValid() then
+                            logger.info(s"ORDER EXECUTED: $ord")
+                            ord.clear()
+                            NCResult("Congratulations. Your order executed. 
You can start make new orders.", ASK_RESULT)
+                        else
+                            NCResult(ord.ask2Specify(), ASK_DIALOG)
+                    case "ord:confirm:no" => cancelAll()
+                    case "ord:confirm:continue" => continue(ord)
+                    case _ => throw new AssertionError()
+
+        onFinish(ord)
+        res
 
     @NCIntent("intent=stop term(stop)={# == 'ord:stop'}")
         def onStop(im: NCIntentMatch, @NCIntentTerm("stop") stop: NCEntity): 
NCResult =
-            if getOrder(im).inProgress() then NCResult("Are you sure that you 
want to cancel current order?", ASK_DIALOG)
-            else NCResult("Nothing to cancel", ASK_RESULT)
+            val ord = getOrder(im)
+            onStart(im, ord)
+            val res =
+                if ord.inProgress() then NCResult("Are you sure that you want 
to cancel current order?", ASK_DIALOG)
+                else NCResult("Nothing to cancel", ASK_RESULT)
+            onFinish(ord)
+            res
 
     @NCIntent(
         "intent=order " +
@@ -102,25 +147,19 @@ class OrderModel extends NCModelAdapter(
         if pizzaKinds.isEmpty && drinks.isEmpty then throw new 
NCRejection("Please order some pizza or drinks")
         if pizzaSizes.size > pizzaKinds.size then throw new NCRejection("Pizza 
and their sizes cannot be recognized")
 
-        case class Holder(entity: NCEntity, position: Double)
-
         val ord = getOrder(im)
-        val hsSizes = mutable.ArrayBuffer.empty ++ pizzaSizes.map(p => 
Holder(p, getAvgPosition(p)))
-
-        // Pizza. Each pizza can be specified by its size. Or size will be 
asked additionally.
-        pizzaKinds.foreach(p => {
-            hsSizes.size match
-                case 0 => ord.addPizza(extractPizzaKind(p))
-                case _ =>
-                    val avgPos = getAvgPosition(p)
-                    val nextNeighbour = hsSizes.minBy(p => Math.abs(avgPos - 
p.position))
-                    ord.addPizza(extractPizzaKind(p), 
extractPizzaSize(nextNeighbour.entity))
-                    hsSizes -= nextNeighbour
-        })
+        onStart(im, ord)
+        val m = withRelations(pizzaKinds, pizzaSizes, Seq.empty, 
im.getContext.getTokens.asScala.toSeq)
+
+        for ((p, sz) <- m)
+            val pz = extractPizzaKind(p)
+            if sz != null then ord.addPizza(pz, extractPizzaSize(sz)) else 
ord.addPizza(pz)
 
         for (p <- drinks.map(extractDrink)) ord.addDrink(p)
 
-        confirmOrSpecify(ord)
+        val res = confirmOrSpecify(ord)
+        onFinish(ord)
+        res
 
     @NCIntent(
         "intent=specifyPizzaSize " +
@@ -137,10 +176,14 @@ class OrderModel extends NCModelAdapter(
         val ord = getOrder(im)
         require(!ord.isValid())
 
+        onStart(im, ord)
+
         val sz = extractPizzaSize(size)
 
         pizzaOpt match
             case Some(pizza) => ord.addPizza(extractPizzaKind(pizza), sz)
             case None => if !ord.specifyPizzaSize(sz) then throw new 
NCRejection("What specified?")
 
-        confirmOrSpecify(ord)
\ No newline at end of file
+        val res = confirmOrSpecify(ord)
+        onFinish(ord)
+        res
\ No newline at end of file
diff --git a/nlpcraft-examples/order/src/main/resources/order_model.yaml 
b/nlpcraft-examples/order/src/main/resources/order_model.yaml
index 660601a..b7c4c30 100644
--- a/nlpcraft-examples/order/src/main/resources/order_model.yaml
+++ b/nlpcraft-examples/order/src/main/resources/order_model.yaml
@@ -59,6 +59,12 @@ elements:
       - "{no|nope|incorrect|wrong}"
       - "{you are|*} {not|are not|aren't } {correct|right}"
 
+  - id: "ord:confirm:continue"
+    description: "Conformation (continue)."
+    groups: ["confirm"]
+    synonyms:
+      - "{continue}"
+
   - id: "ord:stop"
     description: "Stop and cancel all."
     synonyms:
diff --git 
a/nlpcraft-examples/order/src/test/java/org/apache/nlpcraft/examples/order/OrderModelCli.scala
 
b/nlpcraft-examples/order/src/test/java/org/apache/nlpcraft/examples/order/OrderModelCli.scala
new file mode 100644
index 0000000..344bc85
--- /dev/null
+++ 
b/nlpcraft-examples/order/src/test/java/org/apache/nlpcraft/examples/order/OrderModelCli.scala
@@ -0,0 +1,34 @@
+package org.apache.nlpcraft.examples.order
+
+import com.typesafe.scalalogging.LazyLogging
+import org.apache.nlpcraft.examples.order.OrderModelCli.logger
+import org.apache.nlpcraft.*
+import org.apache.nlpcraft.NCResultType.*
+
+import scala.util.Using
+
+object OrderModelCli extends App with LazyLogging :
+    main()
+
+    private def main(): Unit = Using.resource(new NCModelClient(new 
OrderModel)) { client =>
+        logger.info("Application started.")
+
+        var resp: NCResult = null
+
+        while (true)
+            val prompt =
+                if resp == null || resp.getType == ASK_RESULT then "Ask your 
question to the model: "
+                else "Your should answer on the model's question: "
+            logger.info(s">>>>>>>>>> $prompt")
+
+            try
+                resp = client.ask(scala.io.StdIn.readLine(), null, "userId")
+
+                logger.info(s">>>>>>>>>> Response type: ${resp.getType}")
+                logger.info(s">>>>>>>>>> Response body:\n${resp.getBody}")
+            catch
+                case e: NCRejection => logger.info(s"Request rejected: 
${e.getMessage}")
+                case e: Throwable => logger.error(s"Unexpected error: 
${e.getMessage}")
+                    logger.error("Application exit.")
+                    System.exit(-1)
+    }
diff --git 
a/nlpcraft-examples/order/src/test/java/org/apache/nlpcraft/examples/order/OrderModelSpec.scala
 
b/nlpcraft-examples/order/src/test/java/org/apache/nlpcraft/examples/order/OrderModelSpec.scala
index e8bb51a..7f43742 100644
--- 
a/nlpcraft-examples/order/src/test/java/org/apache/nlpcraft/examples/order/OrderModelSpec.scala
+++ 
b/nlpcraft-examples/order/src/test/java/org/apache/nlpcraft/examples/order/OrderModelSpec.scala
@@ -38,7 +38,7 @@ class OrderModelSpec:
                 val resp = client.ask(txt, null, "userId")
 
                 buf += s">> $txt"
-                buf += s">> ${resp.getBody} (${resp.getType})"
+                buf += s">> '${resp.getType}': ${resp.getBody}"
                 buf += ""
 
                 if expResType != resp.getType then

Reply via email to