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:
@@ -15,6 +15,10 @@ struct PolishStyleCorpusExport: Codable, Equatable {
|
||||
let appBuild: String
|
||||
let effectiveCharacterCount: Int
|
||||
let requiredEffectiveCharacterCount: Int
|
||||
/// Maximum effective characters included in this export. Caps the
|
||||
/// training-corpus window even when the user has accumulated far
|
||||
/// more history than the production 2,500-character unlock gate.
|
||||
let trainingExtractionMaximumCharacterCount: Int
|
||||
let examples: [Example]
|
||||
|
||||
struct Example: Codable, Equatable {
|
||||
@@ -55,7 +59,10 @@ final class PolishStyleCorpusExportStore {
|
||||
let eligibleCorpus = PolishStyleLearningCorpusBuilder.build(from: history)
|
||||
guard !eligibleCorpus.examples.isEmpty else { return nil }
|
||||
let corpus = PolishStyleLearningCorpusBuilder.trainingWindow(
|
||||
from: eligibleCorpus.examples
|
||||
from: eligibleCorpus.examples,
|
||||
maximumCharacterCount:
|
||||
PolishStyleLearningCorpusBuilder
|
||||
.trainingExtractionMaximumCharacterCount
|
||||
)
|
||||
|
||||
let examples = corpus.examples
|
||||
@@ -77,6 +84,9 @@ final class PolishStyleCorpusExportStore {
|
||||
effectiveCharacterCount: corpus.effectiveCharacterCount,
|
||||
requiredEffectiveCharacterCount:
|
||||
PolishStyleLearningCorpusBuilder.requiredEffectiveCharacterCount,
|
||||
trainingExtractionMaximumCharacterCount:
|
||||
PolishStyleLearningCorpusBuilder
|
||||
.trainingExtractionMaximumCharacterCount,
|
||||
examples: examples
|
||||
)
|
||||
}
|
||||
|
||||
@@ -135,7 +135,10 @@ struct FlowDiagnosticsSettingsView: View {
|
||||
from: historyStore.snapshot()
|
||||
)
|
||||
return PolishStyleLearningCorpusBuilder.trainingWindow(
|
||||
from: eligibleCorpus.examples
|
||||
from: eligibleCorpus.examples,
|
||||
maximumCharacterCount:
|
||||
PolishStyleLearningCorpusBuilder
|
||||
.trainingExtractionMaximumCharacterCount
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,8 @@ struct PolishStylesView: View {
|
||||
outputLanguage: language,
|
||||
minimumEffectiveCharacterCount:
|
||||
AppDistributionChannel.allowsInternalTools
|
||||
? 0
|
||||
? PolishStyleLearningCorpusBuilder
|
||||
.testBuildEffectiveCharacterCount
|
||||
: PolishStyleLearningCorpusBuilder
|
||||
.requiredEffectiveCharacterCount
|
||||
)
|
||||
@@ -158,17 +159,20 @@ struct PolishStylesView: View {
|
||||
PolishStyleLearningCorpusBuilder.build(from: history.snapshot())
|
||||
}
|
||||
|
||||
/// Debug and TestFlight builds may exercise the complete generation
|
||||
/// pipeline before enough personal corpus exists. App Store builds keep
|
||||
/// the production 2,500-character gate.
|
||||
private var bypassesStyleLearningCharacterGate: Bool {
|
||||
/// Minimum effective character count required to unlock personal style
|
||||
/// generation. Debug + TestFlight builds use a lower 1,250-character
|
||||
/// gate so internal testers can still exercise the pipeline; the
|
||||
/// previous 0 / "unlimited" bypass has been removed.
|
||||
private var styleLearningMinimumCharacterCount: Int {
|
||||
AppDistributionChannel.allowsInternalTools
|
||||
? PolishStyleLearningCorpusBuilder.testBuildEffectiveCharacterCount
|
||||
: PolishStyleLearningCorpusBuilder.requiredEffectiveCharacterCount
|
||||
}
|
||||
|
||||
private func isEligibleForStyleGeneration(
|
||||
_ corpus: PolishStyleLearningCorpus
|
||||
) -> Bool {
|
||||
bypassesStyleLearningCharacterGate || corpus.isReady
|
||||
corpus.effectiveCharacterCount >= styleLearningMinimumCharacterCount
|
||||
}
|
||||
|
||||
private var learnedStylePack: PolishStylePack? {
|
||||
@@ -185,7 +189,7 @@ struct PolishStylesView: View {
|
||||
|
||||
private var styleLearningCard: some View {
|
||||
let corpus = styleLearningCorpus
|
||||
let required = PolishStyleLearningCorpusBuilder.requiredEffectiveCharacterCount
|
||||
let required = styleLearningMinimumCharacterCount
|
||||
let reachedLimit = catalog.entries.count >= PolishStyleLimits.maximumUserPacks
|
||||
let isActionAvailable = isEligibleForStyleGeneration(corpus) && !reachedLimit
|
||||
let canGenerate = isActionAvailable && !isGeneratingLearnedStyle
|
||||
@@ -240,13 +244,17 @@ struct PolishStylesView: View {
|
||||
Spacer()
|
||||
|
||||
Text(
|
||||
bypassesStyleLearningCharacterGate && !corpus.isReady
|
||||
? AppL10n.string("polishStyles.learn.testBuildReady")
|
||||
: corpus.isReady
|
||||
isEligibleForStyleGeneration(corpus)
|
||||
? AppL10n.string("polishStyles.learn.ready")
|
||||
: AppL10n.format(
|
||||
"polishStyles.learn.remaining",
|
||||
Int64(corpus.remainingCharacterCount)
|
||||
Int64(
|
||||
max(
|
||||
0,
|
||||
styleLearningMinimumCharacterCount
|
||||
- corpus.effectiveCharacterCount
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
.font(TypeStyle.caption2)
|
||||
|
||||
@@ -134,7 +134,8 @@ private struct PolishStylesServiceUITestHarness: View {
|
||||
outputLanguage: language,
|
||||
minimumEffectiveCharacterCount:
|
||||
AppDistributionChannel.allowsInternalTools
|
||||
? 0
|
||||
? PolishStyleLearningCorpusBuilder
|
||||
.testBuildEffectiveCharacterCount
|
||||
: PolishStyleLearningCorpusBuilder
|
||||
.requiredEffectiveCharacterCount
|
||||
)
|
||||
|
||||
@@ -128,6 +128,7 @@
|
||||
"common.continue" = "Continue";
|
||||
"common.reset" = "Reset";
|
||||
"common.cancel" = "Cancel";
|
||||
"common.dismiss" = "Dismiss";
|
||||
"common.save" = "Save";
|
||||
"common.clear" = "Clear";
|
||||
"common.delete" = "Delete";
|
||||
@@ -294,7 +295,7 @@
|
||||
"settings.diagnostics.corpus.section" = "Personal Style Training Corpus";
|
||||
"settings.diagnostics.corpus.count" = "%d eligible samples · %d effective characters";
|
||||
"settings.diagnostics.corpus.empty" = "No eligible paired dictations are available.";
|
||||
"settings.diagnostics.corpus.ready" = "Newest complete samples through the 2,500-character target.";
|
||||
"settings.diagnostics.corpus.ready" = "Newest complete samples through the 5,000-character export cap.";
|
||||
"settings.diagnostics.corpus.export" = "Export Training Corpus";
|
||||
"settings.diagnostics.corpus.privacy" = "Contains dictated text and prior polish prompts. Nothing is uploaded automatically. Share only with trusted devices or services.";
|
||||
"settings.diagnostics.privacy.section" = "Privacy";
|
||||
@@ -683,7 +684,6 @@
|
||||
"polishStyles.learn.progress" = "%lld / %lld characters";
|
||||
"polishStyles.learn.remaining" = "%lld to go";
|
||||
"polishStyles.learn.ready" = "Ready";
|
||||
"polishStyles.learn.testBuildReady" = "Test build: 2,500-character limit disabled";
|
||||
"polishStyles.learn.action" = "Generate Style";
|
||||
"polishStyles.learn.generating" = "Generating…";
|
||||
"polishStyles.learn.generated.description" = "Your personal style learned from reviewed evidence.";
|
||||
@@ -959,6 +959,8 @@
|
||||
"account.referral.status.rewarded" = "Rewarded";
|
||||
"account.referral.status.ineligible" = "Ineligible";
|
||||
"account.referral.pendingAfterSignIn" = "Your invitation is saved and will be redeemed after sign-in.";
|
||||
"account.referral.rewardGranted" = "You received %@ credits from the invitation.";
|
||||
"account.referral.rewardGrantedGeneric" = "Your invitation reward has been added to your balance.";
|
||||
"account.security.section" = "Account controls";
|
||||
"account.signOut.action" = "Sign Out";
|
||||
"account.signOut.confirmTitle" = "Sign out?";
|
||||
|
||||
@@ -128,6 +128,7 @@
|
||||
"common.continue" = "继续";
|
||||
"common.reset" = "重置";
|
||||
"common.cancel" = "取消";
|
||||
"common.dismiss" = "关闭";
|
||||
"common.save" = "保存";
|
||||
"common.clear" = "清空";
|
||||
"common.delete" = "删除";
|
||||
@@ -294,7 +295,7 @@
|
||||
"settings.diagnostics.corpus.section" = "个人风格训练语料";
|
||||
"settings.diagnostics.corpus.count" = "%d 条可用样本 · %d 个有效字符";
|
||||
"settings.diagnostics.corpus.empty" = "暂无符合条件的成对听写语料。";
|
||||
"settings.diagnostics.corpus.ready" = "选取最新完整样本,累积至至少 2,500 个有效字符。";
|
||||
"settings.diagnostics.corpus.ready" = "选取最新完整样本,导出时最多累积 5,000 个有效字符。";
|
||||
"settings.diagnostics.corpus.export" = "导出训练语料";
|
||||
"settings.diagnostics.corpus.privacy" = "文件包含口述文本和历史润色提示词,不会自动上传。请仅分享给可信设备或服务。";
|
||||
"settings.diagnostics.privacy.section" = "隐私";
|
||||
@@ -682,7 +683,6 @@
|
||||
"polishStyles.learn.progress" = "%lld / %lld 字";
|
||||
"polishStyles.learn.remaining" = "还差 %lld 字";
|
||||
"polishStyles.learn.ready" = "可以生成";
|
||||
"polishStyles.learn.testBuildReady" = "测试版本:已暂时取消 2500 字限制";
|
||||
"polishStyles.learn.action" = "生成风格";
|
||||
"polishStyles.learn.generating" = "生成中…";
|
||||
"polishStyles.learn.generated.description" = "根据经过确认的证据学习得到的个人表达风格。";
|
||||
@@ -958,6 +958,8 @@
|
||||
"account.referral.status.rewarded" = "已奖励";
|
||||
"account.referral.status.ineligible" = "不符合条件";
|
||||
"account.referral.pendingAfterSignIn" = "邀请已暂存,登录后会自动兑换。";
|
||||
"account.referral.rewardGranted" = "恭喜获得 %@ 积分";
|
||||
"account.referral.rewardGrantedGeneric" = "邀请奖励已发放,积分已到账。";
|
||||
"account.security.section" = "账号操作";
|
||||
"account.signOut.action" = "退出登录";
|
||||
"account.signOut.confirmTitle" = "退出登录?";
|
||||
|
||||
Reference in New Issue
Block a user