///|
pub(all) enum LegacySettingDisposition {
Migrated
NeedsReview
IgnoredUiOnly
} derive(Debug, Eq, ToJson, FromJson)
///|
pub(all) struct LegacySettingsRef {
source_path : String
provider_model : String
provider_base_url : String
provider_api_key_present : Bool
data_source_label : String
symbol : @domain.Symbol
timeframe : @domain.Timeframe
analysis_bar_count : Int
retry_max : Int
retry_max_semantic : Int
retry_stage2 : Bool
} derive(Debug, Eq, ToJson, FromJson)
///|
pub(all) struct MigratedSetting {
source_path : String
target_path : String
disposition : LegacySettingDisposition
message : String
} derive(Debug, Eq, ToJson, FromJson)
///|
pub(all) struct SuiteSettingsPlan {
source_path : String
suite_status : @model.SuiteStatusRef
market_request : @data.MarketSourceRequest
retry_policy : @job.RetryPolicy
provider_snapshot_path : String
provider_secret_redacted : Bool
settings : Array[MigratedSetting]
review_count : Int
summary : String
} derive(Debug, Eq, ToJson, FromJson)
///|
pub fn legacy_settings_ref(
source_path : String,
provider_model : String,
provider_base_url : String,
data_source_label : String,
symbol : @domain.Symbol,
timeframe : @domain.Timeframe,
provider_api_key_present? : Bool = false,
analysis_bar_count? : Int = 100,
retry_max? : Int = 3,
retry_max_semantic? : Int = 1,
retry_stage2? : Bool = true,
) -> LegacySettingsRef {
{
source_path,
provider_model,
provider_base_url,
provider_api_key_present,
data_source_label,
symbol,
timeframe,
analysis_bar_count,
retry_max,
retry_max_semantic,
retry_stage2,
}
}
///|
fn source_to_data_source(
label : String,
) -> (@domain.DataSource, LegacySettingDisposition, String) {
match label {
"tradingview" => (TradingView, Migrated, "mapped to TradingView adapter")
"mt5" => (Mt5, Migrated, "mapped to MT5 adapter")
"yfinance" => (Yfinance, Migrated, "mapped to yfinance adapter")
"akshare" => (AkShare, Migrated, "mapped to AkShare adapter")
"fixture" => (Fixture, Migrated, "mapped to local fixture adapter")
"eastmoney" =>
(
AkShare,
NeedsReview,
"eastmoney is not a standalone Moonfish adapter yet; review AkShare mapping",
)
"tushare" =>
(
AkShare,
NeedsReview,
"tushare token settings stay outside Moonfish core; review AkShare mapping",
)
_ => (Fixture, NeedsReview, "unknown data source; defaulted to fixture")
}
}
///|
fn clamp_retry(value : Int, max : Int) -> Int {
if value < 0 {
0
} else if value > max {
max
} else {
value
}
}
///|
fn normalized_limit(value : Int) -> Int {
if value < 1 {
1
} else {
value
}
}
///|
fn migrated_setting(
source_path : String,
target_path : String,
disposition : LegacySettingDisposition,
message : String,
) -> MigratedSetting {
{ source_path, target_path, disposition, message }
}
///|
fn count_review(items : Array[MigratedSetting]) -> Int {
items.fold(init=0, fn(count, item) {
if item.disposition is NeedsReview {
count + 1
} else {
count
}
})
}
///|
pub fn prepare_suite_settings_plan(
legacy : LegacySettingsRef,
status_path? : String = @model.moonsuite_suite_status_path(),
) -> SuiteSettingsPlan {
let (data_source, data_disposition, data_message) = source_to_data_source(
legacy.data_source_label,
)
let suite_status = @model.suite_status_ref(
status_path,
legacy.provider_base_url,
legacy.provider_model,
ready=legacy.provider_base_url != "" && legacy.provider_model != "",
)
let market_request = @data.market_source_request(
data_source,
legacy.symbol,
legacy.timeframe,
limit=normalized_limit(legacy.analysis_bar_count),
)
let retry_policy = @job.default_retry_policy(
max_attempts=clamp_retry(legacy.retry_max, 5),
semantic_max_attempts=clamp_retry(legacy.retry_max_semantic, 3),
retry_stage2=legacy.retry_stage2,
)
let settings = [
migrated_setting(
"provider.model",
"suite-status.defaultModel",
if legacy.provider_model == "" {
NeedsReview
} else {
Migrated
},
"model selection moves to MoonGate suite status",
),
migrated_setting(
"provider.base_url",
"suite-status.openaiBaseUrl",
if legacy.provider_base_url == "" {
NeedsReview
} else {
Migrated
},
"provider URL moves to MoonGate OpenAI-compatible gateway",
),
migrated_setting(
"provider.api_key",
"moongate-secret-store",
Migrated,
if legacy.provider_api_key_present {
"API key is not copied into Moonfish records; MoonGate owns secret storage"
} else {
"no plaintext API key present in legacy settings"
},
),
migrated_setting(
"general.last_data_source", "market-source-request.data_source", data_disposition,
data_message,
),
migrated_setting(
"general.last_symbol",
"market-source-request.symbol",
if legacy.symbol == "" {
NeedsReview
} else {
Migrated
},
"operator symbol becomes a typed market request field",
),
migrated_setting(
"general.last_timeframe",
"market-source-request.timeframe",
if legacy.timeframe == "" {
NeedsReview
} else {
Migrated
},
"operator timeframe becomes a typed market request field",
),
migrated_setting(
"validation.retry_*",
"retry-policy",
Migrated,
"validation retry settings become a bounded MoonClaw retry policy",
),
migrated_setting(
"general.decision_flow_*",
"moondesk-app-settings",
IgnoredUiOnly,
"legacy PyQt playback and zoom settings stay outside Moonfish core",
),
]
let review_count = count_review(settings)
{
source_path: legacy.source_path,
suite_status,
market_request,
retry_policy,
provider_snapshot_path: "records/settings/provider-snapshot.json",
provider_secret_redacted: true,
settings,
review_count,
summary: if review_count == 0 {
"suite settings migration ready for \{legacy.symbol} \{legacy.timeframe}"
} else {
"suite settings migration has \{review_count} item(s) needing review"
},
}
}