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

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


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

commit 19bb39bda881367fdc43e399f10f1155eb6614c6
Author: skhdl <[email protected]>
AuthorDate: Wed Oct 19 19:03:25 2022 +0400

    WIP.
---
 _includes/left-side-menu.html |  13 +-
 examples/calculator.html      |   2 +-
 examples/pizzeria.html        | 896 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 900 insertions(+), 11 deletions(-)

diff --git a/_includes/left-side-menu.html b/_includes/left-side-menu.html
index 8b4e1d0..0ac7ba6 100644
--- a/_includes/left-side-menu.html
+++ b/_includes/left-side-menu.html
@@ -160,17 +160,10 @@
         {% endif %}
     </li>
     <li>
-        {% if page.id == "weather_bot" %}
-        <a class="active" href="/examples/weather_bot.html">Weather Bot</a>
+        {% if page.id == "pizzeria" %}
+        <a class="active" href="/examples/pizzeria.html">Pizzeria</a>
         {% else %}
-        <a href="/examples/weather_bot.html">Weather Bot</a>
-        {% endif %}
-    </li>
-    <li>
-        {% if page.id == "sql_model" %}
-        <a class="active" href="/examples/sql_model.html">SQL Model</a>
-        {% else %}
-        <a href="/examples/sql_model.html">SQL Model</a>
+        <a href="/examples/pizzeria.html">Pizzeria</a>
         {% endif %}
     </li>
 </ul>
diff --git a/examples/calculator.html b/examples/calculator.html
index a4715bc..2e25b30 100644
--- a/examples/calculator.html
+++ b/examples/calculator.html
@@ -164,7 +164,7 @@ fa_icon: fa-cube
         <ul>
             <li>
                 On <code>line 11</code> declared <code>CalculatorModel</code>, 
model companion object, which contains
-                static context and helper methods.
+                static content and helper methods.
             </li>
             <li>
                 On <code>line 12</code> defined arithmetic operations map, 
with notations as keys and functions definitions as values.
diff --git a/examples/pizzeria.html b/examples/pizzeria.html
new file mode 100644
index 0000000..c1f95ee
--- /dev/null
+++ b/examples/pizzeria.html
@@ -0,0 +1,896 @@
+---
+active_crumb: Pizzeria <code><sub>ex</sub></code>
+layout: documentation
+id: pizzeria
+fa_icon: fa-cube
+---
+
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<div class="col-md-8 second-column example">
+    <section id="overview">
+        <h2 class="section-title">Overview <a href="#"><i class="top-link fas 
fa-fw fa-angle-double-up"></i></a></h2>
+        <p>
+            This example provides a simple pizzeria ordering system.
+            It demonstrates how to work with <code>ASK_DIALOG</code> states in 
callbacks and
+            how to process the systems which require confirmation logic.
+        </p>
+        <p>
+            <b>Complexity:</b> <span class="complexity-three-star"><i 
class="fas fa-gem"></i> <i class="fas fa-gem"></i> <i class="fas 
fa-gem"></i></span><br/>
+            <span class="ex-src">Source code: <a target="github" 
href="https://github.com/apache/incubator-nlpcraft/tree/master/nlpcraft-examples/caclulator";>GitHub
 <i class="fab fa-fw fa-github"></i></a><br/></span>
+            <span class="ex-review-all">Review: <a target="github" 
href="https://github.com/apache/incubator-nlpcraft/tree/master/nlpcraft-examples";>All
 Examples at GitHub <i class="fab fa-fw fa-github"></i></a></span>
+        </p>
+    </section>
+    <section id="new_project">
+        <h2 class="section-title">Create New Project <a href="#"><i 
class="top-link fas fa-fw fa-angle-double-up"></i></a></h2>
+        <p>
+            You can create new Scala projects in many ways - we'll use SBT
+            to accomplish this task. Make sure that <code>build.sbt</code> 
file has the following content:
+        </p>
+        <pre class="brush: js, highlight: []">
+            ThisBuild / version := "0.1.0-SNAPSHOT"
+            ThisBuild / scalaVersion := "3.1.3"
+            lazy val root = (project in file("."))
+              .settings(
+                name := "NLPCraft Calculator Example",
+                version := "{{site.latest_version}}",
+                libraryDependencies += "org.apache.nlpcraft" % "nlpcraft" % 
"{{site.latest_version}}",
+                libraryDependencies += "org.scalatest" %% "scalatest" % 
"3.2.14" % "test"
+              )
+        </pre>
+        <p><b>NOTE: </b>use the latest versions of Scala and ScalaTest.</p>
+        <p>Create the following files so that resulting project structure 
would look like the following:</p>
+        <ul>
+            <li><code>pizzeria_model.yaml</code> - YAML configuration file, 
which contains model description.</li>
+            <li><code>PizzeriaModel.scala</code> - Scala class, model 
implementation.</li>
+            <li><code>PizzeriaOrder.scala</code> - Scala class, pizzeria order 
state representation.</li>
+            <li><code>PizzeriaModelPipeline.scala</code> - Scala class, model 
pipeline.</li>
+            <li><code>PizzeriaOrderMapper.scala</code> - Scala class, 
<code>NCEntityMapper</code> custom implementation.</li>
+            <li><code>PizzeriaOrderValidator.scala</code> - Scala class, 
<code>NCEntityValidator</code> custom implementation.</li>
+            <li><code>PizzeriaModelSpec.scala</code> - Scala tests class, 
which allows to test your model.</li>
+        </ul>
+        <pre class="brush: plain, highlight: [7, 11, 12, 13, 14, 15, 19]">
+            |  build.sbt
+            +--project
+            |    build.properties
+            \--src
+               +--main
+               |  +--resources
+               |  |  pizzeria_model.yaml
+               |  \--scala
+               |     \--demo
+               |        \--components
+               |             PizzeriaModelPipeline.scala
+               |             PizzeriaOrderMapper.scala
+               |             PizzeriaOrderValidator.scala
+               |          PizzeriaModel.scala
+               |          PizzeriaOrder.scala
+               \--test
+                  \--scala
+                     \--demo
+                          PizzeriaModelSpec.scala
+        </pre>
+    </section>
+    <section id="model">
+        <h2 class="section-title">Data Model<a href="#"><i class="top-link fas 
fa-fw fa-angle-double-up"></i></a></h2>
+        <p>
+            We are going to start with declaring the static part of our model 
using YAML which we will later load using
+            <code>NCModelAdapter</code> in our Scala-based model 
implementation.
+            Open <code>src/main/resources/<b>pizzeria_model.yaml</b></code>
+            file and replace its content with the following YAML:
+        </p>
+        <pre class="brush: js, highlight: [2, 9, 16, 23, 29, 35, 40, 46, 51]">
+             elements:
+              - id: "ord:pizza"
+                description: "Kinds of pizza."
+                values:
+                  "margherita": [ ]
+                  "carbonara": [ ]
+                  "marinara": [ ]
+
+              - id: "ord:pizza:size"
+                description: "Size of pizza."
+                values:
+                  "small": [ "{small|smallest|min|minimal|tiny} 
{size|piece|_}" ]
+                  "medium": [ "{medium|intermediate|normal|regular} 
{size|piece|_}" ]
+                  "large": [ "{big|biggest|large|max|maximum|huge|enormous} 
{size|piece|_}" ]
+
+              - id: "ord:drink"
+                description: "Kinds of drinks."
+                values:
+                  "tea": [ ]
+                  "coffee": [ ]
+                  "cola": [ "{pepsi|sprite|dr. pepper|dr 
pepper|fanta|soda|cola|coca cola|cocacola|coca-cola}" ]
+
+              - id: "ord:yes"
+                description: "Confirmation (yes)."
+                synonyms:
+                  - 
"{yes|yeah|right|fine|nice|excellent|good|correct|sure|ok|exact|exactly|agree}"
+                  - "{you are|_} {correct|right}"
+
+              - id: "ord:no"
+                description: "Confirmation (no)."
+                synonyms:
+                  - "{no|nope|incorrect|wrong}"
+                  - "{you are|_} {not|are not|aren't} {correct|right}"
+
+              - id: "ord:stop"
+                description: "Stop and cancel all."
+                synonyms:
+                  - "{stop|cancel|clear|interrupt|quit|close} 
{it|all|everything|_}"
+
+              - id: "ord:status"
+                description: "Order status information."
+                synonyms:
+                  - "{present|current|_} {order|_} 
{status|state|info|information}"
+                  - "what {already|_} ordered"
+
+              - id: "ord:finish"
+                description: "The order is over."
+                synonyms:
+                  - "{i|everything|order|_} {be|_} 
{finish|ready|done|over|confirmed}"
+
+              - id: "ord:menu"
+                description: "Order menu."
+                synonyms:
+                  - "{menu|carte|card}"
+                  - "{products|goods|food|item|_} list"
+                  - "{hi|help|hallo}"
+        </pre>
+        <p>There are number of important points here:</p>
+        <ul>
+            <li>
+                <code>Lines 1, 9, 16</code> define order elements, which 
present parts of orders.
+            </li>
+            <li>
+                <code>Lines 35, 40, 46, 51</code> define command elements, 
which are used to control order state.
+            </li>
+            <li>
+                <code>Lines 23, 29</code> define confirmation elements, which 
are used for commands confirmations or canceling.
+            </li>
+        </ul>
+        <div class="bq info">
+            <p><b>YAML vs. API</b></p>
+            <p>
+                As usual, this YAML-based static model definition is 
convenient but totally optional. All elements definitions
+                can be provided programmatically inside Scala model 
<code>PizzeriaModel</code> class as well.
+            </p>
+        </div>
+    </section>
+    <section id="code">
+        <h2 class="section-title">Model Class <a href="#"><i class="top-link 
fas fa-fw fa-angle-double-up"></i></a></h2>
+        <p>
+            Open <code>src/main/scala/demo/<b>PizzeriaModel.scala</b></code> 
file and replace its content with the following code:
+        </p>
+        <pre class="brush: scala, highlight: [12, 27, 114, 115, 116, 126, 127, 
135, 136, 143, 145, 147, 148, 158, 159, 168, 170, 173, 174, 187, 188, 201]">
+            package demo
+
+            import com.typesafe.scalalogging.LazyLogging
+            import org.apache.nlpcraft.*
+            import org.apache.nlpcraft.NCResultType.*
+            import org.apache.nlpcraft.annotations.*
+            import demo.{PizzeriaOrder as Order, PizzeriaOrderState as State}
+            import demo.PizzeriaOrderState.*
+            import demo.components.PizzeriaModelPipeline
+            import org.apache.nlpcraft.nlp.*
+
+            object PizzeriaExtractors:
+                def extractPizzaSize(e: NCEntity): String = 
e[String]("ord:pizza:size:value")
+                def extractQty(e: NCEntity, qty: String): Option[Int] =
+                    Option.when(e.contains(qty))(e[String](qty).toDouble.toInt)
+                def extractPizza(e: NCEntity): Pizza =
+                    Pizza(
+                        e[String]("ord:pizza:value"),
+                        e.get[String]("ord:pizza:size"),
+                        extractQty(e, "ord:pizza:qty")
+                    )
+                def extractDrink(e: NCEntity): Drink =
+                    Drink(e[String]("ord:drink:value"), extractQty(e, 
"ord:drink:qty"))
+
+            import PizzeriaExtractors.*
+
+            object PizzeriaModel extends LazyLogging:
+                type Result = (NCResult, State)
+                private val UNEXPECTED_REQUEST =
+                    new NCRejection("Unexpected request for current dialog 
context.")
+
+                private def getCurrentOrder()(using ctx: NCContext): Order =
+                    val sess = ctx.getConversation.getData
+                    val usrId = ctx.getRequest.getUserId
+                    sess.get[Order](usrId) match
+                        case Some(ord) => ord
+                        case None =>
+                            val ord = new Order()
+                            sess.put(usrId, ord)
+                            ord
+
+                private def mkResult(msg: String): NCResult = NCResult(msg, 
ASK_RESULT)
+                private def mkDialog(msg: String): NCResult = NCResult(msg, 
ASK_DIALOG)
+
+                private def doRequest(body: Order => Result)(using ctx: 
NCContext, im: NCIntentMatch): NCResult =
+                    val o = getCurrentOrder()
+
+                    logger.info(s"Intent '${im.getIntentId}' activated for 
text: '${ctx.getRequest.getText}'.")
+                    logger.info(s"Before call [desc=${o.getState.toString}, 
resState: $o.")
+
+                    val (res, resState) = body.apply(o)
+                    o.setState(resState)
+
+                    logger.info(s"After call [desc=$o, resState: $resState.")
+
+                    res
+
+                private def askIsReady(): Result = mkDialog("Is order ready?") 
-> DIALOG_IS_READY
+
+                private def askSpecify(o: Order): Result =
+                    require(!o.isValid)
+
+                    o.findPizzaWithoutSize match
+                        case Some(p) =>
+                            mkDialog(s"Choose size (large, medium or small) 
for: '${p.name}'") -> DIALOG_SPECIFY
+                        case None =>
+                            require(o.isEmpty)
+                            mkDialog("Please order something. Ask `menu` to 
look what you can order.") ->
+                                DIALOG_SPECIFY
+
+                private def askShouldStop(): Result =
+                    mkDialog("Should current order be canceled?") ->
+                        DIALOG_SHOULD_CANCEL
+
+                private def doShowMenuResult(): NCResult =
+                    mkResult(
+                        "There are accessible for order: margherita, carbonara 
and marinara. " +
+                        "Sizes: large, medium or small. " +
+                        "Also there are tea, coffee and cola."
+                    )
+
+                private def doShowMenu(state: State): Result = 
doShowMenuResult() -> state
+
+                private def doShowStatus(o: Order, state: State): Result =
+                    mkResult(s"Current order state: $o.") -> state
+
+                private def askConfirm(o: Order): Result =
+                    require(o.isValid)
+                    mkDialog(s"Let's specify your order: $o. Is it correct?") 
-> DIALOG_CONFIRM
+
+                private def doResultWithClear(msg: String)(using ctx: 
NCContext, im: NCIntentMatch): Result =
+                    val conv = ctx.getConversation
+                    conv.getData.remove(ctx.getRequest.getUserId)
+                    conv.clearStm(_ => true)
+                    conv.clearDialog(_ => true)
+                    mkResult(msg) -> DIALOG_EMPTY
+
+                private def doStop(o: Order)(using ctx: NCContext, im: 
NCIntentMatch): Result =
+                    doResultWithClear(
+                        if !o.isEmpty then "Everything cancelled. Ask `menu` 
to look what you can order."
+                        else "Nothing to cancel. Ask `menu` to look what you 
can order."
+                    )
+
+                private def doContinue(): Result = mkResult("OK, please 
continue.") -> DIALOG_EMPTY
+                private def askConfirmOrAskSpecify(o: Order): Result =
+                    if o.isValid then askConfirm(o) else askSpecify(o)
+                private def askIsReadyOrAskSpecify(o: Order): Result =
+                    if o.isValid then askIsReady() else askSpecify(o)
+                private def askStopOrDoStop(o: Order)(using ctx: NCContext, 
im: NCIntentMatch): Result =
+                    if o.isValid then askShouldStop() else doStop(o)
+
+            import org.apache.nlpcraft.examples.pizzeria.PizzeriaModel.*
+
+            class PizzeriaModel extends NCModelAdapter(
+                NCModelConfig("nlpcraft.pizzeria.ex", "Pizzeria Example 
Model", "1.0"),
+                PizzeriaModelPipeline.PIPELINE
+            ) with LazyLogging:
+                // This method is defined in class scope and has package 
access level for tests reasons.
+                private[pizzeria] def doExecute(o: Order)(using ctx: 
NCContext, im: NCIntentMatch): Result =
+                    require(o.isValid)
+                    doResultWithClear(s"Executed: $o.")
+
+                private def doExecuteOrAskSpecify(o: Order)(using ctx: 
NCContext, im: NCIntentMatch): Result =
+                    if o.isValid then doExecute(o) else askSpecify(o)
+
+                @NCIntent("intent=yes term(yes)={# == 'ord:yes'}")
+                def onYes(using ctx: NCContext, im: NCIntentMatch): NCResult = 
doRequest(
+                    o => o.getState match
+                        case DIALOG_CONFIRM => doExecute(o)
+                        case DIALOG_SHOULD_CANCEL => doStop(o)
+                        case DIALOG_IS_READY => askConfirmOrAskSpecify(o)
+                        case DIALOG_SPECIFY | DIALOG_EMPTY => throw 
UNEXPECTED_REQUEST
+                )
+
+                @NCIntent("intent=no term(no)={# == 'ord:no'}")
+                def onNo(using ctx: NCContext, im: NCIntentMatch): NCResult = 
doRequest(
+                    o => o.getState match
+                        case DIALOG_CONFIRM | DIALOG_IS_READY => doContinue()
+                        case DIALOG_SHOULD_CANCEL => askConfirmOrAskSpecify(o)
+                        case DIALOG_SPECIFY | DIALOG_EMPTY => throw 
UNEXPECTED_REQUEST
+                )
+
+                @NCIntent("intent=stop term(stop)={# == 'ord:stop'}")
+                // It doesn't depend on order validity and dialog state.
+                def onStop(using ctx: NCContext, im: NCIntentMatch): NCResult 
= doRequest(askStopOrDoStop)
+
+                @NCIntent("intent=status term(status)={# == 'ord:status'}")
+                def onStatus(using ctx: NCContext, im: NCIntentMatch): 
NCResult = doRequest(
+                    o => o.getState match
+                        // Ignore `status`, confirm again.
+                        case DIALOG_CONFIRM => askConfirm(o)
+                        // Changes state.
+                        case DIALOG_SHOULD_CANCEL => doShowStatus(o, 
DIALOG_EMPTY)
+                         // Keeps same state.
+                        case DIALOG_EMPTY | DIALOG_IS_READY | DIALOG_SPECIFY 
=> doShowStatus(o, o.getState)
+                )
+
+                @NCIntent("intent=finish term(finish)={# == 'ord:finish'}")
+                def onFinish(using ctx: NCContext, im: NCIntentMatch): 
NCResult = doRequest(
+                    o => o.getState match
+                        // Like YES if valid.
+                        case DIALOG_CONFIRM => doExecuteOrAskSpecify(o)
+                        // Ignore `finish`, specify again.
+                        case DIALOG_SPECIFY => askSpecify(o)
+                        case DIALOG_EMPTY | DIALOG_IS_READY | 
DIALOG_SHOULD_CANCEL => askConfirmOrAskSpecify(o)
+                )
+
+                @NCIntent("intent=menu term(menu)={# == 'ord:menu'}")
+                // It doesn't depend and doesn't influence on order validity 
and dialog state.
+                def onMenu(using ctx: NCContext, im: NCIntentMatch): NCResult =
+                    doRequest(o => doShowMenu(o.getState))
+
+                @NCIntent("intent=order term(ps)={# == 'ord:pizza'}* 
term(ds)={# == 'ord:drink'}*")
+                def onOrder(
+                    using ctx: NCContext,
+                    im: NCIntentMatch,
+                    @NCIntentTerm("ps") ps: List[NCEntity],
+                    @NCIntentTerm("ds") ds: List[NCEntity]
+                ): NCResult = doRequest(
+                    o =>
+                        require(ps.nonEmpty || ds.nonEmpty);
+                        // It doesn't depend on order validity and dialog 
state.
+                        o.add(ps.map(extractPizza), ds.map(extractDrink));
+                        askIsReadyOrAskSpecify(o)
+                )
+
+                @NCIntent("intent=orderSpecify term(size)={# == 
'ord:pizza:size'}")
+                def onOrderSpecify(
+                    using ctx: NCContext,
+                    im: NCIntentMatch,
+                    @NCIntentTerm("size") size: NCEntity
+                ): NCResult =
+                    doRequest(
+                        // If order in progress and has pizza with unknown 
size, it doesn't depend on dialog state.
+                        o =>
+                            if !o.isEmpty && 
o.fixPizzaWithoutSize(extractPizzaSize(size))
+                                then askIsReadyOrAskSpecify(o)
+                                else throw UNEXPECTED_REQUEST
+                    )
+
+                override def onRejection(
+                    using ctx: NCContext, im: Option[NCIntentMatch], e: 
NCRejection
+                ): Option[NCResult] =
+                    if im.isEmpty || getCurrentOrder().isEmpty then throw e
+                    Option(doShowMenuResult())
+        </pre>
+        <p>
+            There are few intents in the given model, which allow to prepare, 
change, confirm and cancel pizzeria orders.
+            Note please that given test model supports work with one single 
user.
+            Let's review this implementation step by step:
+        </p>
+        <ul>
+            <li>
+                On <code>line 12</code> declared 
<code>PizzeriaExtractors</code>, helper object, which provides
+                conversion methods from <code>NCEntity</code> objects and 
model data objects.
+            </li>
+            <li>
+                On <code>line 27</code> defined <code>PizzeriaModel</code> 
companion object, which contains
+                static content and helper methods.
+            </li>
+            <li>
+                On <code>line 114</code> our class <code>PizzeriaModel</code> 
extends <code>NCModelAdapter</code> that allows us to pass
+                prepared configuration and pipeline into model.
+            </li>
+            <li>
+                On <code>line 115</code> created model configuration with most 
default parameters.
+            </li>
+            <li>
+                On <code>line 116</code> represented pipeline, prepared in 
<code>PizzeriaModelPipeline</code> class.
+            </li>
+
+            <li>
+                <code>Lines 173 and 174</code> annotates intents 
<code>order</code> and its callback method <code>onOrder</code>.
+                Intent <code>order</code> requires lists of pizza and drinks 
in the order.
+                Note please, that at least one of these lists shouldn't be 
empty, otherwise intent is not triggered.
+                In the callback current order state is changed.
+                If order is in valid state, user receives order confirmation 
response "Is order ready?",
+                otherwise user receives response, which asks user to specify 
this order.
+                Both responses have type <code>ASK_DIALOG</code>.
+            </li>
+
+            <li>
+                Order pizza sizes can be specified by the model, as it was 
described above in <code>order</code> intent.
+                <code>Lines 187 and 188</code> annotates intents 
<code>orderSpecify</code> and its callback method <code>onOrderSpecify</code>.
+                Intent <code>orderSpecify</code> requires pizza size value 
parameter.
+                Callback checks that it was called just for suitable order 
state.
+                Current order state is changed  and user receives order 
confirmation response "Is order ready?"
+            </li>
+            <li>
+                <code>Lines 126, 127 and 135, 136</code> annotates intents 
<code>yes</code> and <code>no</code>
+                with related callbacks <code>onYes</code> and 
<code>onNo</code>.
+                These intents are expected after user received confirmation 
responses with type <code>ASK_DIALOG</code>,
+                like "Is order ready?". Callbacks change order state or send 
some another confirmation requests to user,
+                depends on current order state.
+            </li>
+            <li>
+                <code>Lines 143 and 145, 147 and 148, 158 and 159, 168 and 
170</code> annotates intents
+                <code>stop</code>, <code>status</code>, <code>finish</code> 
and <code>menu</code> intents
+                with related callbacks. They are order management commands, 
these actions are depends on current order state.
+            </li>
+            <li>
+                <code>line 201</code> annotates <code>onRejection</code> 
method,
+                which is called if there aren't triggered intents.
+                <code>stop</code>, <code>status</code>, <code>finish</code> 
and <code>menu</code> intents
+                with related callbacks. They are order management commands, 
these actions are depends on current order state.
+            </li>
+        </ul>
+
+        <p>
+            Open 
<code>src/main/scala/demo/components/<b>PizzeriaOrderValidator.scala</b></code> 
file and replace its content with the following code:
+        </p>
+        <pre class="brush: scala, highlight: []">
+            package demo.components
+
+            import org.apache.nlpcraft.*
+
+            class PizzeriaOrderValidator extends NCEntityValidator:
+                override def validate(req: NCRequest, cfg: NCModelConfig, 
ents: List[NCEntity]): Unit =
+                    def count(id: String): Int = ents.count(_.getId == id)
+
+                    val cntPizza = count("ord:pizza")
+                    val cntDrink = count("ord:drink")
+                    val cntNums = count("stanford:number")
+                    val cntSize = count("ord:pizza:size")
+
+                    // Single size - it is order specification request.
+                    if cntSize != 1 && cntSize > cntPizza then
+                        throw new NCRejection("There are unrecognized pizza 
sizes in the request, maybe because some misprints.")
+
+                    if cntNums > cntPizza + cntDrink then
+                        throw new NCRejection("There are many unrecognized 
numerics in the request, maybe because some misprints.")
+        </pre>
+
+        <p>
+            <code>PizzeriaOrderValidator</code> is implementation of 
<code>NCEntityValidator</code>.
+            It is designed for validation order content and allows right away 
to reject invalid orders.
+        <p>
+
+        <p>
+            Open 
<code>src/main/scala/demo/components/<b>PizzeriaOrderMapper.scala</b></code> 
file and replace its content with the following code:
+        </p>
+        <pre class="brush: scala, highlight: [11, 25, 30, 61]">
+            package demo
+
+            import org.apache.nlpcraft.*
+            import com.typesafe.scalalogging.LazyLogging
+            import org.apache.nlpcraft.NCResultType.ASK_DIALOG
+            import scala.collection.*
+
+            case class PizzeriaOrderMapperDesc(elementId: String, 
propertyName: String)
+
+            object PizzeriaOrderMapper:
+                extension(entity: NCEntity)
+                    def position: Double =
+                        val toks = entity.getTokens
+                        (toks.head.getIndex + toks.last.getIndex) / 2.0
+                    def tokens: List[NCToken] = entity.getTokens
+
+                private def str(es: Iterable[NCEntity]): String =
+                    es.map(e => 
s"id=${e.getId}(${e.tokens.map(_.getIndex).mkString("[", ",", "]")})").
+                        mkString("{", ", ", "}")
+
+                def apply(extra: PizzeriaOrderMapperDesc, dests: 
PizzeriaOrderMapperDesc*): PizzeriaOrderMapper =
+                    new PizzeriaOrderMapper(extra, dests)
+
+            import PizzeriaOrderMapper.*
+
+            case class PizzeriaOrderMapper(
+                extra: PizzeriaOrderMapperDesc,
+                dests: Seq[PizzeriaOrderMapperDesc]
+            ) extends NCEntityMapper with LazyLogging:
+                override def map(req: NCRequest, cfg: NCModelConfig, ents: 
List[NCEntity]): List[NCEntity] =
+                    def map(destEnt: NCEntity, destProp: String, extraEnt: 
NCEntity): NCEntity =
+                        new NCPropertyMapAdapter with NCEntity:
+                            destEnt.keysSet.foreach(k => put(k, destEnt(k)))
+                            put[String](destProp, 
extraEnt[String](extra.propertyName).toLowerCase)
+                            override val getTokens: List[NCToken] =
+                                (destEnt.tokens ++ 
extraEnt.tokens).sortBy(_.getIndex)
+                            override val getRequestId: String = 
req.getRequestId
+                            override val getId: String = destEnt.getId
+
+                    val destsMap = dests.map(p => p.elementId -> p).toMap
+                    val destEnts = mutable.HashSet.empty ++ ents.filter(e => 
destsMap.contains(e.getId))
+                    val extraEnts = ents.filter(_.getId == extra.elementId)
+
+                    if destEnts.nonEmpty && extraEnts.nonEmpty && 
destEnts.size >= extraEnts.size then
+                        val used = (destEnts ++ extraEnts).toSet
+                        val dest2Extra = mutable.HashMap.empty[NCEntity, 
NCEntity]
+
+                        for (extraEnt <- extraEnts)
+                            val destEnt = destEnts.minBy(m => 
Math.abs(m.position - extraEnt.position))
+                            destEnts -= destEnt
+                            dest2Extra += destEnt -> extraEnt
+
+                        val unrelated = ents.filter(e => !used.contains(e))
+                        val artificial = for ((m, e) <- dest2Extra) yield 
map(m, destsMap(m.getId).propertyName, e)
+                        val unused = destEnts
+
+                        val res = (unrelated ++ artificial ++ 
unused).sortBy(_.tokens.head.getIndex)
+
+                        logger.debug(s"Elements mapped [input=${str(ents)}, 
output=${str(res)}]")
+
+                        res
+                    else ents
+        </pre>
+
+        <p>
+            <code>PizzeriaOrderMapper</code> is implementation of 
<code>NCEntityMapper</code>.
+            It is designed for building complex compound entities based on 
another entities.
+        <p>
+        <ul>
+            <li>
+                On <code>line 11</code> declared 
<code>PizzeriaOrderMapper</code>, model companion object, which contains
+                helper methods.
+            </li>
+            <li>
+                On <code>line 25</code> declared 
<code>PizzeriaOrderMapper</code> model which implements 
<code>NCEntityMapper</code>.
+            </li>
+            <li>
+                On <code>line 30</code> defined helper method 
<code>map</code>, which clones <code>destEn</code> entity,
+                extend it by <code>extraEnt</code> tokens and 
<code>destProp</code> property and returns new entities
+                instead of passed inti the method.
+            </li>
+            <li>
+                <code>Line 61</code> defines <code>PizzeriaOrderMapper</code> 
result entities,
+                which will be processed further instead of passed into this 
component method.
+            </li>
+        </ul>
+
+        <p>
+            Open 
<code>src/main/scala/demo/components/<b>PizzeriaModelPipeline.scala</b></code> 
file and replace its content with the following code:
+        </p>
+        <pre class="brush: scala, highlight: [14, 31, 37, 43]">
+            package demo.components
+
+            import edu.stanford.nlp.pipeline.StanfordCoreNLP
+            import opennlp.tools.stemmer.PorterStemmer
+            import org.apache.nlpcraft.nlp.parsers.*
+            import 
org.apache.nlpcraft.nlp.entity.parser.stanford.NCStanfordNLPEntityParser
+            import 
org.apache.nlpcraft.nlp.token.parser.stanford.NCStanfordNLPTokenParser
+            import org.apache.nlpcraft.*
+            import org.apache.nlpcraft.nlp.enrichers.NCEnStopWordsTokenEnricher
+            import org.apache.nlpcraft.nlp.parsers.{NCSemanticEntityParser, 
NCSemanticStemmer}
+            import java.util.Properties
+
+            object PizzeriaModelPipeline:
+                val PIPELINE: NCPipeline =
+                    val stanford =
+                        val props = new Properties()
+                        props.setProperty("annotators", "tokenize, ssplit, 
pos, lemma, ner")
+                        new StanfordCoreNLP(props)
+                    val tokParser = new NCStanfordNLPTokenParser(stanford)
+                    val stemmer = new NCSemanticStemmer():
+                        private val ps = new PorterStemmer
+                        override def stem(txt: String): String = 
ps.synchronized { ps.stem(txt) }
+
+                    import PizzeriaOrderMapperDesc as D
+
+                    new NCPipelineBuilder().
+                        withTokenParser(tokParser).
+                        withTokenEnricher(new NCEnStopWordsTokenEnricher()).
+                        withEntityParser(new 
NCStanfordNLPEntityParser(stanford, Set("number"))).
+                        withEntityParser(NCSemanticEntityParser(stemmer, 
tokParser, "pizzeria_model.yaml")).
+                        withEntityMapper(
+                            PizzeriaOrderMapper(
+                                extra = D("ord:pizza:size", 
"ord:pizza:size:value"),
+                                dests = D("ord:pizza", "ord:pizza:size")
+                            )
+                        ).
+                        withEntityMapper(
+                            PizzeriaOrderMapper(
+                                extra = D("stanford:number", 
"stanford:number:nne"),
+                                dests = D("ord:pizza", "ord:pizza:qty"), 
D("ord:drink", "ord:drink:qty")
+                            )
+                        ).
+                        withEntityValidator(new PizzeriaOrderValidator()).
+                        build
+        </pre>
+        <p>
+            In <code>PizzeriaModelPipeline</code> prepares model pipeline.
+        <p>
+        <ul>
+            <li>
+                On <code>line 14</code> pipeline is defined.
+            </li>
+            <li>
+                On <code>line 30</code> declared 
<code>NCSemanticEntityParser</code>
+                based on YAM model definition <code>pizzeria_model.yaml</code>.
+            </li>
+            <li>
+                On <code>lines 31 and 37</code> defined entity mappers 
<code>PizzeriaOrderMapper</code>, which
+                map <code>ord:pizza</code> elements with theirs sizes from 
<code>ord:pizza:size</code> and
+                quantities from <code>stanford:number</code>.
+            </li>
+            <li>
+                <code>Line 43</code> defines 
<code>PizzeriaOrderValidator</code> class, described above.
+            </li>
+        </ul>
+
+    </section>
+
+    <section id="testing">
+        <h2 class="section-title">Testing <a href="#"><i class="top-link fas 
fa-fw fa-angle-double-up"></i></a></h2>
+        <p>
+            The test defined in <code>CalculatorModelSpec</code> allows to 
check that all input test sentences are
+            processed correctly and trigger the expected intents 
<code>calc</code> and <code>calcMem</code>:
+        </p>
+        <pre class="brush: scala, highlight: [14, 48, 61, 96]">
+            package demo
+
+            import org.apache.nlpcraft.*
+            import org.apache.nlpcraft.NCResultType.*
+            import demo.PizzeriaModel.Result
+            import demo.PizzeriaOrderState.*
+            import org.scalatest.BeforeAndAfter
+            import org.scalatest.funsuite.AnyFunSuite
+
+            import scala.language.implicitConversions
+            import scala.util.Using
+            import scala.collection.mutable
+
+            object PizzeriaModelSpec:
+                type Request = (String, NCResultType)
+                private class ModelTestWrapper extends PizzeriaModel:
+                    private var o: PizzeriaOrder = _
+
+                    override def doExecute(o: PizzeriaOrder)(using ctx: 
NCContext, im: NCIntentMatch): Result =
+                        val res = super.doExecute(o)
+                        this.o = o
+                        res
+
+                    def getLastExecutedOrder: PizzeriaOrder = o
+                    def clearLastExecutedOrder(): Unit = o = null
+
+                private class Builder:
+                    private val o = new PizzeriaOrder
+                    o.setState(DIALOG_EMPTY)
+                    def withPizza(name: String, size: String, qty: Int): 
Builder =
+                        o.add(Seq(Pizza(name, Some(size), Some(qty))), 
Seq.empty)
+                        this
+                    def withDrink(name: String, qty: Int): Builder =
+                        o.add(Seq.empty, Seq(Drink(name, Some(qty))))
+                        this
+                    def build: PizzeriaOrder = o
+
+            import PizzeriaModelSpec.*
+
+            class PizzeriaModelSpec extends AnyFunSuite with BeforeAndAfter:
+                private val mdl = new ModelTestWrapper()
+                private val client = new NCModelClient(mdl)
+                private val msgs = 
mutable.ArrayBuffer.empty[mutable.ArrayBuffer[String]]
+                private val errs = mutable.HashMap.empty[Int, Throwable]
+
+                private var testNum: Int = 0
+
+                after {
+                    if client != null then client.close()
+
+                    for ((seq, num) <- msgs.zipWithIndex)
+                        println("#" * 150)
+                        for (line <- seq) println(line)
+                        errs.get(num) match
+                            case Some(err) => err.printStackTrace()
+                            case None => // No-op.
+
+                    require(errs.isEmpty, s"There are ${errs.size} errors 
above.")
+                }
+
+                private def dialog(exp: PizzeriaOrder, reqs: Request*): Unit =
+                    val testMsgs = mutable.ArrayBuffer.empty[String]
+                    msgs += testMsgs
+
+                    testMsgs += s"Test: $testNum"
+
+                    for (((txt, expType), idx) <- reqs.zipWithIndex)
+                        try
+                            mdl.clearLastExecutedOrder()
+                            val resp = client.ask(txt, "userId")
+
+                            testMsgs += s">> Request: $txt"
+                            testMsgs += s">> Response: '${resp.getType}': 
${resp.getBody}"
+
+                            if expType != resp.getType then
+                                errs += testNum -> new Exception(s"Unexpected 
result type [num=$testNum, txt=$txt, expected=$expType, type=${resp.getType}]")
+
+                            // Check execution result on last request.
+                            if idx == reqs.size - 1 then
+                                val lastOrder = mdl.getLastExecutedOrder
+                                def s(o: PizzeriaOrder) = if o == null then 
null else s"Order [state=${o.getState}, desc=$o]"
+                                val s1 = s(exp)
+                                val s2 = s(lastOrder)
+                                if s1 != s2 then
+                                    errs += testNum ->
+                                        new Exception(
+                                            s"Unexpected result [num=$testNum, 
txt=$txt]" +
+                                            s"\nExpected: $s1" +
+                                            s"\nReal    : $s2"
+                                        )
+                        catch
+                            case e: Exception => errs += testNum -> new 
Exception(s"Error during test [num=$testNum]", e)
+
+                    testNum += 1
+
+                test("test") {
+                    given Conversion[String, Request] with
+                        def apply(txt: String): Request = (txt, ASK_DIALOG)
+
+                    dialog(
+                        new Builder().withDrink("tea", 2).build,
+                        "Two tea",
+                        "yes",
+                        "yes" -> ASK_RESULT
+                    )
+
+                    dialog(
+                        new Builder().
+                            withPizza("carbonara", "large", 1).
+                            withPizza("marinara", "small", 1).
+                            withDrink("tea", 1).
+                            build,
+                        "I want to order carbonara, marinara and tea",
+                        "large size please",
+                        "smallest",
+                        "yes",
+                        "correct" -> ASK_RESULT
+                    )
+
+                    dialog(
+                        new Builder().withPizza("carbonara", "small", 2).build,
+                        "carbonara two small",
+                        "yes",
+                        "yes" -> ASK_RESULT
+                    )
+
+                    dialog(
+                        new Builder().withPizza("carbonara", "small", 1).build,
+                        "carbonara",
+                        "small",
+                        "yes",
+                        "yes" -> ASK_RESULT
+                    )
+
+                    dialog(
+                        null,
+                        "marinara",
+                        "stop" -> ASK_RESULT
+                    )
+
+                    dialog(
+                        new Builder().
+                            withPizza("carbonara", "small", 2).
+                            withPizza("marinara", "large", 4).
+                            withDrink("cola", 3).
+                            withDrink("tea", 1).
+                            build,
+                        "3 cola",
+                        "one tea",
+                        "carbonara 2",
+                        "small",
+                        "4 marinara big size",
+                        "menu" -> ASK_RESULT,
+                        "done",
+                        "yes" -> ASK_RESULT
+                    )
+
+                    dialog(
+                        new Builder().
+                            withPizza("margherita", "small", 2).
+                            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
+                    )
+                }
+        </pre>
+
+        <p>
+            <code>PizzeriaModelSpec</code> is complex test, which is designed 
as dialog with Pizzeria bot.
+        </p>
+
+        <ul>
+            <li>
+                On <code>line 14</code> declared 
<code>PizzeriaModelSpec</code>, test companion object, which contains
+                static content and helper methods.
+            </li>
+            <li>
+                On <code>line 48</code> defined <code>after</code> block.
+                It closes model client and prints test results.
+            </li>
+            <li>
+                On <code>line 61</code> defined test helper method 
<code>dialog</code>.
+                It sends request to model via <code>ask</code> method and 
accumulates execution results.
+            </li>
+            <li>
+                On <code>line 96</code> defined main test block.
+                It contains user request descriptions and expected results on 
them, taking into account order state.
+            </li>
+        </ul>
+        <p>
+            You can run this test via SBT task <code>executeTests</code> or 
using IDE.
+        </p>
+        <pre class="brush: scala, highlight: []">
+            PS C:\apache\incubator-nlpcraft-examples\pizzeria> sbt executeTests
+        </pre>
+    </section>
+    <section>
+        <h2 class="section-title">Done! 👌 <a href="#"><i class="top-link fas 
fa-fw fa-angle-double-up"></i></a></h2>
+        <p>
+            You've created pizzeria model and tested it.
+        </p>
+    </section>
+</div>
+<div class="col-md-2 third-column">
+    <ul class="side-nav">
+        <li class="side-nav-title">On This Page</li>
+        <li><a href="#overview">Overview</a></li>
+        <li><a href="#new_project">New Project</a></li>
+        <li><a href="#model">Data Model</a></li>
+        <li><a href="#code">Model Class</a></li>
+        <li><a href="#testing">Testing</a></li>
+        {% include quick-links.html %}
+    </ul>
+</div>
+
+
+
+
+
+


Reply via email to