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

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

commit 369cd9922133f57abe8f2087703cd6c9e156e89d
Author: Sergey Kamov <[email protected]>
AuthorDate: Thu Jul 29 14:31:37 2021 +0300

    WIP.
---
 .../model/intent/solver/NCIntentSolver.scala       |  70 ++++----
 .../mgrs/nlp/enrichers/sort/NCSortEnricher.scala   |  20 +--
 .../nlpcraft/models/stm/indexes/NCLimitSpec.scala  |  75 ++++++++-
 .../models/stm/indexes/NCRelationSpec.scala        |  74 ++++++++-
 .../nlpcraft/models/stm/indexes/NCSortSpec.scala   | 180 ++++++++++++++++++---
 .../models/stm/indexes/NCSpecModelAdapter.scala    |  11 +-
 .../nlp/enrichers/sort/NCEnricherSortSpec.scala    | 173 +-------------------
 7 files changed, 353 insertions(+), 250 deletions(-)

diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/solver/NCIntentSolver.scala
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/solver/NCIntentSolver.scala
index ba9daf5..983c36c 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/solver/NCIntentSolver.scala
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/solver/NCIntentSolver.scala
@@ -97,18 +97,16 @@ class NCIntentSolver(intents: List[(NCIdlIntent/*Intent*/, 
NCIntentMatch => NCRe
                 i += 1
 
                 val allConvToks = ctx.getConversation.getTokens.asScala
-                val nonConvToks = 
res.groups.flatMap(_.tokens).filterNot(allConvToks.contains)
+                val vrntNotConvToks = 
res.variant.tokens.asScala.filterNot(allConvToks.contains)
 
                 val intentToks =
                     res.groups.map(_.tokens).map(toks => {
                         toks.filter(allConvToks.contains).foreach(convTok =>
-                            fixBuiltTokensMeta(convTok, nonConvToks, 
allConvToks))
+                            fixBuiltTokenMeta(convTok, vrntNotConvToks, 
allConvToks))
 
                         toks.asJava
                     }).asJava
 
-                ctx.getConversation.getTokens
-
                 val intentMatch: NCIntentMatch = new NCMetadataAdapter with 
NCIntentMatch {
                     override val getContext: NCContext = ctx
                     override val getIntentTokens: JList[JList[NCToken]] = 
intentToks
@@ -177,12 +175,13 @@ class NCIntentSolver(intents: 
List[(NCIdlIntent/*Intent*/, NCIntentMatch => NCRe
 
     /**
       *
-      * @param convTok
-      * @param nonConvToks
+      * @param convTok4Fix
+      * @param vrntNotConvToks
       * @param allConvToks
       */
     @throws[NCE]
-    private def fixBuiltTokensMeta(convTok: NCToken, nonConvToks: 
Seq[NCToken], allConvToks: Seq[NCToken]): Unit = {
+    @throws[NCIntentSkip]
+    private def fixBuiltTokenMeta(convTok4Fix: NCToken, vrntNotConvToks: 
Seq[NCToken], allConvToks: Seq[NCToken]): Unit = {
         def isReference(tok: NCToken, id: String, idx: Int): Boolean = 
tok.getId == id && tok.getIndex == idx
 
         /**
@@ -193,12 +192,15 @@ class NCIntentSolver(intents: 
List[(NCIdlIntent/*Intent*/, NCIntentMatch => NCRe
           * 3. Next, for found group, it tries to find actual tokens with this 
group among non-conversation tokens.
           * If these non-conversation tokens found, they should be validated 
and returned,
           * If not found - conversation tokens returned.
-          *
+
           * @param refId Reference token ID.
           * @param refIdxs Reference indexes.
           * @param validate Validate predicate.
+          * @throws NCE It means that we sentence is invalid, internal error.
+          * @throws NCIntentSkip It means that we try to process invalid 
variant and it should be skipped.
           */
         @throws[NCE]
+        @throws[NCIntentSkip]
         def getForRecalc(refId: String, refIdxs: Seq[Int], validate: 
Seq[NCToken] => Boolean): Seq[NCToken] = {
             val convRefs = allConvToks.filter(_.getId == refId)
 
@@ -211,24 +213,24 @@ class NCIntentSolver(intents: 
List[(NCIdlIntent/*Intent*/, NCIntentMatch => NCRe
             if (commonConvGs.isEmpty)
                 throw new NCE(s"Conversation references don't have common 
group [id=$refId]")
 
-            val actualNonConvRefs = 
nonConvToks.filter(_.getGroups.asScala.exists(commonConvGs.contains))
+            val actNonConvRefs = 
vrntNotConvToks.filter(_.getGroups.asScala.exists(commonConvGs.contains))
 
-            if (actualNonConvRefs.nonEmpty) {
-                if (!validate(actualNonConvRefs))
-                    throw new NCE(
+            if (actNonConvRefs.nonEmpty) {
+                if (!validate(actNonConvRefs))
+                    throw new NCIntentSkip(
                         s"Actual valid variant references are not found for 
recalculation [" +
                             s"id=$refId, " +
-                            s"actualNonConvRefs=${actualNonConvRefs.map(p => 
s"${p.getOriginalText}(${p.getId}-${p.getIndex})").mkString(",")}" +
+                            
s"actualNonConvRefs=${actNonConvRefs.mkString(",")}" +
                             s"]"
                     )
 
-                actualNonConvRefs
+                actNonConvRefs
             }
             else
                 convRefs
         }
 
-        convTok.getId match {
+        convTok4Fix.getId match {
             case "nlpcraft:sort" =>
                 def getNotNullSeq[T](tok: NCToken, name: String): Seq[T] = {
                     val list = tok.meta[JList[T]](name)
@@ -237,8 +239,8 @@ class NCIntentSolver(intents: List[(NCIdlIntent/*Intent*/, 
NCIntentMatch => NCRe
                 }
 
                 def process(notesName: String, idxsName: String): Unit = {
-                    val refIds: Seq[String] = getNotNullSeq(convTok, 
s"nlpcraft:sort:$notesName")
-                    val refIdxs: Seq[Int] = getNotNullSeq(convTok, 
s"nlpcraft:sort:$idxsName")
+                    val refIds: Seq[String] = getNotNullSeq(convTok4Fix, 
s"nlpcraft:sort:$notesName")
+                    val refIdxs: Seq[Int] = getNotNullSeq(convTok4Fix, 
s"nlpcraft:sort:$idxsName")
 
                     require(refIds.length == refIdxs.length)
 
@@ -251,7 +253,7 @@ class NCIntentSolver(intents: List[(NCIdlIntent/*Intent*/, 
NCIntentMatch => NCRe
                         // Part of them can be in conversation, part of them - 
in actual variant.
                         refIds.zip(refIdxs).map { case (refId, refIdx) =>
                             val seq =
-                                nonConvToks.find(isReference(_, refId, 
refIdx)) match {
+                                vrntNotConvToks.find(isReference(_, refId, 
refIdx)) match {
                                     case Some(_) => data
                                     case None => notFound
                                 }
@@ -270,8 +272,8 @@ class NCIntentSolver(intents: List[(NCIdlIntent/*Intent*/, 
NCIntentMatch => NCRe
 
                             data = data.sortBy(_._2)
 
-                            
convTok.getMetadata.put(s"nlpcraft:sort:$notesName", data.map(_._1).asJava)
-                            
convTok.getMetadata.put(s"nlpcraft:sort:$idxsName", data.map(_._2).asJava)
+                            
convTok4Fix.getMetadata.put(s"nlpcraft:sort:$notesName", data.map(_._1).asJava)
+                            
convTok4Fix.getMetadata.put(s"nlpcraft:sort:$idxsName", data.map(_._2).asJava)
                         }
                     }
                 }
@@ -279,38 +281,38 @@ class NCIntentSolver(intents: 
List[(NCIdlIntent/*Intent*/, NCIntentMatch => NCRe
                 process("bynotes", "byindexes")
                 process("subjnotes", "subjindexes")
             case "nlpcraft:limit" =>
-                val refId = convTok.meta[String]("nlpcraft:limit:note")
-                val refIdxs = 
convTok.meta[JList[Int]]("nlpcraft:limit:indexes").asScala
+                val refId = convTok4Fix.meta[String]("nlpcraft:limit:note")
+                val refIdxs = 
convTok4Fix.meta[JList[Int]]("nlpcraft:limit:indexes").asScala
 
                 require(refIdxs.size == 1)
 
                 val refIdx = refIdxs.head
 
-                if (!nonConvToks.exists(isReference(_, refId, refIdx))) {
-                    val ref = getForRecalc(refId, Seq(refIdx), _.size == 
1).head
+                if (!vrntNotConvToks.exists(isReference(_, refId, refIdx))) {
+                    val newRef = getForRecalc(refId, Seq(refIdx), _.size == 
1).head
 
-                    convTok.getMetadata.put(s"nlpcraft:limit:note", ref.getId)
-                    convTok.getMetadata.put(s"nlpcraft:limit:indexes", 
Collections.singletonList(ref.getIndex))
+                    convTok4Fix.getMetadata.put(s"nlpcraft:limit:note", 
newRef.getId)
+                    convTok4Fix.getMetadata.put(s"nlpcraft:limit:indexes", 
Collections.singletonList(newRef.getIndex))
                 }
 
             case "nlpcraft:relation" =>
-                val refId = convTok.meta[String]("nlpcraft:relation:note")
-                val refIdxs = 
convTok.meta[JList[Int]]("nlpcraft:relation:indexes").asScala.sorted
+                val refId = convTok4Fix.meta[String]("nlpcraft:relation:note")
+                val refIdxs = 
convTok4Fix.meta[JList[Int]]("nlpcraft:relation:indexes").asScala.sorted
 
-                val nonConvRefs = nonConvToks.filter(t => t.getId == refId && 
refIdxs.contains(t.getIndex))
+                val nonConvRefs = vrntNotConvToks.filter(t => t.getId == refId 
&& refIdxs.contains(t.getIndex))
 
                 if (nonConvRefs.nonEmpty && nonConvRefs.size != refIdxs.size)
                     throw new NCE(s"References are not found [id=$refId, 
indexes=${refIdxs.mkString(", ")}]")
 
                 if (nonConvRefs.isEmpty) {
-                    val refs = getForRecalc(refId, refIdxs, _.size == 
refIdxs.size)
-                    val refsIds = refs.map(_.getId).distinct
+                    val newRefs = getForRecalc(refId, refIdxs, _.size == 
refIdxs.size)
+                    val newRefsIds = newRefs.map(_.getId).distinct
 
-                    if (refsIds.size != 1)
+                    if (newRefsIds.size != 1)
                         throw new NCE(s"Valid variant references are not found 
[id=$refId]")
 
-                    convTok.getMetadata.put(s"nlpcraft:relation:note", 
refsIds.head)
-                    convTok.getMetadata.put(s"nlpcraft:relation:indexes", 
refs.map(_.getIndex).asJava)
+                    convTok4Fix.getMetadata.put(s"nlpcraft:relation:note", 
newRefsIds.head)
+                    convTok4Fix.getMetadata.put(s"nlpcraft:relation:indexes", 
newRefs.map(_.getIndex).asJava)
                 }
 
             case _ => // No-op.
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/sort/NCSortEnricher.scala
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/sort/NCSortEnricher.scala
index 965d80d..286c8b4 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/sort/NCSortEnricher.scala
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/sort/NCSortEnricher.scala
@@ -377,19 +377,21 @@ object NCSortEnricher extends NCProbeEnricher {
 
                                     case TYPE_SUBJ_BY =>
                                         require(seq1.nonEmpty)
-                                        require(seq2.nonEmpty)
                                         require(sortToks.nonEmpty)
                                         require(byToks.nonEmpty)
 
-                                        res = Some(
-                                            Match(
-                                                asc = asc,
-                                                main = sortToks,
-                                                stop = byToks ++ orderToks,
-                                                subjSeq = seq1,
-                                                bySeq = seq2
+                                        if (seq2.isEmpty)
+                                            res = None
+                                        else
+                                            res = Some(
+                                                Match(
+                                                    asc = asc,
+                                                    main = sortToks,
+                                                    stop = byToks ++ orderToks,
+                                                    subjSeq = seq1,
+                                                    bySeq = seq2
+                                                )
                                             )
-                                        )
 
                                     case TYPE_BY =>
                                         require(seq1.nonEmpty)
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/models/stm/indexes/NCLimitSpec.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/models/stm/indexes/NCLimitSpec.scala
index 8b9140f..7aeb9b9 100644
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/models/stm/indexes/NCLimitSpec.scala
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/models/stm/indexes/NCLimitSpec.scala
@@ -26,19 +26,48 @@ import java.util.{List => JList}
 import scala.jdk.CollectionConverters.ListHasAsScala
 import scala.language.implicitConversions
 
-case class NCLimitSpecModelData(note: String, indexes: Seq[Int])
+case class NCLimitSpecModelData(intentId: String, note: String, indexes: 
Seq[Int])
 
 class NCLimitSpecModel extends NCSpecModelAdapter {
-    @NCIntent("intent=limit term(limit)~{tok_id() == 'nlpcraft:limit'} 
term(elem)~{has(tok_groups(), 'G')}")
-    private def onLimit(ctx: NCIntentMatch, @NCIntentTerm("limit") limit: 
NCToken): NCResult =
+    private def mkResult(intentId: String, limit: NCToken) =
         NCResult.json(
             mapper.writeValueAsString(
                 NCLimitSpecModelData(
+                    intentId = intentId,
                     note = limit.meta[String]("nlpcraft:limit:note"),
                     indexes = 
limit.meta[JList[Int]]("nlpcraft:limit:indexes").asScala.toSeq
                 )
             )
         )
+
+    @NCIntent(
+        "intent=limit1 " +
+        "term(limit)~{tok_id() == 'nlpcraft:limit'} " +
+        "term(elem)~{has(tok_groups(), 'G1')}"
+    )
+    private def onLimit1(ctx: NCIntentMatch, @NCIntentTerm("limit") limit: 
NCToken): NCResult =
+        mkResult(intentId = "limit1", limit = limit)
+
+    // `x` is mandatory (difference with `limit3`)
+    @NCIntent(
+        "intent=limit2 " +
+        "term(x)={tok_id() == 'X'} " +
+        "term(limit)~{tok_id() == 'nlpcraft:limit'} " +
+        "term(elem)~{has(tok_groups(), 'G1')}"
+    )
+    private def onLimit2(ctx: NCIntentMatch, @NCIntentTerm("limit") limit: 
NCToken): NCResult =
+        mkResult(intentId = "limit2", limit = limit)
+
+    // `y` is optional (difference with `limit2`)
+    @NCIntent(
+        "intent=limit3 " +
+            "term(y)~{tok_id() == 'Y'} " +
+            "term(limit)~{tok_id() == 'nlpcraft:limit'} " +
+            "term(elem)~{has(tok_groups(), 'G1')}"
+    )
+    private def onLimit3(ctx: NCIntentMatch, @NCIntentTerm("limit") limit: 
NCToken): NCResult =
+        mkResult(intentId = "limit3", limit = limit)
+
 }
 
 @NCTestEnvironment(model = classOf[NCLimitSpecModel], startClient = true)
@@ -46,16 +75,50 @@ class NCLimitSpec extends NCTestContext {
     private def extract(s: String): NCLimitSpecModelData = mapper.readValue(s, 
classOf[NCLimitSpecModelData])
 
     @Test
-    private[stm] def test(): Unit = {
+    private[stm] def test1(): Unit = {
         checkResult(
             "top 23 a a",
             extract,
-            NCLimitSpecModelData(note = "A", indexes = Seq(1))
+            // Reference to variant.
+            NCLimitSpecModelData(intentId = "limit1", note = "A2", indexes = 
Seq(1))
         )
         checkResult(
             "test test b b",
             extract,
-            NCLimitSpecModelData(note = "B", indexes = Seq(2))
+            // Reference to recalculated variant (new changed indexes).
+            NCLimitSpecModelData(intentId = "limit1", note = "B2", indexes = 
Seq(2))
+        )
+    }
+
+    @Test
+    private[stm] def test2(): Unit = {
+        checkResult(
+            "x test top 23 a a",
+            extract,
+            // Reference to variant.
+            NCLimitSpecModelData(intentId = "limit2", note = "A2", indexes = 
Seq(3))
+        )
+        checkResult(
+            "test x",
+            extract,
+            // Reference to conversation (tokens by these ID and indexes can 
be found in conversation).
+            NCLimitSpecModelData(intentId = "limit2", note = "A2", indexes = 
Seq(3))
+        )
+    }
+
+    @Test
+    private[stm] def test3(): Unit = {
+        checkResult(
+            "y test top 23 a a",
+            extract,
+            // Reference to variant.
+            NCLimitSpecModelData(intentId = "limit3", note = "A2", indexes = 
Seq(3))
+        )
+        checkResult(
+            "test y",
+            extract,
+            // Reference to conversation (tokens by these ID and indexes can 
be found in conversation).
+            NCLimitSpecModelData(intentId = "limit3", note = "A2", indexes = 
Seq(3))
         )
     }
 }
\ No newline at end of file
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/models/stm/indexes/NCRelationSpec.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/models/stm/indexes/NCRelationSpec.scala
index c1d42c5..4198c77 100644
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/models/stm/indexes/NCRelationSpec.scala
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/models/stm/indexes/NCRelationSpec.scala
@@ -26,19 +26,47 @@ import java.util.{List => JList}
 import scala.jdk.CollectionConverters.ListHasAsScala
 import scala.language.implicitConversions
 
-case class NCRelationSpecModelData(note: String, indexes: Seq[Int])
+case class NCRelationSpecModelData(intentId: String, note: String, indexes: 
Seq[Int])
 
 class NCRelationSpecModel extends NCSpecModelAdapter {
-    @NCIntent("intent=rel term(rel)~{tok_id() == 'nlpcraft:relation'} 
term(elem)~{has(tok_groups(), 'G')}*")
-    private def onRelation(ctx: NCIntentMatch, @NCIntentTerm("rel") rel: 
NCToken): NCResult =
+    private def mkResult(intentId: String, rel: NCToken) =
         NCResult.json(
             mapper.writeValueAsString(
                 NCRelationSpecModelData(
+                    intentId = intentId,
                     note = rel.meta[String]("nlpcraft:relation:note"),
                     indexes = 
rel.meta[JList[Int]]("nlpcraft:relation:indexes").asScala.toSeq
                 )
             )
         )
+
+    @NCIntent(
+        "intent=rel1 " +
+        "term(rel)~{tok_id() == 'nlpcraft:relation'} " +
+        "term(elem)~{has(tok_groups(), 'G1')}*"
+    )
+    private def onRelation1(ctx: NCIntentMatch, @NCIntentTerm("rel") rel: 
NCToken): NCResult =
+        mkResult(intentId = "rel1", rel = rel)
+
+    // `x` is mandatory (difference with `rel3`)
+    @NCIntent(
+        "intent=rel2 " +
+            "term(x)={tok_id() == 'X'} " +
+            "term(rel)~{tok_id() == 'nlpcraft:relation'} " +
+            "term(elem)~{has(tok_groups(), 'G1')}*"
+    )
+    private def onRelation2(ctx: NCIntentMatch, @NCIntentTerm("rel") rel: 
NCToken): NCResult =
+        mkResult(intentId = "rel2", rel = rel)
+
+    // `y` is optional (difference with `rel2`)
+    @NCIntent(
+        "intent=rel3 " +
+        "term(y)~{tok_id() == 'Y'} " +
+        "term(rel)~{tok_id() == 'nlpcraft:relation'} " +
+        "term(elem)~{has(tok_groups(), 'G1')}*"
+    )
+    private def onRelation3(ctx: NCIntentMatch, @NCIntentTerm("rel") rel: 
NCToken): NCResult =
+        mkResult(intentId = "rel3", rel = rel)
 }
 
 @NCTestEnvironment(model = classOf[NCRelationSpecModel], startClient = true)
@@ -46,16 +74,50 @@ class NCRelationSpec extends NCTestContext {
     private def extract(s: String): NCRelationSpecModelData = 
mapper.readValue(s, classOf[NCRelationSpecModelData])
 
     @Test
-    private[stm] def test(): Unit = {
+    private[stm] def test1(): Unit = {
         checkResult(
             "compare a a and a a",
             extract,
-            NCRelationSpecModelData(note = "A", indexes = Seq(1, 3))
+            // Reference to variant.
+            NCRelationSpecModelData(intentId = "rel1", note = "A2", indexes = 
Seq(1, 3))
         )
         checkResult(
             "b b and b b",
             extract,
-            NCRelationSpecModelData(note = "B", indexes = Seq(0, 2))
+            // Reference to recalculated variant (new changed indexes).
+            NCRelationSpecModelData(intentId = "rel1", note = "B2", indexes = 
Seq(0, 2))
+        )
+    }
+
+    @Test
+    private[stm] def test2(): Unit = {
+        checkResult(
+            "x compare a a and a a",
+            extract,
+            // Reference to variant.
+            NCRelationSpecModelData(intentId = "rel2", note = "A2", indexes = 
Seq(2, 4))
+        )
+        checkResult(
+            "test x",
+            extract,
+            // Reference to conversation (tokens by these ID and indexes can 
be found in conversation).
+            NCRelationSpecModelData(intentId = "rel2", note = "A2", indexes = 
Seq(2, 4))
+        )
+    }
+
+    @Test
+    private[stm] def test3(): Unit = {
+        checkResult(
+            "y compare a a and a a",
+            extract,
+            // Reference to variant.
+            NCRelationSpecModelData(intentId = "rel3", note = "A2", indexes = 
Seq(2, 4))
+        )
+        checkResult(
+            "test y",
+            extract,
+            // Reference to conversation (tokens by these ID and indexes can 
be found in conversation).
+            NCRelationSpecModelData(intentId = "rel3", note = "A2", indexes = 
Seq(2, 4))
         )
     }
 }
\ No newline at end of file
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/models/stm/indexes/NCSortSpec.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/models/stm/indexes/NCSortSpec.scala
index ed47ade..4658df6 100644
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/models/stm/indexes/NCSortSpec.scala
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/models/stm/indexes/NCSortSpec.scala
@@ -29,17 +29,24 @@ import scala.language.implicitConversions
 object NCSortSpecModelData {
     private def nvl[T](list: JList[T]): Seq[T] = if (list == null) Seq.empty 
else list.asScala.toSeq
 
-    def apply(subjnotes: JList[String], subjindexes: JList[Int], bynotes: 
JList[String], byindexes: JList[Int]):
-        NCSortSpecModelData = new
-            NCSortSpecModelData(
-                subjnotes = nvl(subjnotes),
-                subjindexes = nvl(subjindexes),
-                bynotes = nvl(bynotes),
-                byindexes = nvl(byindexes)
-            )
+    def apply(
+        intentId: String,
+        subjnotes: JList[String],
+        subjindexes: JList[Int],
+        bynotes: JList[String],
+        byindexes: JList[Int]
+    ): NCSortSpecModelData =
+        new NCSortSpecModelData(
+            intentId = intentId,
+            subjnotes = nvl(subjnotes),
+            subjindexes = nvl(subjindexes),
+            bynotes = nvl(bynotes),
+            byindexes = nvl(byindexes)
+        )
 }
 
 case class NCSortSpecModelData(
+    intentId: String,
     subjnotes: Seq[String] = Seq.empty,
     subjindexes: Seq[Int] = Seq.empty,
     bynotes: Seq[String] = Seq.empty,
@@ -47,11 +54,11 @@ case class NCSortSpecModelData(
 )
 
 class NCSortSpecModel extends NCSpecModelAdapter {
-    @NCIntent("intent=onBySort term(sort)~{tok_id() == 'nlpcraft:sort'} 
term(elem)~{has(tok_groups(), 'G')}")
-    private def onBySort(ctx: NCIntentMatch, @NCIntentTerm("sort") sort: 
NCToken): NCResult =
+    private def mkResult(intentId: String, sort: NCToken) =
         NCResult.json(
             mapper.writeValueAsString(
                 NCSortSpecModelData(
+                    intentId = intentId,
                     subjnotes = 
sort.meta[JList[String]]("nlpcraft:sort:subjnotes"),
                     subjindexes = 
sort.meta[JList[Int]]("nlpcraft:sort:subjindexes"),
                     bynotes = 
sort.meta[JList[String]]("nlpcraft:sort:bynotes"),
@@ -59,6 +66,44 @@ class NCSortSpecModel extends NCSpecModelAdapter {
                 )
             )
         )
+
+    @NCIntent(
+        "intent=onSort1 " +
+        "term(sort)~{tok_id() == 'nlpcraft:sort'} " +
+        "term(elem)~{has(tok_groups(), 'G1')}*"
+    )
+    private def onSort1(ctx: NCIntentMatch, @NCIntentTerm("sort") sort: 
NCToken): NCResult =
+        mkResult(intentId = "onSort1", sort = sort)
+
+    // `x` is mandatory (difference with `onSort3`)
+    @NCIntent(
+        "intent=onSort2 " +
+        "term(x)={tok_id() == 'X'} " +
+        "term(sort)~{tok_id() == 'nlpcraft:sort'} " +
+        "term(elem)~{has(tok_groups(), 'G1')}*"
+    )
+    private def onSort2(ctx: NCIntentMatch, @NCIntentTerm("sort") sort: 
NCToken): NCResult =
+        mkResult(intentId = "onSort2", sort = sort)
+
+    // `y` is optional (difference with `onSort2`)
+    @NCIntent(
+        "intent=onSort3 " +
+        "term(y)~{tok_id() == 'Y'} " +
+        "term(sort)~{tok_id() == 'nlpcraft:sort'} " +
+        "term(elem)~{has(tok_groups(), 'G1')}*"
+    )
+    private def onSort3(ctx: NCIntentMatch, @NCIntentTerm("sort") sort: 
NCToken): NCResult =
+        mkResult(intentId = "onSort3", sort = sort)
+
+    @NCIntent(
+        "intent=onSort4 " +
+        "term(z)~{tok_id() == 'Z'} " +
+        "term(elem1)~{has(tok_groups(), 'G1')}+ " +
+        "term(elem2)~{has(tok_groups(), 'G2')}+ " +
+        "term(sort)~{tok_id() == 'nlpcraft:sort'}"
+    )
+    private def onSort4(ctx: NCIntentMatch, @NCIntentTerm("sort") sort: 
NCToken): NCResult =
+        mkResult(intentId = "onSort4", sort = sort)
 }
 
 @NCTestEnvironment(model = classOf[NCSortSpecModel], startClient = true)
@@ -66,27 +111,124 @@ class NCSortSpec extends NCTestContext {
     private def extract(s: String): NCSortSpecModelData = mapper.readValue(s, 
classOf[NCSortSpecModelData])
 
     @Test
-    private[stm] def test(): Unit = {
+    private[stm] def testOnSort11(): Unit = {
         checkResult(
             "test test sort by a a",
             extract,
-            NCSortSpecModelData(bynotes = Seq("A"), byindexes = Seq(3))
+            // Reference to variant.
+            NCSortSpecModelData(intentId = "onSort1", bynotes = Seq("A2"), 
byindexes = Seq(3))
         )
         checkResult(
             "test b b",
             extract,
-            NCSortSpecModelData(bynotes = Seq("B"), byindexes = Seq(1))
+            // Reference to variant.
+            NCSortSpecModelData(intentId = "onSort1", bynotes = Seq("B2"), 
byindexes = Seq(1))
         )
         checkResult(
             "test test sort a a by a a",
             extract,
-            NCSortSpecModelData(subjnotes = Seq("A"), subjindexes = Seq(3), 
bynotes = Seq("A"), byindexes = Seq(5))
+            // Reference to variant.
+            NCSortSpecModelData(
+                intentId = "onSort1",
+                subjnotes = Seq("A2"),
+                subjindexes = Seq(3),
+                bynotes = Seq("A2"),
+                byindexes = Seq(5)
+            )
+        )
+
+        checkResult(
+            "test test sort a a, a a by a a, a a",
+            extract,
+            // Reference to variant.
+            NCSortSpecModelData(
+                intentId = "onSort1",
+                subjnotes = Seq("A2", "A2"),
+                subjindexes = Seq(3, 5),
+                bynotes = Seq("A2", "A2"),
+                byindexes = Seq(7, 9)
+            )
+        )
+    }
+
+    @Test
+    private[stm] def testOnSort12(): Unit = {
+        checkResult(
+            "test test sort by a a",
+            extract,
+            // Reference to variant.
+            NCSortSpecModelData(intentId = "onSort1", bynotes = Seq("A2"), 
byindexes = Seq(3))
+        )
+
+        checkResult(
+            "test b b",
+            extract,
+            // Reference to recalculated variant (new changed indexes).
+            NCSortSpecModelData(intentId = "onSort1", bynotes = Seq("B2"), 
byindexes = Seq(1))
+        )
+    }
+
+    @Test
+    private[stm] def testOnSort2(): Unit = {
+        checkResult(
+            "test test x sort by a a",
+            extract,
+            // Reference to variant.
+            NCSortSpecModelData(intentId = "onSort2", bynotes = Seq("A2"), 
byindexes = Seq(4))
+        )
+
+        checkResult(
+            "test x",
+            extract,
+            // Reference to conversation (tokens by these ID and indexes can 
be found in conversation).
+            NCSortSpecModelData(intentId = "onSort2", bynotes = Seq("A2"), 
byindexes = Seq(4))
+        )
+    }
+
+    @Test
+    private[stm] def testOnSort3(): Unit = {
+        checkResult(
+            "test test y sort by a a",
+            extract,
+            // Reference to variant.
+            NCSortSpecModelData(intentId = "onSort3", bynotes = Seq("A2"), 
byindexes = Seq(4))
+        )
+
+        checkResult(
+            "test y",
+            extract,
+            // Reference to conversation (tokens by these ID and indexes can 
be found in conversation).
+            NCSortSpecModelData(intentId = "onSort3", bynotes = Seq("A2"), 
byindexes = Seq(4))
         )
+    }
 
-//        checkResult(
-//            "test test sort a a, a a by a a, a a",
-//            extract,
-//            NCSortSpecModelData(subjnotes = Seq("A"), subjindexes = Seq(2, 
3), bynotes = Seq("A"), byindexes = Seq(5, 6))
-//        )
+    // Like `testOnSort11` and `testOnSort12`, but more complex.
+    @Test
+    private[stm] def testOnSort4(): Unit = {
+        checkResult(
+            "test z test sort x by a a",
+            extract,
+            // Reference to variant.
+            NCSortSpecModelData(
+                intentId = "onSort4",
+                subjnotes = Seq("X"),
+                subjindexes = Seq(4),
+                bynotes = Seq("A2"),
+                byindexes = Seq(6)
+            )
+        )
+
+        checkResult(
+            "test z y b b",
+            extract,
+            // Reference to recalculated variant (new changed indexes).
+            NCSortSpecModelData(
+                intentId = "onSort4",
+                subjnotes = Seq("Y"),
+                subjindexes = Seq(2),
+                bynotes = Seq("B2"),
+                byindexes = Seq(3)
+            )
+        )
     }
 }
\ No newline at end of file
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/models/stm/indexes/NCSpecModelAdapter.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/models/stm/indexes/NCSpecModelAdapter.scala
index 7d0c5de..81e5563 100644
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/models/stm/indexes/NCSpecModelAdapter.scala
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/models/stm/indexes/NCSpecModelAdapter.scala
@@ -34,8 +34,13 @@ object NCSpecModelAdapter {
 class NCSpecModelAdapter extends NCModelAdapter("nlpcraft.stm.idxs.test", "STM 
Indexes Test Model", "1.0") {
     override def getElements: util.Set[NCElement] =
         Set(
-            mkElement("A", "G", "a a"),
-            mkElement("B", "G", "b b")
+            mkElement("A2", "G1", "a a"),
+            mkElement("B2", "G1", "b b"),
+
+            mkElement("X", "G2", "x"),
+            mkElement("Y", "G2", "y"),
+
+            mkElement("Z", "G3", "z")
         ).asJava
 
     private def mkElement(id: String, group: String, syns: String*): NCElement 
=
@@ -48,6 +53,4 @@ class NCSpecModelAdapter extends 
NCModelAdapter("nlpcraft.stm.idxs.test", "STM I
             }
             override def getGroups: util.List[String] = 
Collections.singletonList(group)
         }
-
-
 }
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/sort/NCEnricherSortSpec.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/sort/NCEnricherSortSpec.scala
index df089e3..100334f 100644
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/sort/NCEnricherSortSpec.scala
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/sort/NCEnricherSortSpec.scala
@@ -17,10 +17,10 @@
 
 package org.apache.nlpcraft.probe.mgrs.nlp.enrichers.sort
 
-import org.apache.nlpcraft.{NCTestElement, NCTestEnvironment}
 import org.apache.nlpcraft.model.NCElement
 import org.apache.nlpcraft.probe.mgrs.nlp.enrichers.NCTestSortTokenType._
 import org.apache.nlpcraft.probe.mgrs.nlp.enrichers.{NCDefaultTestModel, 
NCEnricherBaseSpec, NCTestNlpToken => nlp, NCTestSortToken => srt, 
NCTestUserToken => usr}
+import org.apache.nlpcraft.{NCTestElement, NCTestEnvironment}
 import org.junit.jupiter.api.Test
 
 import java.util
@@ -222,177 +222,6 @@ class NCEnricherSortSpec extends NCEnricherBaseSpec {
                 nlp(text = ", asc", isStop = true)
             ),
             _ => checkExists(
-                "sort A",
-                srt(text = "sort", typ = SUBJ_ONLY, note = "A", index = 1),
-                usr("A", "A")
-            ),
-            _ => checkExists(
-                "sort A by A",
-                srt(text = "sort", subjNote = "A", subjIndex = 1, byNote = 
"A", byIndex = 3),
-                usr(text = "A", id = "A"),
-                nlp(text = "by", isStop = true),
-                usr(text = "A", id = "A")
-            ),
-            _ => checkExists(
-                "sort A, C by A, C",
-                srt(text = "sort", subjNotes = Seq("A", "C"), subjIndexes = 
Seq(1, 3), byNotes = Seq("A", "C"), byIndexes = Seq(5, 7)),
-                usr(text = "A", id = "A"),
-                nlp(text = ",", isStop = true),
-                usr(text = "C", id = "C"),
-                nlp(text = "by", isStop = true),
-                usr(text = "A", id = "A"),
-                nlp(text = ",", isStop = true),
-                usr(text = "C", id = "C")
-            ),
-            _ => checkExists(
-                "sort A C by A C",
-                srt(text = "sort", subjNotes = Seq("A", "C"), subjIndexes = 
Seq(1, 2), byNotes = Seq("A", "C"), byIndexes = Seq(4, 5)),
-                usr(text = "A", id = "A"),
-                usr(text = "C", id = "C"),
-                nlp(text = "by", isStop = true),
-                usr(text = "A", id = "A"),
-                usr(text = "C", id = "C")
-            ),
-            _ => checkExists(
-                "sort A B by A B",
-                srt(text = "sort", subjNotes = Seq("A", "B"), subjIndexes = 
Seq(1, 2), byNotes = Seq("A", "B"), byIndexes = Seq(4, 5)),
-                usr(text = "A", id = "A"),
-                usr(text = "B", id = "B"),
-                nlp(text = "by", isStop = true),
-                usr(text = "A", id = "A"),
-                usr(text = "B", id = "B")
-            ),
-            _ => checkExists(
-                "sort A B by A B",
-                srt(text = "sort", subjNote = "AB", subjIndex = 1, byNote = 
"AB", byIndex = 3),
-                usr(text = "A B", id = "AB"),
-                nlp(text = "by", isStop = true),
-                usr(text = "A B", id = "AB")
-            ),
-            _ => checkExists(
-                "A classify",
-                usr(text = "A", id = "A"),
-                srt(text = "classify", typ = SUBJ_ONLY, note = "A", index = 0)
-            ),
-            _ => checkExists(
-                "the A the classify",
-                nlp(text = "the", isStop = true),
-                usr(text = "A", id = "A"),
-                nlp(text = "the", isStop = true),
-                srt(text = "classify", typ = SUBJ_ONLY, note = "A", index = 1)
-            ),
-            _ => checkExists(
-                "segment A by top down",
-                srt(text = "segment", typ = SUBJ_ONLY, note = "A", index = 1, 
asc = false),
-                usr(text = "A", id = "A"),
-                nlp(text = "by top down", isStop = true)
-            ),
-            _ => checkExists(
-                "segment A in bottom up order",
-                srt(text = "segment", typ = SUBJ_ONLY, note = "A", index = 1, 
asc = true),
-                usr(text = "A", id = "A"),
-                nlp(text = "in bottom up order", isStop = true)
-            ),
-            // `by` is redundant word here
-            _ => checkExists(
-                "segment A by in bottom up order",
-                srt(text = "segment", typ = SUBJ_ONLY, note = "A", index = 1),
-                usr(text = "A", id = "A"),
-                nlp(text = "by"),
-                nlp(text = "in"),
-                nlp(text = "bottom"),
-                nlp(text = "up"),
-                nlp(text = "order")
-            ),
-            _ => checkExists(
-                "the segment the A the in bottom up the order the",
-                nlp(text = "the", isStop = true),
-                srt(text = "segment", typ = SUBJ_ONLY, note = "A", index = 3, 
asc = true),
-                nlp(text = "the", isStop = true),
-                usr(text = "A", id = "A"),
-                nlp(text = "the in bottom up the order the", isStop = true)
-            ),
-            _ => checkExists(
-                "the segment the A the by bottom up the order the",
-                nlp(text = "the", isStop = true),
-                srt(text = "segment", typ = SUBJ_ONLY, note = "A", index = 3, 
asc = true),
-                nlp(text = "the", isStop = true),
-                usr(text = "A", id = "A"),
-                nlp(text = "the by bottom up the order the", isStop = true)
-            ),
-            _ => checkExists(
-                "A classify",
-                usr(text = "A", id = "A"),
-                srt(text = "classify", typ = SUBJ_ONLY, note = "A", index = 0)
-            ),
-            _ => checkAll(
-                "A B classify",
-                Seq(
-                    usr(text = "A B", id = "AB"),
-                    srt(text = "classify", typ = SUBJ_ONLY, note = "AB", index 
= 0)
-                ),
-                Seq(
-                    usr(text = "A", id = "A"),
-                    usr(text = "B", id = "B"),
-                    srt(text = "classify", subjNotes = Seq("A", "B"), 
subjIndexes = Seq(0, 1))
-                ),
-                Seq(
-                    usr(text = "A", id = "A"),
-                    usr(text = "B", id = "B"),
-                    srt(text = "classify", subjNotes = Seq("B"), subjIndexes = 
Seq(1))
-                )
-            ),
-            _ => checkAll(
-                "D classify",
-                Seq(
-                    usr(text = "D", id = "D1"),
-                    srt(text = "classify", typ = SUBJ_ONLY, note = "D1", index 
= 0)
-                ),
-                Seq(
-                    usr(text = "D", id = "D2"),
-                    srt(text = "classify", typ = SUBJ_ONLY, note = "D2", index 
= 0)
-                )
-            ),
-            _ => checkAll(
-                "sort by A",
-                Seq(
-                    srt(text = "sort by", typ = BY_ONLY, note = "A", index = 
1),
-                    usr(text = "A", id = "A")
-                )
-            ),
-            _ => checkExists(
-                "organize by A, B top down",
-                srt(text = "organize by", byNotes = Seq("A", "B"), byIndexes = 
Seq(1, 3), asc = Some(false)),
-                usr(text = "A", id = "A"),
-                nlp(text = ",", isStop = true),
-                usr(text = "B", id = "B"),
-                nlp(text = "top down", isStop = true)
-            ),
-            _ => checkExists(
-                "organize by A, B from bottom up order",
-                srt(text = "organize by", byNotes = Seq("A", "B"), byIndexes = 
Seq(1, 3), asc = Some(true)),
-                usr(text = "A", id = "A"),
-                nlp(text = ",", isStop = true),
-                usr(text = "B", id = "B"),
-                nlp(text = "from bottom up order", isStop = true)
-            ),
-            _ => checkExists(
-                "organize by A, B the descending",
-                srt(text = "organize by", byNotes = Seq("A", "B"), byIndexes = 
Seq(1, 3), asc = Some(false)),
-                usr(text = "A", id = "A"),
-                nlp(text = ",", isStop = true),
-                usr(text = "B", id = "B"),
-                nlp(text = "the descending", isStop = true)
-            ),
-            _ => checkExists(
-                "organize by A, B, asc",
-                srt(text = "organize by", byNotes = Seq("A", "B"), byIndexes = 
Seq(1, 3), asc = Some(true)),
-                usr(text = "A", id = "A"),
-                nlp(text = ",", isStop = true),
-                usr(text = "B", id = "B"),
-                nlp(text = ", asc", isStop = true)
-            ),
-            _ => checkExists(
                 "sort A the A the A",
                 srt(text = "sort", typ = SUBJ_ONLY, note = "wrapperA", index = 
1),
                 usr("A A A", "wrapperA"),

Reply via email to