You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nlpcraft.apache.org by se...@apache.org on 2021/09/09 19:13:27 UTC

[incubator-nlpcraft] branch master updated: NCElement 'greedy' parameter added. Date model extended.

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

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


The following commit(s) were added to refs/heads/master by this push:
     new e6dfeba  NCElement 'greedy' parameter added. Date model extended.
e6dfeba is described below

commit e6dfeba029d88fb3e8b1cd26b65de48f867a4069
Author: Sergey Kamov <sk...@gmail.com>
AuthorDate: Thu Sep 9 22:12:53 2021 +0300

    NCElement 'greedy' parameter added.
    Date model extended.
---
 .../nlpcraft/common/nlp/numeric/NCNumeric.scala    |  13 ++-
 .../common/nlp/numeric/NCNumericManager.scala      |  26 +++--
 .../scala/org/apache/nlpcraft/model/NCElement.java |   5 +
 .../apache/nlpcraft/model/NCModelFileAdapter.java  |  10 ++
 .../org/apache/nlpcraft/model/NCModelView.java     |   9 ++
 .../nlpcraft/model/impl/json/NCElementJson.java    |   8 ++
 .../nlpcraft/model/impl/json/NCModelJson.java      |   7 ++
 .../nlpcraft/probe/mgrs/cmd/NCCommandManager.scala |   6 ++
 .../mgrs/nlp/enrichers/model/NCModelEnricher.scala |  42 ++++----
 .../probe/mgrs/sentence/NCSentenceManager.scala    |  27 +++--
 .../nlp/enrichers/numeric/NCNumericEnricher.scala  | 115 ++++++++++++---------
 .../scala/org/apache/nlpcraft/NCTestElement.scala  |  17 ++-
 .../mgrs/nlp/enrichers/NCEnrichersTestBeans.scala  |  18 +++-
 .../model/NCEnricherNestedModelSpec2.scala         |   3 +-
 .../model/NCEnricherNestedModelSpec3.scala         |  12 +--
 ...ec41.scala => NCEnricherNestedModelSpec4.scala} |   4 +-
 .../model/NCEnricherNestedModelSpec5.scala         |   6 +-
 ...pec5.scala => NCEnricherNestedModelSpec6.scala} |  39 +++----
 .../enrichers/model/anyword/NCNestedAnySpec.scala  | 102 ++++++++++++++++++
 .../anyword/NCNestedTestModelsAnyAlphaNum.scala    |  35 +++++++
 .../anyword/NCNestedTestModelsAnyNotSpace.scala    |  35 +++++++
 .../model/anyword/NCNestedTestModelsAnyRegex.scala |  35 +++++++
 .../adapters/NCNestedModelAnyAdapter.scala}        |  37 +++----
 .../anyword/adapters/NCNestedTestModelAny1.scala   |  51 +++++++++
 .../anyword/adapters/NCNestedTestModelAny2.scala   |  52 ++++++++++
 .../anyword/adapters/NCNestedTestModelAny3.scala   |  49 +++++++++
 .../anyword/adapters/NCNestedTestModelAny4.scala   |  55 ++++++++++
 .../anyword/adapters/NCNestedTestModelAny5.scala   |  55 ++++++++++
 .../anyword/adapters/NCNestedTestModelAny6.scala   |  55 ++++++++++
 .../anyword/adapters/NCNestedTestModelAny7.scala   |  55 ++++++++++
 .../enrichers/numeric/NCEnricherNumericSpec.scala  |  75 ++++++++++++++
 31 files changed, 917 insertions(+), 141 deletions(-)

diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/nlp/numeric/NCNumeric.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/nlp/numeric/NCNumeric.scala
index 75a3365..1de9d4b 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/nlp/numeric/NCNumeric.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/nlp/numeric/NCNumeric.scala
@@ -28,14 +28,21 @@ case class NCNumericUnit(name: String, unitType: String)
 
 /**
   *
+  * @param unit
+  * @param tokens
+  */
+case class NCNumericUnitData(unit: NCNumericUnit, tokens: Seq[NCNlpSentenceToken])
+
+/**
+  *
   * @param tokens
   * @param value
   * @param isFractional
-  * @param unit
+  * @param unitData
   */
 case class NCNumeric(
     tokens: Seq[NCNlpSentenceToken],
     value: Double,
     isFractional: Boolean,
-    unit: Option[NCNumericUnit]
-)
+    unitData: Option[NCNumericUnitData]
+)
\ No newline at end of file
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/nlp/numeric/NCNumericManager.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/nlp/numeric/NCNumericManager.scala
index a428ab9..3d767dc 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/nlp/numeric/NCNumericManager.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/nlp/numeric/NCNumericManager.scala
@@ -85,14 +85,23 @@ object NCNumericManager extends NCService {
       * @return
       */
     private def mkSolidNumUnit(t: NCNlpSentenceToken): Option[NCNumeric] = {
-        val s = t.origText
+        val s = t.normText
 
         val num = s.takeWhile(_.isDigit)
         val after = s.drop(num.length)
 
         if (num.nonEmpty && after.nonEmpty) {
-            def mkNumeric(u: NCNumericUnit): Option[NCNumeric] =
-                Some(NCNumeric(Seq(t), java.lang.Double.valueOf(num), isFractional = isFractional(num), unit = Some(u)))
+            def mkNumeric(u: NCNumericUnit): Option[NCNumeric] = {
+                val toks = Seq(t)
+
+                Some(
+                    NCNumeric(
+                        toks,
+                        java.lang.Double.valueOf(num),
+                        isFractional = isFractional(num),
+                        unitData = Some(NCNumericUnitData(u, toks)))
+                )
+            }
 
             unitsOrigs.get(after) match {
                 case Some(u) => mkNumeric(u)
@@ -123,12 +132,14 @@ object NCNumericManager extends NCService {
                 senWords.indexOfSlice(dtWords) match {
                     case -1 => None
                     case idx =>
+                        val toks = senToks.slice(idx, idx + dtWords.length)
+
                         Some(
                             NCNumeric(
-                                tokens = senToks.slice(idx, idx + dtWords.length),
+                                tokens = toks,
                                 value = dtPeriod.value,
                                 isFractional = false,
-                                unit = Some(dtPeriod.unit)
+                                unitData = Some(NCNumericUnitData(dtPeriod.unit, toks))
                             )
                         )
                 }
@@ -404,7 +415,10 @@ object NCNumericManager extends NCService {
                             None
                     }).headOption match {
                         case Some((unit, unitToks)) =>
-                            val numWithUnit = NCNumeric(seq ++ unitToks, v, isFractional = isFractional, Some(unit))
+                            val numWithUnit =
+                                NCNumeric(
+                                    seq ++ unitToks, v, isFractional = isFractional, Some(NCNumericUnitData(unit, unitToks))
+                                )
 
                             // If unit name is same as user element name,
                             // it returns both variants: numeric with unit and without.
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCElement.java b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCElement.java
index 9f5872a..b5b6cbd 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCElement.java
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCElement.java
@@ -382,4 +382,9 @@ public interface NCElement extends NCMetadata, Serializable {
     default Optional<Boolean> isSparse() {
         return Optional.empty();
     }
+
+    // TODO:
+    default Optional<Boolean> isGreedy() {
+        return Optional.empty();
+    }
 }
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCModelFileAdapter.java b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCModelFileAdapter.java
index c313bf7..efa2b68 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCModelFileAdapter.java
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCModelFileAdapter.java
@@ -357,6 +357,11 @@ abstract public class NCModelFileAdapter extends NCModelAdapter {
                             return nvl(js.isSparse(), proxy.isSparse());
                         }
 
+                        @Override
+                        public Optional<Boolean> isGreedy() {
+                            return nvl(js.isGreedy(), proxy.isGreedy());
+                        }
+
                         private<T> Optional<T> nvl(T t, T dflt) {
                             return Optional.of(t != null ? t : dflt);
                         }
@@ -484,6 +489,11 @@ abstract public class NCModelFileAdapter extends NCModelAdapter {
     }
 
     @Override
+    public boolean isGreedy() {
+        return proxy.isGreedy();
+    }
+
+    @Override
     public Map<String, Object> getMetadata() {
         return metadata;
     }
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCModelView.java b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCModelView.java
index c213098..30a2b40 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCModelView.java
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/NCModelView.java
@@ -169,6 +169,11 @@ public interface NCModelView extends NCMetadata {
     boolean DFLT_IS_SPARSE = false;
 
     /**
+     * Default value for {@link #isGreedy()} method.
+     */
+    boolean DFLT_IS_GREEDY = true;
+
+    /**
      * Default value for {@link #getMaxElementSynonyms()} method.
      */
     int DFLT_MAX_ELEMENT_SYNONYMS = 1000;
@@ -818,6 +823,10 @@ public interface NCModelView extends NCMetadata {
         return DFLT_IS_SPARSE;
     }
 
+    default boolean isGreedy() {
+        return DFLT_IS_GREEDY;
+    }
+
     /**
      * Gets optional user defined model metadata that can be set by the developer and accessed later.
      * By default, it returns an empty map. Note that this metadata is mutable and can be
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/impl/json/NCElementJson.java b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/impl/json/NCElementJson.java
index addca45..be7b995 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/impl/json/NCElementJson.java
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/impl/json/NCElementJson.java
@@ -36,6 +36,8 @@ public class NCElementJson {
     private Boolean isPermutateSynonyms;
     // Can be null.
     private Boolean isSparse;
+    // Can be null.
+    private Boolean isGreedy;
 
     public String getParentId() {
         return parentId;
@@ -97,4 +99,10 @@ public class NCElementJson {
     public void setSparse(Boolean sparse) {
         isSparse = sparse;
     }
+    public Boolean isGreedy() {
+        return isGreedy;
+    }
+    public void setGreedy(Boolean greedy) {
+        isGreedy = greedy;
+    }
 }
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/impl/json/NCModelJson.java b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/impl/json/NCModelJson.java
index d2459d3..f332e08 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/impl/json/NCModelJson.java
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/impl/json/NCModelJson.java
@@ -58,6 +58,7 @@ public class NCModelJson {
     private int maxTotalSynonyms = DFLT_MAX_TOTAL_SYNONYMS;
     private boolean isPermutateSynonyms = DFLT_IS_PERMUTATE_SYNONYMS;
     private boolean isSparse = DFLT_IS_SPARSE;
+    private boolean isGreedy = DFLT_IS_GREEDY;
     private int maxElementSynonyms = DFLT_MAX_TOTAL_SYNONYMS;
     private boolean maxSynonymsThresholdError = DFLT_MAX_SYNONYMS_THRESHOLD_ERROR;
     private long conversationTimeout = DFLT_CONV_TIMEOUT_MS;
@@ -202,6 +203,12 @@ public class NCModelJson {
     public boolean isSparse() {
         return isSparse;
     }
+    public boolean isGreedy() {
+        return isGreedy;
+    }
+    public void setGreedy(boolean greedy) {
+        isGreedy = greedy;
+    }
     public void setSparse(boolean sparse) {
         isSparse = sparse;
     }
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/cmd/NCCommandManager.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/cmd/NCCommandManager.scala
index 1d923ce..431097c 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/cmd/NCCommandManager.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/cmd/NCCommandManager.scala
@@ -242,6 +242,7 @@ object NCCommandManager extends NCService {
                                         override def getMaxTotalSynonyms: Int = mdl.getMaxTotalSynonyms
                                         override def isNoUserTokensAllowed: Boolean = mdl.isNoUserTokensAllowed
                                         override def isSparse: Boolean = mdl.isSparse
+                                        override def isGreedy: Boolean = mdl.isGreedy
                                         override def getMetadata: util.Map[String, AnyRef] = mdl.getMetadata
                                         override def getAdditionalStopWords: util.Set[String] = mdl.getAdditionalStopWords
                                         override def getExcludedStopWords: util.Set[String] = mdl.getExcludedStopWords
@@ -269,6 +270,9 @@ object NCCommandManager extends NCService {
 
                                                 // New method instead of `isSparse`
                                                 def getSparse: lang.Boolean
+
+                                                // New method instead of `isGreedy`
+                                                def getGreedy: lang.Boolean
                                             }
 
                                             val elm: NCElement =
@@ -288,10 +292,12 @@ object NCCommandManager extends NCService {
                                                     // Hidden.
                                                     override def isPermutateSynonyms: Optional[lang.Boolean] = null
                                                     override def isSparse: Optional[lang.Boolean] = null
+                                                    override def isGreedy: Optional[lang.Boolean] = null
 
                                                     // Wrapped.
                                                     override def getPermutateSynonyms: lang.Boolean = e.isPermutateSynonyms.orElse(null)
                                                     override def getSparse: lang.Boolean = e.isSparse.orElse(null)
+                                                    override def getGreedy: lang.Boolean = e.isGreedy.orElse(null)
                                                 }
                                             elm
                                         }).asJava
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/NCModelEnricher.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/NCModelEnricher.scala
index 1f81711..6e6f7d1 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/NCModelEnricher.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/NCModelEnricher.scala
@@ -151,7 +151,7 @@ object NCModelEnricher extends NCProbeEnricher {
     /**
       *
       * @param ns
-      * @param elem
+      * @param elemId
       * @param toks
       * @param direct
       * @param syn
@@ -160,7 +160,7 @@ object NCModelEnricher extends NCProbeEnricher {
       */
     private def mark(
         ns: Sentence,
-        elem: NCElement,
+        elemId: String,
         toks: Seq[NlpToken],
         direct: Boolean,
         syn: Option[Synonym] = None,
@@ -189,7 +189,7 @@ object NCModelEnricher extends NCProbeEnricher {
 
         val idxs = toks.map(_.index).sorted
 
-        val note = NlpNote(idxs, elem.getId, params: _*)
+        val note = NlpNote(idxs, elemId, params: _*)
 
         toks.foreach(_.add(note))
 
@@ -269,7 +269,7 @@ object NCModelEnricher extends NCProbeEnricher {
                         if (!alreadyMarked(ns, elmId, matchedToks, matchedToks.map(_.index).sorted))
                             mark(
                                 ns,
-                                elem = mdl.elements.getOrElse(elmId, throw new NCE(s"Custom model parser returned unknown element: $elmId")),
+                                elemId = elmId,
                                 toks = matchedToks,
                                 direct = true,
                                 metaOpt = Some(e.getMetadata.asScala.toMap)
@@ -410,27 +410,30 @@ object NCModelEnricher extends NCProbeEnricher {
         dbgType: String,
         ns: Sentence,
         contCache: Cache,
-        elem: NCElement,
+        elemId: String,
+        greedy: Boolean,
         elemToks: Seq[NlpToken],
         sliceToksIdxs: Seq[Int],
         syn: Synonym,
-        parts: Seq[TokType] = Seq.empty)
-    : Unit = {
+        parts: Seq[TokType] = Seq.empty
+    ): Unit = {
         val resIdxs = elemToks.map(_.index)
         val resIdxsSorted = resIdxs.sorted
 
         if (resIdxsSorted == sliceToksIdxs && U.isContinuous(resIdxsSorted))
-            contCache(elem.getId) += sliceToksIdxs
+            contCache(elemId) += sliceToksIdxs
 
-        val ok = !alreadyMarked(ns, elem.getId, elemToks, sliceToksIdxs)
+        val ok =
+            (!greedy || !alreadyMarked(ns, elemId, elemToks, sliceToksIdxs)) &&
+            ( parts.isEmpty || !parts.exists { case (t, _) => t.getId == elemId })
 
         if (ok)
-            mark(ns, elem, elemToks, direct = syn.isDirect && U.isIncreased(resIdxs), syn = Some(syn), parts = parts)
+            mark(ns, elemId, elemToks, direct = syn.isDirect && U.isIncreased(resIdxs), syn = Some(syn), parts = parts)
 
         if (DEEP_DEBUG)
             logger.trace(
                 s"${if (ok) "Added" else "Skipped"} element [" +
-                    s"id=${elem.getId}, " +
+                    s"id=$elemId, " +
                     s"type=$dbgType, " +
                     s"text='${elemToks.map(_.origText).mkString(" ")}', " +
                     s"indexes=${resIdxs.mkString("[", ",", "]")}, " +
@@ -466,10 +469,11 @@ object NCModelEnricher extends NCProbeEnricher {
                         toks <- combToks;
                         idxs = toks.map(_.index);
                         e <- mdl.elements.values;
-                        eId = e.getId
+                        eId = e.getId;
+                        greedy = e.isGreedy.orElse(mdl.model.isGreedy)
                         if
-                            !contCache(eId).exists(_.containsSlice(idxs)) &&
-                            !alreadyMarked(ns, eId, toks, idxs)
+                            !greedy ||
+                            !contCache(eId).exists(_.containsSlice(idxs))  && !alreadyMarked(ns, eId, toks, idxs)
                     ) {
                         // 1. SIMPLE.
                         if (simpleEnabled && (if (idlEnabled) mdl.hasIdlSynonyms(eId) else !mdl.hasIdlSynonyms(eId))) {
@@ -485,7 +489,7 @@ object NCModelEnricher extends NCProbeEnricher {
                                             syns.get(tokStems) match {
                                                 case Some(s) =>
                                                     found = true
-                                                    add("simple continuous", ns, contCache, e, toks, idxs, s)
+                                                    add("simple continuous", ns, contCache, eId, greedy, toks, idxs, s)
                                                 case None => notFound()
                                             }
 
@@ -493,7 +497,7 @@ object NCModelEnricher extends NCProbeEnricher {
                                             for (s <- syns if !found)
                                                 if (s.isMatch(toks)) {
                                                     found = true
-                                                    add("simple continuous scan", ns, contCache, e, toks, idxs, s)
+                                                    add("simple continuous scan", ns, contCache, eId, greedy, toks, idxs, s)
                                                 }
 
                                         tryMap(
@@ -512,7 +516,7 @@ object NCModelEnricher extends NCProbeEnricher {
                             if (!found && mdl.hasSparseSynonyms)
                                 for (s <- get(mdl.sparseSynonyms, eId))
                                     s.sparseMatch(toks) match {
-                                        case Some(res) => add("simple sparse", ns, contCache, e, res, idxs, s)
+                                        case Some(res) => add("simple sparse", ns, contCache, eId, greedy, res, idxs, s)
                                         case None => // No-op.
                                     }
                         }
@@ -534,7 +538,7 @@ object NCModelEnricher extends NCProbeEnricher {
                                     data = comb.map(_.data)
                                 )
                                     if (s.isMatch(data, req)) {
-                                        add("IDL continuous", ns, contCache, e, toks, idxs, s, toParts(data, s))
+                                        add("IDL continuous", ns, contCache, eId, greedy, toks, idxs, s, toParts(data, s))
 
                                         idlCache += comb
 
@@ -551,7 +555,7 @@ object NCModelEnricher extends NCProbeEnricher {
                                         case Some(res) =>
                                             val typ = if (s.sparse) "IDL sparse" else "IDL continuous"
 
-                                            add(typ, ns, contCache, e, toTokens(res, ns), idxs, s, toParts(res, s))
+                                            add(typ, ns, contCache, eId, greedy, toTokens(res, ns), idxs, s, toParts(res, s))
 
                                             idlCache += comb
                                         case None => // No-op.
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/sentence/NCSentenceManager.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/sentence/NCSentenceManager.scala
index 74ead87..d5dfc1e 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/sentence/NCSentenceManager.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/sentence/NCSentenceManager.scala
@@ -30,7 +30,7 @@ import java.util
 import java.util.{List => JList}
 import scala.collection.mutable
 import scala.collection.parallel.CollectionConverters._
-import scala.jdk.CollectionConverters.{ListHasAsScala, SeqHasAsJava, SetHasAsJava}
+import scala.jdk.CollectionConverters.{ListHasAsScala, SeqHasAsJava, SetHasAsJava, SetHasAsScala}
 import scala.language.implicitConversions
 
 /**
@@ -656,8 +656,8 @@ object NCSentenceManager extends NCService {
                         delCombs.filter(_ != note).flatMap(n => if (getPartKeys(n).contains(key)) Some(n) else None)
 
                     if (
-                        delCombOthers.exists(o => noteWordsIdxs == o.wordIndexes.toSet) ||
-                        delCombOthers.nonEmpty && !delCombOthers.exists(o => noteWordsIdxs.subsetOf(o.wordIndexes.toSet))
+                        delCombOthers.nonEmpty &&
+                        !delCombOthers.exists(o => noteWordsIdxs.subsetOf(o.wordIndexes.toSet))
                     )
                         Some(note)
                     else
@@ -732,18 +732,25 @@ object NCSentenceManager extends NCService {
             )
         )
 
+
         def notNlpNotes(s: NCNlpSentence): Seq[NCNlpSentenceNote] = s.flatten.filter(!_.isNlp)
 
         // Drops similar sentences (with same notes structure). Keeps with more found.
+        val notGreedyElems =
+            mdl.getElements.asScala.flatMap(e => if (!e.isGreedy.orElse(mdl.isGreedy)) Some(e.getId) else None).toSet
+
         sens = sens.groupBy(notNlpNotes(_).groupBy(_.noteType).keys.toSeq.sorted.distinct).
-            flatMap(p => {
-                val m: Map[NCNlpSentence, Int] = p._2.map(p => p -> notNlpNotes(p).size).toMap
+            flatMap { case (types, sensSeq) =>
+                if (types.exists(notGreedyElems.contains))
+                    sensSeq
+                else {
+                    val m: Map[NCNlpSentence, Int] = sensSeq.map(p => p -> notNlpNotes(p).size).toMap
 
-                val max = m.values.max
+                    val max = m.values.max
 
-                m.filter(_._2 == max).keys
-            }).
-            toSeq
+                    m.filter(_._2 == max).keys
+                }
+            }.toSeq
 
         sens =
             sens.filter(s => {
@@ -791,4 +798,4 @@ object NCSentenceManager extends NCService {
       * @param srvReqId
       */
     def clearCache(srvReqId: String): Unit = combCache -= srvReqId
-}
+}
\ No newline at end of file
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/nlp/enrichers/numeric/NCNumericEnricher.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/nlp/enrichers/numeric/NCNumericEnricher.scala
index b28f198..670a4dc 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/nlp/enrichers/numeric/NCNumericEnricher.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/nlp/enrichers/numeric/NCNumericEnricher.scala
@@ -198,7 +198,7 @@ object NCNumericEnricher extends NCServerEnricher {
     private def toString(seq: Seq[NCNlpSentenceToken], sep: String = " ", stem: Boolean = false) =
         seq.map(t => if (stem) t.stem else t.normText).mkString(sep)
 
-    private def mkNote(
+    private def mkNotes(
         toks: Seq[NCNlpSentenceToken],
         from: Double,
         fromIncl: Boolean,
@@ -206,9 +206,10 @@ object NCNumericEnricher extends NCServerEnricher {
         to: Double,
         toIncl: Boolean,
         toFractional: Boolean,
-        unitOpt: Option[NCNumericUnit]
-    ): NCNlpSentenceNote = {
-        val params = mutable.ArrayBuffer.empty[(String, Any)] ++
+        unitDataOpt: Option[NCNumericUnitData],
+    ): Seq[NCNlpSentenceNote] = {
+        val params =
+            mutable.ArrayBuffer.empty[(String, Any)] ++
             Seq(
                 "from" -> from,
                 "fromIncl" -> fromIncl,
@@ -222,14 +223,36 @@ object NCNumericEnricher extends NCServerEnricher {
                 "isToPositiveInfinity" -> (to == MAX_VALUE)
             )
 
-        unitOpt match {
-            case Some(unit) =>
-                params += "unit" -> unit.name
-                params += "unitType" -> unit.unitType
-            case None => // No-op.
+        def mkAndAssign(toks: Seq[NCNlpSentenceToken], typ: String, params: (String, Any)*):NCNlpSentenceNote = {
+            val note = NCNlpSentenceNote(toks.map(_.index), "nlpcraft:num", params:_*)
+
+            toks.foreach(_.add(note))
+
+            note
+        }
+
+        unitDataOpt match {
+            case Some(unitData) =>
+                def extend(): Seq[(String, Any)] = {
+                    params += "unit" -> unitData.unit.name
+                    params += "unitType" -> unitData.unit.unitType
+
+                    params
+                }
+
+                if (unitData.tokens == toks)
+                    Seq(mkAndAssign(toks, "nlpcraft:num", extend():_*))
+                else {
+                    Seq(
+                        mkAndAssign(
+                            toks.filter(t => !unitData.tokens.contains(t)), "nlpcraft:num", params.clone():_*
+                        ),
+                        mkAndAssign(toks, "nlpcraft:num", extend():_*)
+                    )
+                }
+
+            case None => Seq(mkAndAssign(toks, "nlpcraft:num", params:_*))
         }
-    
-        NCNlpSentenceNote(toks.map(_.index), "nlpcraft:num", params:_*)
     }
 
     /**
@@ -274,25 +297,28 @@ object NCNumericEnricher extends NCServerEnricher {
     
                     val prepToks = Seq(getBefore(ts1)) ++ ts1 ++ Seq(getBefore(ts2)) ++ ts2
     
-                    val badRange = num1.unit.isDefined && num2.unit.isDefined && num1.unit != num2.unit
+                    val badRange =
+                        num1.unitData.isDefined &&
+                        num2.unitData.isDefined &&
+                        num1.unitData.get.unit != num2.unitData.get.unit
     
                     if (!badRange) {
                         val unit =
-                            if (num1.unit.isDefined && num2.unit.isEmpty)
-                                num1.unit
-                            else if (num1.unit.isEmpty && num2.unit.isDefined)
-                                num2.unit
-                            else if (num1.unit.isEmpty && num2.unit.isEmpty)
+                            if (num1.unitData.isDefined && num2.unitData.isEmpty)
+                                num1.unitData
+                            else if (num1.unitData.isEmpty && num2.unitData.isDefined)
+                                num2.unitData
+                            else if (num1.unitData.isEmpty && num2.unitData.isEmpty)
                                 None
-                            else{
-                                require(num1.unit == num2.unit)
-    
-                                num1.unit
+                            else {
+                                require(num1.unitData.get.unit == num2.unitData.get.unit)
+
+                                Some(NCNumericUnitData(num1.unitData.get.unit, num1.tokens ++ num2.tokens))
                             }
     
-                        val note = p._2 match {
+                        val notes = p._2 match {
                             case BETWEEN_EXCLUSIVE =>
-                                mkNote(
+                                mkNotes(
                                     prepToks,
                                     d1,
                                     fromIncl = false,
@@ -303,7 +329,7 @@ object NCNumericEnricher extends NCServerEnricher {
                                     unit
                                 )
                             case BETWEEN_INCLUSIVE =>
-                                mkNote(
+                                mkNotes(
                                     prepToks,
                                     d1,
                                     fromIncl = true,
@@ -315,9 +341,7 @@ object NCNumericEnricher extends NCServerEnricher {
                                 )
                             case _ => throw new AssertionError(s"Illegal note type: ${p._2}.")
                         }
-    
-                        prepToks.foreach(_.add(note))
-    
+
                         processed ++= ts1
                         processed ++= ts2
                     }
@@ -340,10 +364,10 @@ object NCNumericEnricher extends NCServerEnricher {
     
                             processed ++= toks
     
-                            val note =
+                            val notes =
                                 prep.prepositionType match {
                                     case MORE =>
-                                        mkNote(
+                                        mkNotes(
                                             toks,
                                             num.value,
                                             fromIncl = false,
@@ -351,10 +375,10 @@ object NCNumericEnricher extends NCServerEnricher {
                                             to = MAX_VALUE,
                                             toIncl = true,
                                             toFractional = num.isFractional,
-                                            num.unit
+                                            num.unitData
                                         )
                                     case MORE_OR_EQUAL =>
-                                        mkNote(
+                                        mkNotes(
                                             toks,
                                             num.value,
                                             fromIncl = true,
@@ -362,10 +386,10 @@ object NCNumericEnricher extends NCServerEnricher {
                                             to = MAX_VALUE,
                                             toIncl = true,
                                             toFractional = num.isFractional,
-                                            num.unit
+                                            num.unitData
                                         )
                                     case LESS =>
-                                        mkNote(
+                                        mkNotes(
                                             toks,
                                             MIN_VALUE,
                                             fromIncl = true,
@@ -373,10 +397,10 @@ object NCNumericEnricher extends NCServerEnricher {
                                             to = num.value,
                                             toIncl = false,
                                             toFractional = num.isFractional,
-                                            num.unit
+                                            num.unitData
                                         )
                                     case LESS_OR_EQUAL =>
-                                        mkNote(
+                                        mkNotes(
                                             toks,
                                             MIN_VALUE,
                                             fromIncl = true,
@@ -384,10 +408,10 @@ object NCNumericEnricher extends NCServerEnricher {
                                             to = num.value,
                                             toIncl = true,
                                             toFractional = num.isFractional,
-                                            num.unit
+                                            num.unitData
                                         )
                                     case EQUAL =>
-                                        mkNote(
+                                        mkNotes(
                                             toks,
                                             num.value,
                                             fromIncl = true,
@@ -395,10 +419,10 @@ object NCNumericEnricher extends NCServerEnricher {
                                             to = num.value,
                                             toIncl = true,
                                             toFractional = num.isFractional,
-                                            num.unit
+                                            num.unitData
                                         )
                                     case NOT_EQUAL =>
-                                        mkNote(
+                                        mkNotes(
                                             toks,
                                             num.value,
                                             fromIncl = false,
@@ -406,12 +430,13 @@ object NCNumericEnricher extends NCServerEnricher {
                                             to = num.value,
                                             toIncl = false,
                                             toFractional = num.isFractional,
-                                            num.unit
+                                            num.unitData
                                         )
                                     case _ => throw new AssertionError(s"Illegal note type: ${prep.prepositionType}.")
                                 }
-    
-                            toks.foreach(_.add(note))
+
+                            for (note <- notes)
+                                toks.foreach(_.add(note))
                         }
                 }
     
@@ -423,7 +448,7 @@ object NCNumericEnricher extends NCServerEnricher {
     
             // Numeric without conditions.
             for (num <- nums if !processed.exists(num.tokens.contains)) {
-                val note = mkNote(
+                val notes = mkNotes(
                     num.tokens,
                     num.value,
                     fromIncl = true,
@@ -431,12 +456,10 @@ object NCNumericEnricher extends NCServerEnricher {
                     num.value,
                     toIncl = true,
                     num.isFractional,
-                    num.unit
+                    num.unitData
                 )
     
                 processed ++= num.tokens
-    
-                num.tokens.foreach(_.add(note))
             }
         }
     }
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/NCTestElement.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/NCTestElement.scala
index 3598d2e..daf1ab0 100644
--- a/nlpcraft/src/test/scala/org/apache/nlpcraft/NCTestElement.scala
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/NCTestElement.scala
@@ -19,7 +19,8 @@ package org.apache.nlpcraft
 
 import org.apache.nlpcraft.model.{NCElement, NCValue}
 
-import java.util
+import java.{lang, util}
+import java.util.Optional
 import scala.jdk.CollectionConverters.{SeqHasAsJava, SetHasAsJava}
 import scala.language.implicitConversions
 
@@ -29,9 +30,23 @@ import scala.language.implicitConversions
 case class NCTestElement(id: String, syns: String*) extends NCElement {
     private val values = new util.ArrayList[NCValue]
 
+    var metadata: util.Map[String, AnyRef] = super.getMetadata
+    var description: String = super.getDescription
+    var parentId: String = super.getParentId
+    var permutateSynonyms: Optional[lang.Boolean] = super.isPermutateSynonyms
+    var sparse: Optional[lang.Boolean] = super.isSparse
+    var greedy: Optional[lang.Boolean] = super.isGreedy
+
     override def getId: String = id
     override def getSynonyms: util.List[String] = (syns :+ id).asJava
     override def getValues: util.List[NCValue] = values
+
+    override def getMetadata: util.Map[String, AnyRef] = metadata
+    override def getDescription: String = description
+    override def getParentId: String = parentId
+    override def isPermutateSynonyms: Optional[lang.Boolean] = permutateSynonyms
+    override def isSparse: Optional[lang.Boolean] = sparse
+    override def isGreedy: Optional[lang.Boolean] = greedy
 }
 
 /**
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/NCEnrichersTestBeans.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/NCEnrichersTestBeans.scala
index fa54b2d..b4d2f71 100644
--- a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/NCEnrichersTestBeans.scala
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/NCEnrichersTestBeans.scala
@@ -64,11 +64,20 @@ case class NCTestCoordinateToken(text: String, latitude: Double, longitude: Doub
     override def toString: String = s"$text(coordinate)<lon=$longitude, lat=$longitude>"
 }
 
-case class NCTestNumericToken(text: String, from: Double, to: Double) extends NCTestToken {
+case class NCTestNumericToken(text: String, from: Double, to: Double, unit: Option[String] = None) extends NCTestToken {
     require(text != null)
 
     override def id: String = "nlpcraft:num"
-    override def toString: String = s"$text(num)<from=$from, to=$to>"
+    override def toString: String = {
+        var s = s"$text(num)<from=$from, to=$to>"
+
+        unit match {
+            case Some(u) => s = s"$s($u)"
+            case None => // No-op.
+        }
+
+        s
+    }
 }
 
 case class NCTestCityToken(text: String, city: String) extends NCTestToken {
@@ -305,10 +314,13 @@ object NCTestToken {
                     longitude = t.meta("nlpcraft:coordinate:longitude")
                 )
             case "nlpcraft:num" =>
+                val unit: Optional[String] = t.metaOpt("nlpcraft:num:unit")
+
                 NCTestNumericToken(
                     txt,
                     from = t.meta("nlpcraft:num:from"),
-                    to = t.meta("nlpcraft:num:to")
+                    to = t.meta("nlpcraft:num:to"),
+                    unit = unit.asScala
                 )
             case "nlpcraft:date" => NCTestDateToken(txt)
             case "nlpcraft:city" => NCTestCityToken(txt, city = t.meta("nlpcraft:city:city"))
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/NCEnricherNestedModelSpec2.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/NCEnricherNestedModelSpec2.scala
index 45c1690..ce2fc90 100644
--- a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/NCEnricherNestedModelSpec2.scala
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/NCEnricherNestedModelSpec2.scala
@@ -68,5 +68,4 @@ class NCNestedTestModel22 extends NCNestedTestModel21 {
   * Nested elements model enricher test.
   */
 @NCTestEnvironment(model = classOf[NCNestedTestModel22], startClient = true)
-class NCEnricherNestedModelSpec22 extends NCEnricherNestedModelSpec21
-
+class NCEnricherNestedModelSpec22 extends NCEnricherNestedModelSpec21
\ No newline at end of file
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/NCEnricherNestedModelSpec3.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/NCEnricherNestedModelSpec3.scala
index 60e83c6..2303e30 100644
--- a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/NCEnricherNestedModelSpec3.scala
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/NCEnricherNestedModelSpec3.scala
@@ -17,7 +17,7 @@
 
 package org.apache.nlpcraft.probe.mgrs.nlp.enrichers.model
 
-import org.apache.nlpcraft.model.{NCElement, NCIntent, NCIntentMatch, NCModelAdapter, NCResult}
+import org.apache.nlpcraft.model.{NCElement, NCIntent, NCModelAdapter, NCResult}
 import org.apache.nlpcraft.{NCTestContext, NCTestElement, NCTestEnvironment}
 import org.junit.jupiter.api.Test
 
@@ -27,20 +27,18 @@ import scala.jdk.CollectionConverters.SetHasAsJava
 /**
   * Nested Elements test model.
   */
-class NCNestedTestModel3 extends NCModelAdapter(
-    "nlpcraft.nested3.test.mdl", "Nested Data Test Model", "1.0"
-) {
+class NCNestedTestModel3 extends NCModelAdapter("nlpcraft.nested3.test.mdl", "Nested Test Model", "1.0") {
     override def getElements: util.Set[NCElement] =
         Set(
             NCTestElement("e1", "//[a-zA-Z0-9]+//"),
-            NCTestElement("e2", "^^{# == 'e1'}^^"),
+            NCTestElement("e2", "^^{# == 'e1'}^^")
         )
 
     override def getAbstractTokens: util.Set[String] = Set("e1").asJava
     override def getEnabledBuiltInTokens: util.Set[String] = Set.empty[String].asJava
 
     @NCIntent("intent=onE2 term(t1)={# == 'e2'}[12, 100]")
-    def onAB(ctx: NCIntentMatch): NCResult = NCResult.text("OK")
+    def onAB(): NCResult = NCResult.text("OK")
 }
 
 /**
@@ -49,5 +47,5 @@ class NCNestedTestModel3 extends NCModelAdapter(
 @NCTestEnvironment(model = classOf[NCNestedTestModel3], startClient = true)
 class NCEnricherNestedModelSpec3 extends NCTestContext {
     @Test
-    def test(): Unit = checkIntent("a " * 18, "onE2")
+    def test(): Unit = checkIntent("a " * 15, "onE2")
 }
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/NCEnricherNestedModelSpec41.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/NCEnricherNestedModelSpec4.scala
similarity index 95%
rename from nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/NCEnricherNestedModelSpec41.scala
rename to nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/NCEnricherNestedModelSpec4.scala
index e049a39..27082f1 100644
--- a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/NCEnricherNestedModelSpec41.scala
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/NCEnricherNestedModelSpec4.scala
@@ -27,9 +27,7 @@ import scala.jdk.CollectionConverters.SetHasAsJava
 /**
   * Nested Elements test model.
   */
-class NCNestedTestModel41 extends NCModelAdapter(
-    "nlpcraft.nested4.test.mdl", "Nested Data Test Model", "1.0"
-) {
+class NCNestedTestModel41 extends NCModelAdapter("nlpcraft.nested4.test.mdl", "Nested Test Model", "1.0") {
     override def getElements: util.Set[NCElement] =
         Set(
             NCTestElement("e1", "//[a-zA-Z0-9]+//"),
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/NCEnricherNestedModelSpec5.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/NCEnricherNestedModelSpec5.scala
index 72d6589..48d0441 100644
--- a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/NCEnricherNestedModelSpec5.scala
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/NCEnricherNestedModelSpec5.scala
@@ -26,9 +26,7 @@ import java.util
 /**
   * Nested Elements test model.
   */
-class NCNestedTestModel5 extends NCModelAdapter(
-    "nlpcraft.nested5.test.mdl", "Nested Data Test Model", "1.0"
-) {
+class NCNestedTestModel5 extends NCModelAdapter("nlpcraft.nested5.test.mdl", "Nested Test Model", "1.0") {
     override def getElements: util.Set[NCElement] =
         Set(
             NCTestElement("cityWrapper", "^^[cityAlias]{# == 'nlpcraft:city'}^^"),
@@ -40,7 +38,7 @@ class NCNestedTestModel5 extends NCModelAdapter(
         "    get(meta_part('cityAlias', 'nlpcraft:city:citymeta'), 'population') >= 10381222" +
         "}"
     )
-    private def onBigCity(ctx: NCIntentMatch): NCResult = NCResult.text("OK")
+    private def onBigCity(): NCResult = NCResult.text("OK")
 }
 
 /**
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/NCEnricherNestedModelSpec5.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/NCEnricherNestedModelSpec6.scala
similarity index 55%
copy from nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/NCEnricherNestedModelSpec5.scala
copy to nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/NCEnricherNestedModelSpec6.scala
index 72d6589..fd21e8a 100644
--- a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/NCEnricherNestedModelSpec5.scala
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/NCEnricherNestedModelSpec6.scala
@@ -17,37 +17,38 @@
 
 package org.apache.nlpcraft.probe.mgrs.nlp.enrichers.model
 
-import org.apache.nlpcraft.model.{NCElement, NCIntent, NCIntentMatch, NCModelAdapter, NCResult}
+import org.apache.nlpcraft.model.{NCContext, NCElement, NCModelAdapter, NCResult}
 import org.apache.nlpcraft.{NCTestContext, NCTestElement, NCTestEnvironment}
 import org.junit.jupiter.api.Test
 
 import java.util
+import scala.jdk.CollectionConverters.{IterableHasAsScala, SetHasAsJava}
 
 /**
   * Nested Elements test model.
   */
-class NCNestedTestModel5 extends NCModelAdapter(
-    "nlpcraft.nested5.test.mdl", "Nested Data Test Model", "1.0"
-) {
+class NCNestedTestModel6 extends NCModelAdapter("nlpcraft.nested6.test.mdl", "Nested Test Model", "1.0") {
+    override def getAbstractTokens: util.Set[String] = Set("nlpcraft:date").asJava
+
     override def getElements: util.Set[NCElement] =
-        Set(
-            NCTestElement("cityWrapper", "^^[cityAlias]{# == 'nlpcraft:city'}^^"),
-        )
-    @NCIntent(
-        "intent=bigCity " +
-        "term(city)={" +
-        "    # == 'cityWrapper' && " +
-        "    get(meta_part('cityAlias', 'nlpcraft:city:citymeta'), 'population') >= 10381222" +
-        "}"
-    )
-    private def onBigCity(ctx: NCIntentMatch): NCResult = NCResult.text("OK")
-}
+        Set(NCTestElement("dateWrapper", "^^{# == 'nlpcraft:date'}^^"))
+
+    override def onContext(ctx: NCContext): NCResult = {
+        require(ctx.getRequest.getNormalizedText == "today")
 
+        println(s"Variants:\n${ctx.getVariants.asScala.mkString("\n")}")
+
+        // `nlpcraft:date` will be deleted.
+        require(ctx.getVariants.size() == 1)
+
+        NCResult.text("OK")
+    }
+}
 /**
   *
   */
-@NCTestEnvironment(model = classOf[NCNestedTestModel5], startClient = true)
-class NCEnricherNestedModelSpec5 extends NCTestContext {
+@NCTestEnvironment(model = classOf[NCNestedTestModel6], startClient = true)
+class NCEnricherNestedModelSpec6 extends NCTestContext {
     @Test
-    def test(): Unit = checkIntent("moscow", "bigCity")
+    def test(): Unit = checkResult("today", "OK")
 }
\ No newline at end of file
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/NCNestedAnySpec.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/NCNestedAnySpec.scala
new file mode 100644
index 0000000..8d8c769
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/NCNestedAnySpec.scala
@@ -0,0 +1,102 @@
+/*
+ * 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
+ *
+ *      https://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.
+ */
+
+package org.apache.nlpcraft.probe.mgrs.nlp.enrichers.model.anyword
+
+import org.apache.nlpcraft.{NCTestContext, NCTestEnvironment}
+import org.junit.jupiter.api.Test
+
+/**
+  *
+  */
+class NCNestedAnySpec extends NCTestContext {
+    private def test(): Unit = {
+        // 1, 2 and 3 any words should be suitable.
+        checkIntent("a t1 t2 t3 b", "compose")
+        checkIntent("a t1 t2 b", "compose")
+        checkIntent("a t1 b", "compose")
+
+        // Too many 'any words'.
+        checkFail("a t1 t2 t3 t4 b")
+
+        // Missed 'any words'.
+        checkFail("a b")
+    }
+
+    @Test @NCTestEnvironment(model = classOf[NCNestedTestModelAnyRegex1], startClient = true)
+    def testRegex1(): Unit = test()
+
+    @Test @NCTestEnvironment(model = classOf[NCNestedTestModelAnyRegex2], startClient = true)
+    def testRegex2(): Unit = test()
+
+    @Test @NCTestEnvironment(model = classOf[NCNestedTestModelAnyRegex3], startClient = true)
+    def testRegex3(): Unit = test()
+
+    @Test @NCTestEnvironment(model = classOf[NCNestedTestModelAnyRegex5], startClient = true)
+    def testRegex4(): Unit = test()
+
+    @Test @NCTestEnvironment(model = classOf[NCNestedTestModelAnyRegex5], startClient = true)
+    def testRegex5(): Unit = test()
+
+    @Test @NCTestEnvironment(model = classOf[NCNestedTestModelAnyRegex6], startClient = true)
+    def testRegex6(): Unit = test()
+
+    @Test @NCTestEnvironment(model = classOf[NCNestedTestModelAnyRegex7], startClient = true)
+    def testRegex7(): Unit = test()
+
+    @Test @NCTestEnvironment(model = classOf[NCNestedTestModelAnyAlphaNum1], startClient = true)
+    def testAlphaNum1(): Unit = test()
+
+    @Test @NCTestEnvironment(model = classOf[NCNestedTestModelAnyAlphaNum2], startClient = true)
+    def testAlphaNum2(): Unit = test()
+
+    @Test @NCTestEnvironment(model = classOf[NCNestedTestModelAnyAlphaNum3], startClient = true)
+    def testAlphaNum3(): Unit = test()
+
+    @Test @NCTestEnvironment(model = classOf[NCNestedTestModelAnyAlphaNum4], startClient = true)
+    def testAlphaNum4(): Unit = test()
+
+    @Test @NCTestEnvironment(model = classOf[NCNestedTestModelAnyAlphaNum5], startClient = true)
+    def testAlphaNum5(): Unit = test()
+
+    @Test @NCTestEnvironment(model = classOf[NCNestedTestModelAnyAlphaNum6], startClient = true)
+    def testAlphaNum6(): Unit = test()
+
+    @Test @NCTestEnvironment(model = classOf[NCNestedTestModelAnyAlphaNum7], startClient = true)
+    def testAlphaNum7(): Unit = test()
+
+    @Test @NCTestEnvironment(model = classOf[NCNestedTestModelAnyNotSpace1], startClient = true)
+    def testNotSpaceNum1(): Unit = test()
+
+    @Test @NCTestEnvironment(model = classOf[NCNestedTestModelAnyNotSpace2], startClient = true)
+    def testNotSpaceNum2(): Unit = test()
+
+    @Test @NCTestEnvironment(model = classOf[NCNestedTestModelAnyNotSpace3], startClient = true)
+    def testNotSpaceNum3(): Unit = test()
+
+    @Test @NCTestEnvironment(model = classOf[NCNestedTestModelAnyNotSpace4], startClient = true)
+    def testNotSpaceNum4(): Unit = test()
+
+    @Test @NCTestEnvironment(model = classOf[NCNestedTestModelAnyNotSpace5], startClient = true)
+    def testNotSpaceNum5(): Unit = test()
+
+    @Test @NCTestEnvironment(model = classOf[NCNestedTestModelAnyNotSpace6], startClient = true)
+    def testNotSpaceNum6(): Unit = test()
+
+    @Test @NCTestEnvironment(model = classOf[NCNestedTestModelAnyNotSpace7], startClient = true)
+    def testNotSpaceNum7(): Unit = test()
+}
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/NCNestedTestModelsAnyAlphaNum.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/NCNestedTestModelsAnyAlphaNum.scala
new file mode 100644
index 0000000..32df509
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/NCNestedTestModelsAnyAlphaNum.scala
@@ -0,0 +1,35 @@
+/*
+ * 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
+ *
+ *      https://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.
+ */
+
+package org.apache.nlpcraft.probe.mgrs.nlp.enrichers.model.anyword
+
+import org.apache.nlpcraft.probe.mgrs.nlp.enrichers.model.anyword.adapters._
+
+/**
+  *
+  */
+trait NCNestedTestModelAnyAlphaNum extends NCNestedModelAnyAdapter {
+    override def anyDefinition: String = "{^^{is_alphanum(tok_txt)}^^}"
+}
+
+class NCNestedTestModelAnyAlphaNum1 extends NCNestedTestModelAny1 with NCNestedTestModelAnyAlphaNum
+class NCNestedTestModelAnyAlphaNum2 extends NCNestedTestModelAny2 with NCNestedTestModelAnyAlphaNum
+class NCNestedTestModelAnyAlphaNum3 extends NCNestedTestModelAny3 with NCNestedTestModelAnyAlphaNum
+class NCNestedTestModelAnyAlphaNum4 extends NCNestedTestModelAny4 with NCNestedTestModelAnyAlphaNum
+class NCNestedTestModelAnyAlphaNum5 extends NCNestedTestModelAny5 with NCNestedTestModelAnyAlphaNum
+class NCNestedTestModelAnyAlphaNum6 extends NCNestedTestModelAny6 with NCNestedTestModelAnyAlphaNum
+class NCNestedTestModelAnyAlphaNum7 extends NCNestedTestModelAny7 with NCNestedTestModelAnyAlphaNum
\ No newline at end of file
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/NCNestedTestModelsAnyNotSpace.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/NCNestedTestModelsAnyNotSpace.scala
new file mode 100644
index 0000000..dd5ba28
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/NCNestedTestModelsAnyNotSpace.scala
@@ -0,0 +1,35 @@
+/*
+ * 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
+ *
+ *      https://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.
+ */
+
+package org.apache.nlpcraft.probe.mgrs.nlp.enrichers.model.anyword
+
+import org.apache.nlpcraft.probe.mgrs.nlp.enrichers.model.anyword.adapters._
+
+/**
+  *
+  */
+trait NCNestedTestModelAnyNotSpace extends NCNestedModelAnyAdapter {
+    override def anyDefinition: String = "{^^{tok_txt != ' '}^^}"
+}
+
+class NCNestedTestModelAnyNotSpace1 extends NCNestedTestModelAny1 with NCNestedTestModelAnyNotSpace
+class NCNestedTestModelAnyNotSpace2 extends NCNestedTestModelAny2 with NCNestedTestModelAnyNotSpace
+class NCNestedTestModelAnyNotSpace3 extends NCNestedTestModelAny3 with NCNestedTestModelAnyNotSpace
+class NCNestedTestModelAnyNotSpace4 extends NCNestedTestModelAny4 with NCNestedTestModelAnyNotSpace
+class NCNestedTestModelAnyNotSpace5 extends NCNestedTestModelAny5 with NCNestedTestModelAnyNotSpace
+class NCNestedTestModelAnyNotSpace6 extends NCNestedTestModelAny6 with NCNestedTestModelAnyNotSpace
+class NCNestedTestModelAnyNotSpace7 extends NCNestedTestModelAny7 with NCNestedTestModelAnyNotSpace
\ No newline at end of file
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/NCNestedTestModelsAnyRegex.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/NCNestedTestModelsAnyRegex.scala
new file mode 100644
index 0000000..7a1fd71
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/NCNestedTestModelsAnyRegex.scala
@@ -0,0 +1,35 @@
+/*
+ * 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
+ *
+ *      https://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.
+ */
+
+package org.apache.nlpcraft.probe.mgrs.nlp.enrichers.model.anyword
+
+import org.apache.nlpcraft.probe.mgrs.nlp.enrichers.model.anyword.adapters._
+
+/**
+  *
+  */
+trait NCNestedTestModelAnyRegex extends NCNestedModelAnyAdapter {
+    override def anyDefinition: String = "{//[a-zA-Z0-9]+//}"
+}
+
+class NCNestedTestModelAnyRegex1 extends NCNestedTestModelAny1 with NCNestedTestModelAnyRegex
+class NCNestedTestModelAnyRegex2 extends NCNestedTestModelAny2 with NCNestedTestModelAnyRegex
+class NCNestedTestModelAnyRegex3 extends NCNestedTestModelAny3 with NCNestedTestModelAnyRegex
+class NCNestedTestModelAnyRegex4 extends NCNestedTestModelAny4 with NCNestedTestModelAnyRegex
+class NCNestedTestModelAnyRegex5 extends NCNestedTestModelAny5 with NCNestedTestModelAnyRegex
+class NCNestedTestModelAnyRegex6 extends NCNestedTestModelAny6 with NCNestedTestModelAnyRegex
+class NCNestedTestModelAnyRegex7 extends NCNestedTestModelAny7 with NCNestedTestModelAnyRegex
\ No newline at end of file
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/nlp/numeric/NCNumeric.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/adapters/NCNestedModelAnyAdapter.scala
similarity index 55%
copy from nlpcraft/src/main/scala/org/apache/nlpcraft/common/nlp/numeric/NCNumeric.scala
copy to nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/adapters/NCNestedModelAnyAdapter.scala
index 75a3365..0627c6e 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/nlp/numeric/NCNumeric.scala
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/adapters/NCNestedModelAnyAdapter.scala
@@ -15,27 +15,28 @@
  * limitations under the License.
  */
 
-package org.apache.nlpcraft.common.nlp.numeric
+package org.apache.nlpcraft.probe.mgrs.nlp.enrichers.model.anyword.adapters
 
-import org.apache.nlpcraft.common.nlp.NCNlpSentenceToken
+import org.apache.nlpcraft.NCTestElement
+import org.apache.nlpcraft.model.NCModelAdapter
 
-/**
-  *
-  * @param name
-  * @param unitType
-  */
-case class NCNumericUnit(name: String, unitType: String)
+import java.util
+import java.util.Optional
+import scala.jdk.CollectionConverters.SetHasAsJava
 
 /**
   *
-  * @param tokens
-  * @param value
-  * @param isFractional
-  * @param unit
   */
-case class NCNumeric(
-    tokens: Seq[NCNlpSentenceToken],
-    value: Double,
-    isFractional: Boolean,
-    unit: Option[NCNumericUnit]
-)
+abstract class NCNestedModelAnyAdapter extends NCModelAdapter("nlpcraft.test.mdl", "Test Model", "1.0") {
+    override def getEnabledBuiltInTokens: util.Set[String] = Set.empty[String].asJava
+
+    protected def mkNotGreedy(id: String, syn: String): NCTestElement = {
+        val e = NCTestElement(id, syn)
+
+        e.greedy = Optional.of(false)
+
+        e
+    }
+
+    def anyDefinition: String
+}
\ No newline at end of file
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/adapters/NCNestedTestModelAny1.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/adapters/NCNestedTestModelAny1.scala
new file mode 100644
index 0000000..de70e71
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/adapters/NCNestedTestModelAny1.scala
@@ -0,0 +1,51 @@
+/*
+ * 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
+ *
+ *      https://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.
+ */
+
+package org.apache.nlpcraft.probe.mgrs.nlp.enrichers.model.anyword.adapters
+
+import org.apache.nlpcraft.NCTestElement
+import org.apache.nlpcraft.model.{NCElement, NCIntent, NCResult}
+
+import java.util
+import scala.jdk.CollectionConverters.SetHasAsJava
+
+/**
+  * 'any' ('not greedy') element - regex.
+  * 'compose' element.
+  * intent without DSL.
+  * 'any' element's position is not restricted.
+  */
+abstract class NCNestedTestModelAny1 extends NCNestedModelAnyAdapter {
+    override def getAbstractTokens: util.Set[String] = Set("a", "b", "any").asJava
+
+    // Variants:
+    // a t1 t2 t3 b - 1
+    // a t1 t2 b - 1
+    // a t1 b - 1
+    // a t1 t2 t3 t4 b - 1
+    // a b - 1
+    override def getElements: util.Set[NCElement] =
+        Set(
+            NCTestElement("a"),
+            NCTestElement("b"),
+            mkNotGreedy("any", s"$anyDefinition[1, 3]"),
+            NCTestElement("compose", "^^{# == 'a'}^^ ^^{# == 'any'}^^ ^^{# == 'b'}^^")
+        )
+
+    @NCIntent("intent=compose term(x)={# == 'compose'}")
+    def onCompose(): NCResult = NCResult.text("OK")
+}
\ No newline at end of file
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/adapters/NCNestedTestModelAny2.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/adapters/NCNestedTestModelAny2.scala
new file mode 100644
index 0000000..74218ee
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/adapters/NCNestedTestModelAny2.scala
@@ -0,0 +1,52 @@
+/*
+ * 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
+ *
+ *      https://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.
+ */
+
+package org.apache.nlpcraft.probe.mgrs.nlp.enrichers.model.anyword.adapters
+
+import org.apache.nlpcraft.NCTestElement
+import org.apache.nlpcraft.model.{NCElement, NCIntent, NCResult}
+
+import java.util
+
+/**
+  * 'any' ('not greedy') element - regex.
+  * 'compose' element.
+  * intent without DSL.
+  * 'any' element's position is restricted.
+  */
+abstract class NCNestedTestModelAny2 extends NCNestedModelAnyAdapter {
+    //override def getAbstractTokens: util.Set[String] = Set("a", "b", "any").asJava
+
+    // Variants:
+    // a t1 t2 t3 b -
+    // a t1 t2 b -
+    // a t1 b -
+    // a t1 t2 t3 t4 b -
+    // a b -
+    override def getElements: util.Set[NCElement] =
+        Set(
+            NCTestElement("a"),
+            NCTestElement("b"),
+            mkNotGreedy("any", s"$anyDefinition[1, 3]"),
+            NCTestElement(
+                "compose", "^^{# == 'a'}^^ ^^{# == 'any' && tok_is_between_ids('a', 'b') == true}^^ ^^{# == 'b'}^^"
+            )
+        )
+
+    @NCIntent("intent=compose term(x)={# == 'compose'}")
+    def onCompose(): NCResult = NCResult.text("OK")
+}
\ No newline at end of file
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/adapters/NCNestedTestModelAny3.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/adapters/NCNestedTestModelAny3.scala
new file mode 100644
index 0000000..ce3dcf8
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/adapters/NCNestedTestModelAny3.scala
@@ -0,0 +1,49 @@
+/*
+ * 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
+ *
+ *      https://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.
+ */
+
+package org.apache.nlpcraft.probe.mgrs.nlp.enrichers.model.anyword.adapters
+
+import org.apache.nlpcraft.NCTestElement
+import org.apache.nlpcraft.model.{NCElement, NCIntent, NCResult}
+
+import java.util
+import scala.jdk.CollectionConverters.SetHasAsJava
+
+/**
+  * 'compose' element with IDL (regex)
+  * intent without DSL.
+  * 'any' element's position is restricted implicitly via synonym definition.
+  */
+abstract class NCNestedTestModelAny3 extends NCNestedModelAnyAdapter {
+    override def getAbstractTokens: util.Set[String] = Set("a", "b").asJava
+
+    // Variants:
+    // a t1 t2 t3 b - 1
+    // a t1 t2 b - 1
+    // a t1 b - 1
+    // a t1 t2 t3 t4 b - 1
+    // a b - 1
+    override def getElements: util.Set[NCElement] =
+        Set(
+            NCTestElement("a"),
+            NCTestElement("b"),
+            NCTestElement("compose", s"^^{# == 'a'}^^ $anyDefinition[1, 3] ^^{# == 'b'}^^")
+        )
+
+    @NCIntent("intent=compose term(x)={# == 'compose'}")
+    def onCompose(): NCResult = NCResult.text("OK")
+}
\ No newline at end of file
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/adapters/NCNestedTestModelAny4.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/adapters/NCNestedTestModelAny4.scala
new file mode 100644
index 0000000..e1fc89d
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/adapters/NCNestedTestModelAny4.scala
@@ -0,0 +1,55 @@
+/*
+ * 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
+ *
+ *      https://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.
+ */
+
+package org.apache.nlpcraft.probe.mgrs.nlp.enrichers.model.anyword.adapters
+
+import org.apache.nlpcraft.NCTestElement
+import org.apache.nlpcraft.model.{NCElement, NCIntent, NCResult}
+
+import java.util
+import scala.jdk.CollectionConverters.SetHasAsJava
+
+/**
+  * 'any' ('not greedy') element - regex.
+  * no 'compose' element.
+  * intent with DSL.
+  * 'any' element's position is not restricted (IDL).
+  */
+abstract class NCNestedTestModelAny4 extends NCNestedModelAnyAdapter {
+    override def getAbstractTokens: util.Set[String] = Set.empty[String].asJava
+
+    // Variants:
+    // a t1 t2 t3 b - 13
+    // a t1 t2 b - 11
+    // a t1 b - 8
+    // a t1 t2 t3 t4 b - 16
+    // a b - 5
+    override def getElements: util.Set[NCElement] =
+        Set(
+            NCTestElement("a"),
+            NCTestElement("b"),
+            mkNotGreedy("any", s"$anyDefinition[1, 3]")
+        )
+
+    @NCIntent(
+        "intent=compose options={'ordered': true, 'unused_free_words': false} " +
+        "    term(a)={# == 'a'}" +
+        "    term(any)={# == 'any'} " +
+        "    term(b)={# == 'b'}"
+    )
+    def onCompose(): NCResult = NCResult.text("OK")
+}
\ No newline at end of file
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/adapters/NCNestedTestModelAny5.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/adapters/NCNestedTestModelAny5.scala
new file mode 100644
index 0000000..8bcdd7f
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/adapters/NCNestedTestModelAny5.scala
@@ -0,0 +1,55 @@
+/*
+ * 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
+ *
+ *      https://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.
+ */
+
+package org.apache.nlpcraft.probe.mgrs.nlp.enrichers.model.anyword.adapters
+
+import org.apache.nlpcraft.NCTestElement
+import org.apache.nlpcraft.model.{NCElement, NCIntent, NCResult}
+
+import java.util
+import scala.jdk.CollectionConverters.SetHasAsJava
+
+/**
+  * 'any' ('not greedy') element - regex.
+  * no 'compose' element.
+  * intent with DSL.
+  * 'any' element's position is not restricted (IDL).
+  */
+abstract class NCNestedTestModelAny5 extends NCNestedModelAnyAdapter {
+    override def getAbstractTokens: util.Set[String] = Set.empty[String].asJava
+
+    // Variants:
+    // a t1 t2 t3 b - 4
+    // a t1 t2 b - 4
+    // a t1 b - 4
+    // a t1 t2 t3 t4 b - 4
+    // a b - 4
+    override def getElements: util.Set[NCElement] =
+        Set(
+            NCTestElement("a"),
+            NCTestElement("b"),
+            mkNotGreedy("any", anyDefinition)
+        )
+
+    @NCIntent(
+        "intent=compose options={'ordered': true, 'unused_free_words': false} " +
+        "    term(a)={# == 'a'}" +
+        "    term(any)={# == 'any'}[1, 3] " +
+        "    term(b)={# == 'b'}"
+    )
+    def onCompose(): NCResult = NCResult.text("OK")
+}
\ No newline at end of file
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/adapters/NCNestedTestModelAny6.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/adapters/NCNestedTestModelAny6.scala
new file mode 100644
index 0000000..d63151b
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/adapters/NCNestedTestModelAny6.scala
@@ -0,0 +1,55 @@
+/*
+ * 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
+ *
+ *      https://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.
+ */
+
+package org.apache.nlpcraft.probe.mgrs.nlp.enrichers.model.anyword.adapters
+
+import org.apache.nlpcraft.NCTestElement
+import org.apache.nlpcraft.model.{NCElement, NCIntent, NCResult}
+
+import java.util
+import scala.jdk.CollectionConverters.SetHasAsJava
+
+/**
+  * 'any' ('not greedy') element - regex.
+  * no 'compose' element.
+  * intent with DSL.
+  * 'any' element's position is restricted (IDL).
+  */
+abstract class NCNestedTestModelAny6 extends NCNestedModelAnyAdapter {
+    override def getAbstractTokens: util.Set[String] = Set.empty[String].asJava
+
+    // Variants:
+    // a t1 t2 t3 b - 13
+    // a t1 t2 b - 11
+    // a t1 b - 8
+    // a t1 t2 t3 t4 b - 16
+    // a b - 5
+    override def getElements: util.Set[NCElement] =
+        Set(
+            NCTestElement("a"),
+            NCTestElement("b"),
+            mkNotGreedy("any", s"$anyDefinition[1, 3]")
+        )
+
+    @NCIntent(
+        "intent=compose options={'ordered': true, 'unused_free_words': false} " +
+        "    term(a)={# == 'a'}" +
+        "    term(any)={# == 'any' && tok_is_between_ids('a', 'b') == true} " +
+        "    term(b)={# == 'b'}"
+        )
+    def onCompose(): NCResult = NCResult.text("OK")
+}
\ No newline at end of file
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/adapters/NCNestedTestModelAny7.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/adapters/NCNestedTestModelAny7.scala
new file mode 100644
index 0000000..384cb26
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/model/anyword/adapters/NCNestedTestModelAny7.scala
@@ -0,0 +1,55 @@
+/*
+ * 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
+ *
+ *      https://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.
+ */
+
+package org.apache.nlpcraft.probe.mgrs.nlp.enrichers.model.anyword.adapters
+
+import org.apache.nlpcraft.NCTestElement
+import org.apache.nlpcraft.model.{NCElement, NCIntent, NCResult}
+
+import java.util
+import scala.jdk.CollectionConverters.SetHasAsJava
+
+/**
+  * 'any' ('not greedy') element - regex.
+  * no 'compose' element.
+  * intent with DSL.
+  * 'any' element's position is restricted (IDL).
+  */
+abstract class NCNestedTestModelAny7 extends NCNestedModelAnyAdapter {
+    override def getAbstractTokens: util.Set[String] = Set.empty[String].asJava
+
+    // Variants:
+    // a t1 t2 t3 b - 4
+    // a t1 t2 b - 4
+    // a t1 b - 4
+    // a t1 t2 t3 t4 b - 4
+    // a b - 4
+    override def getElements: util.Set[NCElement] =
+        Set(
+            NCTestElement("a"),
+            NCTestElement("b"),
+            mkNotGreedy("any", anyDefinition)
+        )
+
+    @NCIntent(
+        "intent=compose options={'ordered': true, 'unused_free_words': false} " +
+        "    term(a)={# == 'a'}" +
+        "    term(any)={# == 'any' && tok_is_between_ids('a', 'b') == true}[1, 3] " +
+        "    term(b)={# == 'b'}"
+        )
+    def onCompose(): NCResult = NCResult.text("OK")
+}
\ No newline at end of file
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/numeric/NCEnricherNumericSpec.scala b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/numeric/NCEnricherNumericSpec.scala
new file mode 100644
index 0000000..7e25cb1
--- /dev/null
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/numeric/NCEnricherNumericSpec.scala
@@ -0,0 +1,75 @@
+/*
+ * 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
+ *
+ *      https://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.
+ */
+
+package org.apache.nlpcraft.probe.mgrs.nlp.enrichers.numeric
+
+import org.apache.nlpcraft.NCTestEnvironment
+import org.apache.nlpcraft.probe.mgrs.nlp.enrichers.{NCDefaultTestModel, NCEnricherBaseSpec, NCTestNlpToken => nlp, NCTestNumericToken => num}
+import org.junit.jupiter.api.Test
+
+/**
+  * Limit enricher test.
+  */
+@NCTestEnvironment(model = classOf[NCDefaultTestModel], startClient = true)
+class NCEnricherNumericSpec extends NCEnricherBaseSpec {
+    /**
+      *
+      */
+    @Test
+    def test(): Unit =
+        runBatch(
+            // Two variants.
+            _ => checkAll(
+                "23 meters",
+                Seq(
+                    num(text = "23 meters", 23, 23, unit = Some("meter"))
+                ),
+                Seq(
+                    num(text = "23", 23, 23),
+                    nlp(text = "meters")
+                )
+            ),
+
+            // Two variants.
+            _ => checkAll(
+                "23 m",
+                Seq(
+                    num(text = "23 m", 23, 23, unit = Some("meter"))
+                ),
+                Seq(
+                    num(text = "23", 23, 23),
+                    nlp(text = "m")
+                )
+            ),
+
+            // One variant.
+            _ => checkAll(
+                "23m",
+                Seq(
+                    num(text = "23m", 23, 23, unit = Some("meter"))
+                )
+            ),
+
+            // One variant.
+            _ => checkAll(
+                "23M",
+                Seq(
+                    num(text = "23M", 23, 23, unit = Some("meter"))
+                )
+            )
+        )
+}