fix(polish): gate test-build style generation at 1,250 chars and cap export at 5,000
Remove the test-mode unlimited personal-style corpus bypass and replace it with a real 1,250-character minimum gate. Production stays at 2,500. Training-corpus export and the diagnostics view now use a 5,000-character upper bound so the user's full private history never leaves the device in a single export. - Add PolishStyleLearningCorpusBuilder.testBuildEffectiveCharacterCount (1,250) and trainingExtractionMaximumCharacterCount (5,000); trainingWindow(from:maximumCharacterCount:) accepts a custom cap. - PolishStylesView: drop bypassesStyleLearningCharacterGate in favor of styleLearningMinimumCharacterCount, unify the ready/remaining text, remove the obsolete polishStyles.learn.testBuildReady key. - PolishStyleCorpusExportStore: pass the 5,000 cap to trainingWindow and expose trainingExtractionMaximumCharacterCount in the export schema. - FlowDiagnosticsSettingsView: show the export-sized (5,000-cap) corpus so the diagnostics match what the export ZIP actually contains. - Tests: rewrite the export cap test for 5,000, add below-cap coverage, add trainingWindow(maximumCharacterCount:) unit coverage, and convert the old UI test into a regression test for the 1,250 test-build minimum. - L10n: drop the testBuildReady string in en + zh-Hans, update diagnostics.corpus.ready to mention 5,000. - CHANGELOG: bilingual entry under [Unreleased].
This commit is contained in:
@@ -46,6 +46,7 @@ final class PolishStyleCorpusExportStoreTests: XCTestCase {
|
||||
XCTAssertEqual(export.appBuild, "94")
|
||||
XCTAssertEqual(export.effectiveCharacterCount, 4)
|
||||
XCTAssertEqual(export.requiredEffectiveCharacterCount, 2_500)
|
||||
XCTAssertEqual(export.trainingExtractionMaximumCharacterCount, 5_000)
|
||||
XCTAssertEqual(export.examples.count, 1)
|
||||
XCTAssertEqual(export.examples[0].prePolishText, "你好 世界")
|
||||
XCTAssertEqual(export.examples[0].finalText, "你好,世界。")
|
||||
@@ -105,7 +106,50 @@ final class PolishStyleCorpusExportStoreTests: XCTestCase {
|
||||
XCTAssertEqual(decoded.examples.count, 1)
|
||||
}
|
||||
|
||||
func testExportUsesNewestCompleteExamplesThroughThreshold() throws {
|
||||
func testExportUsesNewestCompleteExamplesThroughFiveThousandThreshold() throws {
|
||||
// Total available characters: 7,000 — above the export cap.
|
||||
// Newest first: newest (3,000) + middle (2,000) = 5,000 (cap reached,
|
||||
// stop). Oldest is dropped from the export window.
|
||||
let history = SyncedSpeechHistory(
|
||||
entries: [
|
||||
SpeechHistoryEntry(
|
||||
text: "oldest",
|
||||
prePolishText: String(repeating: "旧", count: 2_000),
|
||||
createdAt: Date(timeIntervalSince1970: 1)
|
||||
),
|
||||
SpeechHistoryEntry(
|
||||
text: "middle-complete",
|
||||
prePolishText: String(repeating: "中", count: 2_000),
|
||||
createdAt: Date(timeIntervalSince1970: 2)
|
||||
),
|
||||
SpeechHistoryEntry(
|
||||
text: "newest-complete",
|
||||
prePolishText: String(repeating: "新", count: 3_000),
|
||||
createdAt: Date(timeIntervalSince1970: 3)
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
let export = try XCTUnwrap(
|
||||
PolishStyleCorpusExportStore(
|
||||
directoryURL: temporaryDirectory()
|
||||
).makeExport(from: history)
|
||||
)
|
||||
|
||||
XCTAssertEqual(export.effectiveCharacterCount, 5_000)
|
||||
XCTAssertEqual(export.requiredEffectiveCharacterCount, 2_500)
|
||||
XCTAssertEqual(export.trainingExtractionMaximumCharacterCount, 5_000)
|
||||
XCTAssertEqual(
|
||||
export.examples.map(\.finalText),
|
||||
["middle-complete", "newest-complete"]
|
||||
)
|
||||
XCTAssertEqual(export.examples[0].prePolishText.count, 2_000)
|
||||
XCTAssertEqual(export.examples[1].prePolishText.count, 3_000)
|
||||
}
|
||||
|
||||
func testExportKeepsEveryEligibleExampleWhenUnderFiveThousandThreshold() throws {
|
||||
// 3,600 characters total — below the 5,000 cap, so the export keeps
|
||||
// every eligible example chronologically.
|
||||
let history = SyncedSpeechHistory(
|
||||
entries: [
|
||||
SpeechHistoryEntry(
|
||||
@@ -132,13 +176,11 @@ final class PolishStyleCorpusExportStoreTests: XCTestCase {
|
||||
).makeExport(from: history)
|
||||
)
|
||||
|
||||
XCTAssertEqual(export.effectiveCharacterCount, 2_600)
|
||||
XCTAssertEqual(export.effectiveCharacterCount, 3_600)
|
||||
XCTAssertEqual(
|
||||
export.examples.map(\.finalText),
|
||||
["middle-complete", "newest-complete"]
|
||||
["oldest", "middle-complete", "newest-complete"]
|
||||
)
|
||||
XCTAssertEqual(export.examples[0].prePolishText.count, 1_600)
|
||||
XCTAssertEqual(export.examples[1].prePolishText.count, 1_000)
|
||||
}
|
||||
|
||||
func testEmptyCorpusRemovesPreviousExport() throws {
|
||||
|
||||
@@ -146,6 +146,42 @@ final class PolishStyleLearningServiceTests: XCTestCase {
|
||||
XCTAssertEqual(window.examples.map(\.finalText), ["older", "newer"])
|
||||
}
|
||||
|
||||
func testTrainingWindowHonorsCustomMaximumCharacterCount() {
|
||||
// Total available characters: 7,000. With a 5,000-character
|
||||
// cap (training-corpus export), newest (3,000) + middle (2,000)
|
||||
// fills the window and oldest is dropped.
|
||||
let oldest = PolishStyleLearningExample(
|
||||
prePolishText: String(repeating: "旧", count: 2_000),
|
||||
finalText: "oldest",
|
||||
polishStyleID: nil,
|
||||
createdAt: Date(timeIntervalSince1970: 1)
|
||||
)
|
||||
let middle = PolishStyleLearningExample(
|
||||
prePolishText: String(repeating: "中", count: 2_000),
|
||||
finalText: "middle",
|
||||
polishStyleID: nil,
|
||||
createdAt: Date(timeIntervalSince1970: 2)
|
||||
)
|
||||
let newest = PolishStyleLearningExample(
|
||||
prePolishText: String(repeating: "新", count: 3_000),
|
||||
finalText: "newest",
|
||||
polishStyleID: nil,
|
||||
createdAt: Date(timeIntervalSince1970: 3)
|
||||
)
|
||||
|
||||
let window = PolishStyleLearningCorpusBuilder.trainingWindow(
|
||||
from: [oldest, newest, middle],
|
||||
maximumCharacterCount: PolishStyleLearningCorpusBuilder
|
||||
.trainingExtractionMaximumCharacterCount
|
||||
)
|
||||
|
||||
XCTAssertEqual(window.effectiveCharacterCount, 5_000)
|
||||
XCTAssertEqual(
|
||||
window.examples.map(\.finalText),
|
||||
["middle", "newest"]
|
||||
)
|
||||
}
|
||||
|
||||
func testGenerationRunsExtractorBeforeSynthesizerWithSeparatedPayloads() async throws {
|
||||
var catalog = PolishStyleCatalog()
|
||||
let activeStyle = PolishStylePack(
|
||||
|
||||
Reference in New Issue
Block a user