///|
pub(all) struct AnalysisPlanInput {
run_id : @domain.RunId
operator_request : String
data_source : @domain.DataSource
symbol : @domain.Symbol
timeframe : @domain.Timeframe
limit : Int
captured_at_ms : Int64
suite_status : @model.SuiteStatusRef
} derive(Debug, Eq, ToJson, FromJson)
///|
pub(all) struct AnalysisPlan {
run_id : @domain.RunId
market_request : @data.MarketSourceRequest
market_plan : @data.MarketSourcePlan
refresh_plan : @data.MarketRefreshPlan
gateway : @model.ModelGateway
routine_plan : @routine.RoutinePlan
issues : Array[@domain.ValidationIssue]
can_start : Bool
can_complete_without_external_data : Bool
summary : String
} derive(Debug, Eq, ToJson, FromJson)
///|
pub fn analysis_plan_input(
run_id : @domain.RunId,
operator_request : String,
data_source : @domain.DataSource,
symbol : @domain.Symbol,
timeframe : @domain.Timeframe,
suite_status : @model.SuiteStatusRef,
limit? : Int = 60,
captured_at_ms? : Int64 = 0L,
) -> AnalysisPlanInput {
{
run_id,
operator_request,
data_source,
symbol,
timeframe,
limit,
captured_at_ms,
suite_status,
}
}
///|
fn push_issue(
issues : Array[@domain.ValidationIssue],
severity : @domain.ValidationSeverity,
path : String,
message : String,
) -> Unit {
issues.push(@domain.validation_issue(severity, path, message))
}
///|
fn collect_issues(
input : AnalysisPlanInput,
market_plan : @data.MarketSourcePlan,
refresh_plan : @data.MarketRefreshPlan,
gateway : @model.ModelGateway,
) -> Array[@domain.ValidationIssue] {
let issues : Array[@domain.ValidationIssue] = []
if input.run_id == "" {
push_issue(issues, Error, "run_id", "run ID is required")
}
if input.operator_request == "" {
push_issue(issues, Warning, "operator_request", "operator request is empty")
}
if input.symbol == "" {
push_issue(issues, Error, "symbol", "symbol is required")
}
if input.timeframe == "" {
push_issue(issues, Error, "timeframe", "timeframe is required")
}
if gateway.default_model == "" {
push_issue(issues, Error, "suite_status.default_model", "model is required")
}
if gateway.openai_base_url == "" {
push_issue(
issues,
Error,
"suite_status.openai_base_url",
"OpenAI-compatible base URL is required",
)
}
if gateway.health is Unavailable {
push_issue(
issues,
Error,
"suite_status.ready",
"MoonGate reports the model gateway is unavailable",
)
} else if gateway.health is Degraded {
push_issue(
issues,
Warning,
"suite_status.ready",
"MoonGate reports degraded model gateway health",
)
}
if gateway.usage.quota_exhausted {
push_issue(
issues,
Error,
"suite_status.usage.quota_remaining",
"MoonGate reports provider quota exhausted",
)
}
for contract in gateway.contracts {
if !contract.ready {
push_issue(
issues,
Warning,
"suite_status.contracts.\{contract.name}",
"MoonGate integration contract is not ready at \{contract.path}",
)
}
}
if market_plan.availability is ExternalPlanned {
push_issue(
issues,
Warning,
"data_source",
"external market adapter is planned and will await fetched data",
)
}
if refresh_plan.action is ReviewTimeframe {
push_issue(
issues,
Error,
"timeframe",
"timeframe is unsupported by the market refresh planner",
)
} else if refresh_plan.action is WaitForBarClose {
push_issue(
issues,
Warning,
"market_refresh.wait_seconds",
"newest market bar is still forming; wait for closed-bar evidence",
)
}
issues
}
///|
fn has_errors(issues : Array[@domain.ValidationIssue]) -> Bool {
issues.any(fn(issue) { issue.severity is Error })
}
///|
fn can_complete_without_external_data(
market_plan : @data.MarketSourcePlan,
issues : Array[@domain.ValidationIssue],
) -> Bool {
market_plan.availability is LocalFixture && !has_errors(issues)
}
///|
fn plan_summary(
input : AnalysisPlanInput,
market_plan : @data.MarketSourcePlan,
refresh_plan : @data.MarketRefreshPlan,
gateway : @model.ModelGateway,
can_start : Bool,
can_complete : Bool,
) -> String {
let start_label = if can_start { "ready" } else { "blocked" }
let completion_label = if can_complete {
"complete-offline"
} else {
"requires-external-evidence"
}
"\{start_label} \{completion_label} plan for \{input.symbol} \{input.timeframe} via \{market_plan.adapter} with refresh \{refresh_plan.action.label()} using \{gateway.default_model} from \{gateway.catalog.length()} catalog model(s)"
}
///|
pub fn prepare_analysis_plan(input : AnalysisPlanInput) -> AnalysisPlan {
let market_request = @data.market_source_request(
input.data_source,
input.symbol,
input.timeframe,
limit=input.limit,
captured_at_ms=input.captured_at_ms,
)
let market_plan = @data.plan_market_source(market_request)
let refresh_plan = @data.prepare_market_refresh_plan(
@data.market_refresh_input(market_request),
)
let gateway = input.suite_status.to_gateway()
let routine_plan = @routine.routine_plan(input.run_id)
let issues = collect_issues(input, market_plan, refresh_plan, gateway)
let can_start = !has_errors(issues)
let can_complete = can_complete_without_external_data(market_plan, issues)
{
run_id: input.run_id,
market_request,
market_plan,
refresh_plan,
gateway,
routine_plan,
issues,
can_start,
can_complete_without_external_data: can_complete,
summary: plan_summary(
input, market_plan, refresh_plan, gateway, can_start, can_complete,
),
}
}