Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DUCK] PartOfDay未结束区间参考重做 #255

Merged
merged 3 commits into from
Dec 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 12 additions & 17 deletions .github/workflows/duckling.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,17 @@ on:
jobs:
build:

runs-on: ubuntu-latest
runs-on: ubuntu-20.04

steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '11'
distribution: 'adopt'
- name: Run tests
run: |
cd duckling-fork-chinese
sbt duckModel
sbt +test
sbt coverage
sbt coverageAggregate
bash <(curl -s https://codecov.io/bash) -r du00cs/MiNLP -t 'd2de025e-e5b7-4115-a98e-07e6fc3d7001'
- uses: actions/[email protected]
with:
fetch-depth: 0
- uses: olafurpg/setup-scala@v10
- run: |
cd duckling-fork-chinese
sbt duckModel
sbt +test
sbt coverage
sbt coverageAggregate
bash <(curl -s https://codecov.io/bash) -r du00cs/MiNLP -t 'd2de025e-e5b7-4115-a98e-07e6fc3d7001'
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ case class TimeData(timePred: TimePredicate,
|| hint != Hint.Recent && !options.timeOptions.alwaysInFuture)
val valueOpt =
try {
resolveTimeData(refTime, this, reverseTake, options)
resolveTimeData(refTime, this, reverseTake)
} catch {
case e: java.time.DateTimeException =>
logger.error(s"time resolve failed with DateTimeException [${e.getMessage}]")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,8 @@ object TimePredicateHelpers {
notImmediate: Boolean,
cyclicPred: TimePredicate,
basePred: TimePredicate): TimePredicate = {
def f(t: TimeObject, ctx: TimeContext, options: Options): Option[TimeObject] = {
val (past, future) = runPredicate(cyclicPred)(t, ctx, null)
def f(t: TimeObject, ctx: TimeContext): Option[TimeObject] = {
val (past, future) = runPredicate(cyclicPred)(t, ctx)
val rest = if (n >= 0) {
future match {
case ahead #:: _ if notImmediate && timeBefore(ahead, t) => future.drop(n + 1)
Expand All @@ -151,7 +151,7 @@ object TimePredicateHelpers {
def timeCycle(grain: Grain): CycleSeriesPredicate = timeCycle(grain, grain)

def timeCycle(grain: Grain, roundGrain: Grain, step: Int = 1): CycleSeriesPredicate = {
CycleSeriesPredicate((t: TimeObject, _: TimeContext, _: Options) => {
CycleSeriesPredicate((t: TimeObject, _: TimeContext) => {
timeSequence(grain, step, if (roundGrain != NoGrain) timeRound(t, roundGrain) else t)
}, step, grain)
}
Expand All @@ -160,10 +160,10 @@ object TimePredicateHelpers {
* Takes `n` cycles of `f`
*/
def takeN(literalN: Int, notImmediate: Boolean, cycleSP: CycleSeriesPredicate): TimePredicate = {
def series(t: TimeObject, context: TimeContext, options: Options) = {
def series(t: TimeObject, context: TimeContext) = {
val baseTime = context.refTime
// 确定起点
val (past, future) = runPredicate(cycleSP)(baseTime, context, options)
val (past, future) = runPredicate(cycleSP)(baseTime, context)
val fut = future match {
case ahead #:: rest if notImmediate && timeIntersect(ahead)(baseTime).nonEmpty => rest
case _ => future
Expand Down Expand Up @@ -200,8 +200,8 @@ object TimePredicateHelpers {
* 0 is the first element in the future
*/
def takeNth(n: Int, notImmediate: Boolean, f: TimePredicate): TimePredicate = {
val series = (t: TimeObject, context: TimeContext, options: Options) => {
val (past, future) = runPredicate(f)(context.refTime, context, options)
val series = (t: TimeObject, context: TimeContext) => {
val (past, future) = runPredicate(f)(context.refTime, context)
val rest = if (n >= 0) {
future match {
case Stream.Empty => Stream.Empty
Expand Down Expand Up @@ -232,7 +232,7 @@ object TimePredicateHelpers {
}

def solarTermPredicate(term: String): SeriesPredicate = {
val series: SeriesPredicateF = (t: TimeObject, context: TimeContext, options: Options) => {
val series: SeriesPredicateF = (t: TimeObject, context: TimeContext) => {
if (!containSolarTerm(t.start.year, term)) (Stream.empty, Stream.empty)
else {
def f(step: Int)(to: TimeObject): TimeObject = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
package com.xiaomi.duckling.dimension

import com.github.heqiao2010.lunar.{LunarCalendar, LunarData}

import java.time.LocalTime

import com.xiaomi.duckling.Types.{conf, Options, ZoneCN}
import com.xiaomi.duckling.Types.{ZoneCN, conf}
import com.xiaomi.duckling.dimension.time.Types.{TimeContext, TimeObject, _}
import com.xiaomi.duckling.dimension.time.enums.AMPM._
import com.xiaomi.duckling.dimension.time.enums.Grain._
Expand All @@ -35,7 +36,7 @@ package object time {
/**
* Return a tuple of (past, future) elements
*/
type SeriesPredicateF = (TimeObject, TimeContext, Options) => PastFutureTime
type SeriesPredicateF = (TimeObject, TimeContext) => PastFutureTime

implicit class GrainWrapper(grain: Grain) {
def <(that: Grain): Boolean = grain.compareTo(that) < 0
Expand Down Expand Up @@ -81,12 +82,11 @@ package object time {

def resolveTimeData(refTime: TimeObject,
td: TimeData,
reverseTake: Boolean = false,
options: Options): Option[TimeObject] = {
reverseTake: Boolean = false): Option[TimeObject] = {

val tc = refTimeContext(refTime, reverseTake)

val (past, future) = runPredicate(td.timePred)(refTime, tc, options)
val (past, future) = runPredicate(td.timePred)(refTime, tc)

val reverse = if (reverseTake) {
future match {
Expand All @@ -113,17 +113,21 @@ package object time {
// 逻辑比较混乱,待收集到问题再处理
val happened =
td.timePred match {
// 下午3点问“下午”, 12号 04:30 问 12号凌晨,还需要停留在12号
case _: TimeIntervalsPredicate | IntersectTimePredicate(TimeIntervalsPredicate(_, _, _, _), _) =>
val beforeEndOfInterval = td.timePred match {
case TimeIntervalsPredicate(_, _, _, b) => b
case IntersectTimePredicate(TimeIntervalsPredicate(_, _, _, b), _) => b
}
val g = if (td.timeGrain >= Grain.Day) td.timeGrain else Grain.NoGrain
if (!beforeEndOfInterval) timeBefore(ahead, refTime, g)
else endBefore(ahead, refTime, g)
case _: TimeDatePredicate | _: IntersectTimePredicate =>
// 若参考时间是2013/2/12 04:30,在alwaysInFuture情况下
// 1. 过了一部分还需要再出的,12号 => 2/12,2月 => 2013/2
// 2. 问4点,需要给出 16:00
val g = if (td.timeGrain >= Grain.Day) td.timeGrain else Grain.NoGrain
if (options.timeOptions.beforeEndOfInterval) endBefore(ahead, refTime, g)
else timeBefore(ahead, refTime, g)
case TimeIntervalsPredicate(_, _, _, beforeEndOfInterval) =>
val g = if (td.timeGrain >= Grain.Day) td.timeGrain else Grain.NoGrain
if (!beforeEndOfInterval) timeBefore(ahead, refTime, g)
else endBefore(ahead, refTime, g)
timeBefore(ahead, refTime, g)
case _ => false
}
if (happened || td.notImmediate && timeIntersect(ahead)(refTime).nonEmpty) {
Expand All @@ -135,7 +139,7 @@ package object time {
}

val EmptySeries: PastFutureTime = (Stream.empty, Stream.empty)
val EmptySeriesPredicate: SeriesPredicateF = (_: TimeObject, _: TimeContext, _: Options) => EmptySeries
val EmptySeriesPredicate: SeriesPredicateF = (_: TimeObject, _: TimeContext) => EmptySeries

def runPredicate(tp: TimePredicate): SeriesPredicateF = {
tp match {
Expand All @@ -154,9 +158,9 @@ package object time {
year.map(runYearPredicate)
).flatten

def series(t: TimeObject, tc: TimeContext, options: Options): PastFutureTime = {
def series(t: TimeObject, tc: TimeContext): PastFutureTime = {
val pred = toCompose.reduceOption(runCompose).getOrElse(EmptySeriesPredicate)
val (past, future) = pred(t, tc, options)
val (past, future) = pred(t, tc)
(past, future)
}

Expand All @@ -173,7 +177,7 @@ package object time {
}
}

def runEndOfGrainPredicate(t: TimeObject, context: TimeContext, options: Options): PastFutureTime = {
def runEndOfGrainPredicate(t: TimeObject, context: TimeContext): PastFutureTime = {
val (start, grain) = t.grain match {
case Grain.Month =>
(t.start.plusMonths(1).plusDays(-1), Day)
Expand All @@ -187,10 +191,10 @@ package object time {
def runReplacePartPredicate(
td1: TimeData,
td2: TimeData
)(t: TimeObject, context: TimeContext, options: Options): PastFutureTime = {
)(t: TimeObject, context: TimeContext): PastFutureTime = {
(for {
t1 <- resolveTimeData(t, td1, options = options)
t2 <- resolveTimeData(t, td2, options = options)
t1 <- resolveTimeData(t, td1)
t2 <- resolveTimeData(t, td2)
} yield {
val to =
if (td2.timePred.maxGrain.nonEmpty && td1.timeGrain > td2.timePred.maxGrain.get) {
Expand Down Expand Up @@ -256,14 +260,14 @@ package object time {

@scala.annotation.tailrec
def runSequencePredicate(list: List[TimeData])(t: TimeObject,
context: TimeContext, options: Options): PastFutureTime = {
context: TimeContext): PastFutureTime = {
list match {
case Nil => (Stream.empty, Stream(context.refTime))
case td :: xs =>
resolveTimeData(t, td, options = options) match {
resolveTimeData(t, td) match {
case Some(refTime) =>
val tc = refTimeContext(refTime)
runSequencePredicate(xs)(refTime, tc, options)
runSequencePredicate(xs)(refTime, tc)
case None => EmptySeries
}
}
Expand All @@ -273,13 +277,13 @@ package object time {
runCompose(runPredicate(pred1), runPredicate(pred2))
}

def runSecondPredicate(n: Int)(t: TimeObject, context: TimeContext, options: Options): PastFutureTime = {
def runSecondPredicate(n: Int)(t: TimeObject, context: TimeContext): PastFutureTime = {
val s = t.start.second
val anchor = timePlus(timeRound(t, Second), Second, n - s % 60)
timeSequence(Minute, 1, anchor)
}

def runMinutePredicate(n: Int)(t: TimeObject, context: TimeContext, options: Options): PastFutureTime = {
def runMinutePredicate(n: Int)(t: TimeObject, context: TimeContext): PastFutureTime = {
val rounded = timeRound(t, Minute)
val m = t.start.minute
val anchor = timePlus(rounded, Minute, (n - m) % 60)
Expand All @@ -288,7 +292,7 @@ package object time {

def runHourPredicate(
ampm: Option[AMPM]
)(hour: (Boolean, Int))(t: TimeObject, context: TimeContext, options: Options): PastFutureTime = {
)(hour: (Boolean, Int))(t: TimeObject, context: TimeContext): PastFutureTime = {
val (is12H, n) = hour
val step = if (is12H && n <= 12 && ampm.isEmpty) 12 else 24
val nAdjust = ampm match {
Expand All @@ -309,13 +313,13 @@ package object time {
)
}

def runDayOfTheWeekPredicate(n: Int)(t: TimeObject, context: TimeContext, options: Options): PastFutureTime = {
def runDayOfTheWeekPredicate(n: Int)(t: TimeObject, context: TimeContext): PastFutureTime = {
val daysUntilNextWeek = Math.floorMod(n - t.start.dayOfWeek, 7)
val anchor = timePlus(timeRound(t, Day), Day, daysUntilNextWeek)
timeSequence(Day, 7, anchor)
}

def runDayOfTheMonthPredicate(n: Int)(t: TimeObject, context: TimeContext, options: Options): PastFutureTime = {
def runDayOfTheMonthPredicate(n: Int)(t: TimeObject, context: TimeContext): PastFutureTime = {

def enoughDays(t: TimeObject): Boolean = {
n <= t.start.date.lengthOfMonth
Expand All @@ -335,7 +339,7 @@ package object time {
(past, future)
}

def runMonthPredicate(calendar: Option[Calendar])(n: Int)(t: TimeObject, context: TimeContext, options: Options): PastFutureTime = {
def runMonthPredicate(calendar: Option[Calendar])(n: Int)(t: TimeObject, context: TimeContext): PastFutureTime = {
val y = timeRound(t, Year, calendar)
val rounded =
calendar match {
Expand All @@ -348,7 +352,7 @@ package object time {
timeSequence(Year, 1, anchor)
}

def runYearPredicate(n: Int)(t: TimeObject, context: TimeContext, options: Options): PastFutureTime = {
def runYearPredicate(n: Int)(t: TimeObject, context: TimeContext): PastFutureTime = {
val year = n
val tyear = t.start.year
val y = timePlus(timeRound(t, Year), Year, year - tyear)
Expand All @@ -363,14 +367,14 @@ package object time {
* Performs best when pred1 is smaller grain than pred2
*/
def runCompose(pred1: SeriesPredicateF, pred2: SeriesPredicateF): SeriesPredicateF = {
val series = (nowTime: TimeObject, context: TimeContext, options: Options) => {
val (past, future) = pred2(nowTime, context, options)
val series = (nowTime: TimeObject, context: TimeContext) => {
val (past, future) = pred2(nowTime, context)

def startsBefore(t1: TimeObject)(t: TimeObject): Boolean = timeStartsBeforeTheEndOf(t)(t1)

def computeSeries(tokens: Stream[TimeObject]): Stream[TimeObject] = {
tokens.take(safeMax).flatMap { time1 =>
val (past, future) = pred1(time1, fixedTimeContext(time1), options)
val (past, future) = pred1(time1, fixedTimeContext(time1))
val before = future.takeWhile(startsBefore(time1))
before.flatMap(timeIntersect(time1))
}
Expand All @@ -388,8 +392,8 @@ package object time {
pred2: TimePredicate,
beforeEndOfInterval: Boolean): SeriesPredicateF = {
// Pick the first interval *after* the given time segment
def f(thisSegment: TimeObject, ctx: TimeContext, options: Options): Option[TimeObject] = {
runPredicate(pred2)(thisSegment, ctx, options) match {
def f(thisSegment: TimeObject, ctx: TimeContext): Option[TimeObject] = {
runPredicate(pred2)(thisSegment, ctx) match {
case (_, firstFuture #:: tail) =>
// 避免9点-9点,左右一样(空区间)
val end = if (firstFuture != thisSegment || tail.headOption.isEmpty) firstFuture else tail.head
Expand All @@ -398,8 +402,8 @@ package object time {
}
}

def b(thisSegment: TimeObject, ctx: TimeContext, options: Options): Option[TimeObject] = {
runPredicate(pred1)(thisSegment, ctx, options) match {
def b(thisSegment: TimeObject, ctx: TimeContext): Option[TimeObject] = {
runPredicate(pred1)(thisSegment, ctx) match {
case (past, future) =>
val choosed = future.take(safeMax).find(t => timeStartsBeforeTheEndOf(t)(thisSegment))
.orElse(past.take(safeMax).find(t => timeStartsBeforeTheEndOf(t)(thisSegment)))
Expand All @@ -426,15 +430,15 @@ package object time {
* @return Series generator for values that come from `f`
*/
def timeSeqMap(dontReverse: Boolean,
f: (TimeObject, TimeContext, Options) => Option[TimeObject],
f: (TimeObject, TimeContext) => Option[TimeObject],
g: TimePredicate): SeriesPredicateF = {
def seriesF(nowTime: TimeObject, context: TimeContext, options: Options) = {
def seriesF(nowTime: TimeObject, context: TimeContext) = {
// computes a single interval from `f` based on each interval in the series
def applyF(series: Stream[TimeObject]) = {
series.take(safeMaxInterval).flatMap(f(_, context, options))
series.take(safeMaxInterval).flatMap(f(_, context))
}

val (firstPast, firstFuture) = runPredicate(g)(nowTime, context, options)
val (firstPast, firstFuture) = runPredicate(g)(nowTime, context)
val (past1, future1) = (applyF(firstPast), applyF(firstFuture))

// Separate what's before and after now from the past's series
Expand Down Expand Up @@ -467,7 +471,7 @@ package object time {
case _ => false
}

def runTimeOpenIntervalPredicate(it: IntervalDirection)(t: TimeObject, context: TimeContext, options: Options): PastFutureTime = {
def runTimeOpenIntervalPredicate(it: IntervalDirection)(t: TimeObject, context: TimeContext): PastFutureTime = {
(Stream(t.copy(direction = Some(it))), Stream.empty)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,20 @@ import com.xiaomi.duckling.dimension.time.helper.TimeObjectHelpers.{timeIntersec
import com.xiaomi.duckling.dimension.time.Types._
import com.xiaomi.duckling.dimension.time.enums.Grain
import com.xiaomi.duckling.ranking.Testing
import com.xiaomi.duckling.Types.{Options, ZoneCN}
import com.xiaomi.duckling.Types.ZoneCN
import com.xiaomi.duckling.UnitSpec

class TypesTest extends UnitSpec {

describe("TypesTest") {

def round1(refTime: TimeObject, td: TimeData, options: Options): Option[TimeObject] = {
def round1(refTime: TimeObject, td: TimeData): Option[TimeObject] = {
val tc = TimeContext(
refTime = refTime,
maxTime = timePlus(refTime, Grain.Year, 2000),
minTime = timePlus(refTime, Grain.Year, -2000)
)
val (past, future) = runPredicate(td.timePred)(refTime, tc, options)
val (past, future) = runPredicate(td.timePred)(refTime, tc)

val valueOpt = future match {
case Stream.Empty => past.headOption
Expand All @@ -53,14 +53,13 @@ class TypesTest extends UnitSpec {

it("sequence apply demo") {
val refTime = new TimeObject(Testing.testContext.referenceTime, Grain.Second)
val options = Options()
val td1 = cycleNth(Day, 1)

val r1 = round1(refTime, td1, options).get
val r1 = round1(refTime, td1).get
r1.start.dayOfMonth shouldBe 13

val td2 = cycleNth(Day, 2)
val r2 = round1(r1, td2, options).get
val r2 = round1(r1, td2).get
r2.start.dayOfMonth shouldBe 15
}

Expand Down
Loading
Loading