diff --git a/autotests/dfm-search-tests/tst_chinese_nlp.cpp b/autotests/dfm-search-tests/tst_chinese_nlp.cpp index 321b1b79..2971e72d 100644 --- a/autotests/dfm-search-tests/tst_chinese_nlp.cpp +++ b/autotests/dfm-search-tests/tst_chinese_nlp.cpp @@ -204,6 +204,7 @@ private Q_SLOTS: void size_suffix_max(); void size_suffix_combined(); void size_suffix_chineseUnits(); + void size_doesNotShadow_yearMonth(); // Relative time tests void timeRelative_justNow(); @@ -971,6 +972,22 @@ void tst_ChineseNLP::timeCustom_yearMonth() QCOMPARE(intent.timeConstraint().customStart().date().day(), 1); QCOMPARE(intent.timeConstraint().customEnd().date().year(), 2025); QCOMPARE(intent.timeConstraint().customEnd().date().month(), 12); + + // "2025年12" — trailing 月 is optional + ParsedIntent intent2; + m_parser->parse(QStringLiteral("2025年12创建的文件"), intent2); + QCOMPARE(intent2.timeConstraint().kind(), TimeConstraintKind::Custom); + QCOMPARE(intent2.timeConstraint().customStart().date().year(), 2025); + QCOMPARE(intent2.timeConstraint().customStart().date().month(), 12); + QCOMPARE(intent2.timeConstraint().customStart().date().day(), 1); + + // "2025年12月份" — support 月份 suffix too + ParsedIntent intent3; + m_parser->parse(QStringLiteral("2025年12月份创建的文件"), intent3); + QCOMPARE(intent3.timeConstraint().kind(), TimeConstraintKind::Custom); + QCOMPARE(intent3.timeConstraint().customStart().date().year(), 2025); + QCOMPARE(intent3.timeConstraint().customStart().date().month(), 12); + QCOMPARE(intent3.timeConstraint().customStart().date().day(), 1); } void tst_ChineseNLP::timeCustom_yearMonth_separators() @@ -1035,6 +1052,14 @@ void tst_ChineseNLP::timeCustom_fullDate() // Verify time boundaries QCOMPARE(intent.timeConstraint().customStart().time().hour(), 0); QCOMPARE(intent.timeConstraint().customEnd().time().hour(), 23); + + // "2025年12月30" — trailing 日号 is optional + ParsedIntent intent2; + m_parser->parse(QStringLiteral("2025年12月30创建的文件"), intent2); + QCOMPARE(intent2.timeConstraint().kind(), TimeConstraintKind::Custom); + QCOMPARE(intent2.timeConstraint().customStart().date().year(), 2025); + QCOMPARE(intent2.timeConstraint().customStart().date().month(), 12); + QCOMPARE(intent2.timeConstraint().customStart().date().day(), 30); } void tst_ChineseNLP::timeCustom_fullDate_separators() @@ -1579,6 +1604,35 @@ void tst_ChineseNLP::size_suffix_chineseUnits() QCOMPARE(intent2.sizeConstraint().maxSize(), 51200LL); // 50KB } +void tst_ChineseNLP::size_doesNotShadow_yearMonth() +{ + ParsedIntent intent; + m_parser->parse(QStringLiteral("2025-5的音乐"), intent); + + QCOMPARE(intent.timeConstraint().kind(), TimeConstraintKind::Custom); + QCOMPARE(intent.timeConstraint().customStart().date().year(), 2025); + QCOMPARE(intent.timeConstraint().customStart().date().month(), 5); + QVERIFY(!intent.sizeConstraint().isValid()); + QVERIFY(setEquals(intent.fileExtensions(), audioExpectedExts())); + + bool hasTimeYearMonth = false; + bool hasAudio = false; + bool hasSizeBetween = false; + for (const MatchSpan &span : intent.consumedSpans()) { + if (span.ruleId() == QLatin1String("time_exact_year_month")) { + hasTimeYearMonth = true; + } else if (span.ruleId() == QLatin1String("filetype_audio")) { + hasAudio = true; + } else if (span.ruleId() == QLatin1String("size_dynamic_between")) { + hasSizeBetween = true; + } + } + + QVERIFY(hasTimeYearMonth); + QVERIFY(hasAudio); + QVERIFY(!hasSizeBetween); +} + // ===== Relative Time Tests ===== void tst_ChineseNLP::timeRelative_justNow() diff --git a/autotests/dfm-search-tests/tst_semantic_search.cpp b/autotests/dfm-search-tests/tst_semantic_search.cpp index b42f76ec..737a51e4 100644 --- a/autotests/dfm-search-tests/tst_semantic_search.cpp +++ b/autotests/dfm-search-tests/tst_semantic_search.cpp @@ -306,15 +306,35 @@ void tst_TimeExtraction::customYearMonth() meta["type"] = "custom"; meta["format"] = "year_month"; QByteArray json = makeRuleJson("time", "time_exact_year_month", - "(?\\d{4})-(?\\d{1,2})", 150, meta); + "(?\\d{2,4})[年\\./\\-](?\\d{1,2})(?:月份?)?", 150, meta); RuleGroup group; QVERIFY(buildGroupFromJson(json, group)); - auto match = group.rules[0].regex.match("2025-12"); - QVERIFY(match.hasMatch()); - QCOMPARE(match.captured("year"), QString("2025")); - QCOMPARE(match.captured("month"), QString("12")); + auto match1 = group.rules[0].regex.match("2025年12月"); + QVERIFY(match1.hasMatch()); + QCOMPARE(match1.captured("year"), QString("2025")); + QCOMPARE(match1.captured("month"), QString("12")); + + auto match2 = group.rules[0].regex.match("2025年12"); + QVERIFY(match2.hasMatch()); + QCOMPARE(match2.captured("year"), QString("2025")); + QCOMPARE(match2.captured("month"), QString("12")); + + auto match3 = group.rules[0].regex.match("2025年12月份"); + QVERIFY(match3.hasMatch()); + QCOMPARE(match3.captured("year"), QString("2025")); + QCOMPARE(match3.captured("month"), QString("12")); + + auto match4 = group.rules[0].regex.match("2025-12"); + QVERIFY(match4.hasMatch()); + QCOMPARE(match4.captured("year"), QString("2025")); + QCOMPARE(match4.captured("month"), QString("12")); + + auto match5 = group.rules[0].regex.match("25.12"); + QVERIFY(match5.hasMatch()); + QCOMPARE(match5.captured("year"), QString("25")); + QCOMPARE(match5.captured("month"), QString("12")); } void tst_TimeExtraction::customFullDate() @@ -323,17 +343,35 @@ void tst_TimeExtraction::customFullDate() meta["type"] = "custom"; meta["format"] = "full_date"; QByteArray json = makeRuleJson("time", "time_exact_full_date", - "(?\\d{4})-(?\\d{1,2})-(?\\d{1,2})", + "(?\\d{2,4})[年\\./\\-](?\\d{1,2})[月\\./\\-](?\\d{1,2})[日号]?", 140, meta); RuleGroup group; QVERIFY(buildGroupFromJson(json, group)); - auto match = group.rules[0].regex.match("2025-03-15"); - QVERIFY(match.hasMatch()); - QCOMPARE(match.captured("year"), QString("2025")); - QCOMPARE(match.captured("month"), QString("03")); - QCOMPARE(match.captured("day"), QString("15")); + auto match1 = group.rules[0].regex.match("2025年12月5日"); + QVERIFY(match1.hasMatch()); + QCOMPARE(match1.captured("year"), QString("2025")); + QCOMPARE(match1.captured("month"), QString("12")); + QCOMPARE(match1.captured("day"), QString("5")); + + auto match2 = group.rules[0].regex.match("2025年12月5"); + QVERIFY(match2.hasMatch()); + QCOMPARE(match2.captured("year"), QString("2025")); + QCOMPARE(match2.captured("month"), QString("12")); + QCOMPARE(match2.captured("day"), QString("5")); + + auto match3 = group.rules[0].regex.match("2025-03-15"); + QVERIFY(match3.hasMatch()); + QCOMPARE(match3.captured("year"), QString("2025")); + QCOMPARE(match3.captured("month"), QString("03")); + QCOMPARE(match3.captured("day"), QString("15")); + + auto match4 = group.rules[0].regex.match("2025/12/5"); + QVERIFY(match4.hasMatch()); + QCOMPARE(match4.captured("year"), QString("2025")); + QCOMPARE(match4.captured("month"), QString("12")); + QCOMPARE(match4.captured("day"), QString("5")); } void tst_TimeExtraction::noMatch() diff --git a/src/dfm-search/dfm-search-lib/semantic/extractors/sizeextractor.cpp b/src/dfm-search/dfm-search-lib/semantic/extractors/sizeextractor.cpp index fd0a70ec..4f0bda5b 100644 --- a/src/dfm-search/dfm-search-lib/semantic/extractors/sizeextractor.cpp +++ b/src/dfm-search/dfm-search-lib/semantic/extractors/sizeextractor.cpp @@ -29,6 +29,17 @@ void SizeExtractor::extract(const QString &input, ParsedIntent &intent) return; } + // Size extraction runs after time/filetype. If this size span is already + // fully consumed by an earlier, higher-confidence dimension such as a + // year-month time expression ("2025-5"), ignore it to avoid overwriting + // the intent with a bogus size constraint. + for (const MatchSpan &existing : intent.consumedSpans()) { + if (match.capturedStart() >= existing.start() + && match.capturedEnd() <= existing.end()) { + return; + } + } + const QVariantMap metadata = m_engine->ruleMetadata("size", ruleId); const QString typeStr = metadata.value("type").toString(); SizeConstraint sc; diff --git a/src/dfm-search/dfm-search-lib/semantic/rules/zh_CN/time_rules.json b/src/dfm-search/dfm-search-lib/semantic/rules/zh_CN/time_rules.json index df4086be..31e13dec 100644 --- a/src/dfm-search/dfm-search-lib/semantic/rules/zh_CN/time_rules.json +++ b/src/dfm-search/dfm-search-lib/semantic/rules/zh_CN/time_rules.json @@ -157,7 +157,7 @@ }, { "id": "time_exact_year_month", - "pattern": "(?\\d{2,4})[年\\./\\-](?\\d{1,2})月份?", + "pattern": "(?\\d{2,4})[年\\./\\-](?\\d{1,2})(?:月份?)?", "description": "Exact year-month (e.g. 2025年12月, 2025-12)", "enabled": true, "priority": 140, @@ -337,4 +337,4 @@ ] } ] -} \ No newline at end of file +}