Repository: XiaoMi/MiNLP (language: python)

Issue:
[DUCK]时间类型错误：7点05分
7点05分，预期只抽取datetime   
实际：duration+datetime



Discussion / hints:

Fix patch:
diff --git a/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/Rules.scala b/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/Rules.scala
index a1644e79..cd8779fe 100644
--- a/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/Rules.scala
+++ b/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/Rules.scala
@@ -133,7 +133,7 @@ trait Rules extends DimRules {
     prod = tokens {
       case Token(Time, td: TimeData) ::
         Token(Ordinal, od: OrdinalData)
-        :: Token(TimeGrain, GrainData(g, _)) :: _ if td.timeGrain > g && g == Grain.Day && !od.ge =>
+        :: Token(TimeGrain, GrainData(g, _, _)) :: _ if td.timeGrain > g && g == Grain.Day && !od.ge =>
         
         val predicate = if (od.value >= 0) {
           val ov = if(od.value > 0) od.value.toInt -1 else 0
@@ -157,7 +157,7 @@ trait Rules extends DimRules {
     prod = tokens {
       case Token(Time, td: TimeData) :: _ ::
         Token(Ordinal, od: OrdinalData)
-        :: Token(TimeGrain, GrainData(g, _)) :: _ if td.timeGrain > g && g == Grain.Day && !od.ge =>
+        :: Token(TimeGrain, GrainData(g, _, _)) :: _ if td.timeGrain > g && g == Grain.Day && !od.ge =>
         
         val predicate = if (od.value >= 0) {
           val ov = if(od.value > 0) od.value.toInt -1 else 0
@@ -237,7 +237,7 @@ trait Rules extends DimRules {
     pattern =
       List("(这个?|今个?|本|下+一?个?|上+一?个?|去|昨|明|大*前|大*后)".regex, isDimension(TimeGrain).predicate),
     prod = tokens {
-      case Token(_, GroupMatch(s :: _)) :: Token(TimeGrain, GrainData(g, false)) :: _ =>
+      case Token(_, GroupMatch(s :: _)) :: Token(TimeGrain, GrainData(g, false, _)) :: _ =>
         val td: Option[TimeData] = s(0) match {
           case '这' => cycleNthThis(0, g, Year, Month, Hour, Week)
           case '今' => cycleNthThis(0, g, Year, Month, Day)
diff --git a/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/duration/Rules.scala b/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/duration/Rules.scala
index ce235379..ef67e7e5 100644
--- a/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/duration/Rules.scala
+++ b/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/duration/Rules.scala
@@ -43,7 +43,7 @@ trait Rules extends DimRules {
   }
 
   val tp: TokensProduction = {
-    case t1 :: Token(TimeGrain, GrainData(g, latent)) :: _ =>
+    case t1 :: Token(TimeGrain, GrainData(g, latent, _)) :: _ =>
       if (isDimension(UnitNumber)(t1) && !compatibleWithUnitNumber(g)) None
       else {
         if (isDimension(Numeral)(t1) && g == Month) None
@@ -95,7 +95,7 @@ trait Rules extends DimRules {
     name = "few <unit-of-duration>",
     pattern = List("几个?".regex, isDimension(TimeGrain).predicate),
     prod = optTokens {
-      case (options: Options, _ :: Token(TimeGrain, GrainData(g, _)) :: _) if options.timeOptions.durationFuzzyOn =>
+      case (options: Options, _ :: Token(TimeGrain, GrainData(g, _, _)) :: _) if options.timeOptions.durationFuzzyOn =>
         Token(Duration,
           DurationData(
             options.timeOptions.durationFuzzyValue, g,
@@ -118,7 +118,7 @@ trait Rules extends DimRules {
     name = "number.number grain",
     pattern = List("(\\d+)\\.(\\d+)".regex, isDimension(TimeGrain).predicate),
     prod = tokens {
-      case Token(_, GroupMatch(_ :: integer :: decimal :: _)) :: Token(_, GrainData(g, false)) :: _ =>
+      case Token(_, GroupMatch(_ :: integer :: decimal :: _)) :: Token(_, GrainData(g, false, _)) :: _ =>
         (for {
           i <- parseInt(integer).toOption
           d <- parseInt(decimal).toOption
@@ -144,7 +144,7 @@ trait Rules extends DimRules {
     name = "half a <time-grain>",
     pattern = List("半个?".regex, isDimension(TimeGrain).predicate),
     prod = tokens {
-      case _ :: Token(TimeGrain, GrainData(grain, _)) :: _ =>
+      case _ :: Token(TimeGrain, GrainData(grain, _, _)) :: _ =>
         for (d <- timesOneAndAHalf(grain, 0)) yield Token(Duration, d)
     }
   )
@@ -170,7 +170,7 @@ trait Rules extends DimRules {
     name = "<natural> half <unit-of-duration>",
     pattern = List(isDimension(UnitNumber).predicate, "半".regex, isDimension(TimeGrain).predicate),
     prod = tokens {
-      case t1 :: _ :: Token(TimeGrain, GrainData(g, _)) :: _ =>
+      case t1 :: _ :: Token(TimeGrain, GrainData(g, _, _)) :: _ =>
         for {
           i <- getIntValue(t1)
           d <- timesOneAndAHalf(g, i.toInt)
@@ -203,7 +203,7 @@ trait Rules extends DimRules {
       isNotLatentDuration.predicate
     ),
     prod = tokens {
-      case t1 :: Token(TimeGrain, GrainData(g, _)) :: _ :: Token(_, dd@DurationData(_, dg, _, _, _)) :: _
+      case t1 :: Token(TimeGrain, GrainData(g, _, _)) :: _ :: Token(_, dd@DurationData(_, dg, _, _, _)) :: _
         if g > dg && g != Month =>
         for (i <- getIntValue(t1)) yield Token(Duration, DurationData(i.toInt, g, schema = durationSchema(i.toInt.toString, g)) + dd)
     }
@@ -224,8 +224,8 @@ trait Rules extends DimRules {
       isDimension(TimeGrain).predicate
     ),
     prod = tokens {
-      case t1 :: Token(TimeGrain, GrainData(g0, _)) :: Token(DigitSequence, DigitSequenceData(s, _, _)) :: Token(TimeGrain, GrainData(g1, _)) :: _
-        if g0 > g1 =>
+      case t1 :: Token(TimeGrain, GrainData(g0, _, raw)) :: Token(DigitSequence, DigitSequenceData(s, _, _)) :: Token(TimeGrain, GrainData(g1, _, _)) :: _
+        if g0 > g1 && raw != "点" =>
         for (i <- getIntValue(t1)) yield Token(Duration, DurationData(i.toInt, g0, schema = durationSchema(i.toString, g0)) + DurationData(s.toInt, g1, schema = durationSchema(s.toInt.toString, g1)))
     }
   )
diff --git a/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/duration/package.scala b/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/duration/package.scala
index b3bc3c64..3b7948a9 100644
--- a/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/duration/package.scala
+++ b/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/duration/package.scala
@@ -60,7 +60,7 @@ package object duration {
   }
 
   def isMonth: Predicate = {
-    case Token(TimeGrain, GrainData(Month, _)) => true
+    case Token(TimeGrain, GrainData(Month, _, _)) => true
   }
 
   def isNotLatentDuration: Predicate = {
@@ -72,7 +72,7 @@ package object duration {
   }
 
   def isNotLatentGrain: Predicate = {
-    case Token(TimeGrain, GrainData(_, latent)) => !latent
+    case Token(TimeGrain, GrainData(_, latent, _)) => !latent
   }
   
   private def grainShorthand(grain: Grain): String = {
diff --git a/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/grain/Rules.scala b/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/grain/Rules.scala
index 3a7c8916..eb7e75ed 100644
--- a/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/grain/Rules.scala
+++ b/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/grain/Rules.scala
@@ -41,7 +41,7 @@ trait Rules extends DimRules {
     case (name, regexPattern, grain) =>
       Rule(name = name, pattern = List(regexPattern.regex), prod = singleRegexMatch {
         case s =>
-          Token(TimeGrain, GrainData(grain, latentExpr.contains(s)))
+          Token(TimeGrain, GrainData(grain, latentExpr.contains(s), s))
       })
   }
 }
diff --git a/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/grain/TimeGrain.scala b/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/grain/TimeGrain.scala
index 2da7a7c7..4af7c6ab 100644
--- a/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/grain/TimeGrain.scala
+++ b/duckling-fork-chinese/core/src/main/scala/com/xiaomi/duckling/dimension/time/grain/TimeGrain.scala
@@ -25,7 +25,7 @@ case object TimeGrain extends Dimension with Rules {
   override val name: String = "TimeGrain"
 }
 
-case class GrainData(grain: Grain, latent: Boolean = false) extends Resolvable with ResolvedValue {
+case class GrainData(grain: Grain, latent: Boolean = false, raw: String) extends Resolvable with ResolvedValue {
 
   override def resolve(context: Context, options: Options): Option[(ResolvedValue, Boolean)] = {
     (this, false)


Test patch:
diff --git a/duckling-fork-chinese/core/src/test/scala/com/xiaomi/duckling/dimension/time/duration/DurationTest.scala b/duckling-fork-chinese/core/src/test/scala/com/xiaomi/duckling/dimension/time/duration/DurationTest.scala
new file mode 100644
index 00000000..bcd12dc5
--- /dev/null
+++ b/duckling-fork-chinese/core/src/test/scala/com/xiaomi/duckling/dimension/time/duration/DurationTest.scala
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2020, Xiaomi and/or its affiliates. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.xiaomi.duckling.dimension.time.duration
+
+import com.xiaomi.duckling.Api.analyze
+import com.xiaomi.duckling.ranking.Testing._
+import com.xiaomi.duckling.UnitSpec
+
+class DurationTest extends UnitSpec {
+
+  private val options = testOptions.copy(targets = Set(Duration))
+
+  describe("duration cases") {
+
+    it("零一年五月八月") {
+      val a = analyze("七点05分", testContext, options)
+      a shouldBe empty
+    }
+  }
+}