同步店铺、全局列表展示
This commit is contained in:
parent
52cb6b74d5
commit
e844debfaa
@ -34,25 +34,7 @@
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20" v-if="form.model_type">
|
||||
<el-col :span="5">
|
||||
<el-form-item label="模型版本">
|
||||
<el-select
|
||||
v-model="form.version"
|
||||
placeholder="选择版本"
|
||||
style="width: 100%"
|
||||
:disabled="!availableVersions.length"
|
||||
:loading="versionsLoading"
|
||||
>
|
||||
<el-option
|
||||
v-for="version in availableVersions"
|
||||
:key="version"
|
||||
:label="version"
|
||||
:value="version"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="预测天数">
|
||||
<el-input-number
|
||||
v-model="form.future_days"
|
||||
@ -62,7 +44,7 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="历史天数">
|
||||
<el-input-number
|
||||
v-model="form.history_lookback_days"
|
||||
@ -72,7 +54,7 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="起始日期">
|
||||
<el-date-picker
|
||||
v-model="form.start_date"
|
||||
@ -85,7 +67,7 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="预测分析">
|
||||
<el-switch
|
||||
v-model="form.analyze_result"
|
||||
@ -98,17 +80,49 @@
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div class="prediction-actions">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="large"
|
||||
@click="startPrediction"
|
||||
:loading="predicting"
|
||||
:disabled="!canPredict"
|
||||
>
|
||||
<el-icon><TrendCharts /></el-icon>
|
||||
开始预测
|
||||
</el-button>
|
||||
<div v-if="availableVersions.length > 0" class="versions-list-section">
|
||||
<h4>📦 可用模型版本</h4>
|
||||
<el-table :data="availableVersions" style="width: 100%" v-loading="versionsLoading">
|
||||
<el-table-column prop="aggregation_method" label="聚合方法" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ row.aggregation_method || 'N/A' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="version" label="版本号" width="180"></el-table-column>
|
||||
<el-table-column prop="created_at" label="创建时间" width="200">
|
||||
<template #default="{ row }">
|
||||
{{ new Date(row.created_at).toLocaleString() }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="metrics.mae" label="MAE">
|
||||
<template #default="{ row }">
|
||||
{{ row.metrics && row.metrics.mae ? row.metrics.mae.toFixed(4) : 'N/A' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="metrics.rmse" label="RMSE">
|
||||
<template #default="{ row }">
|
||||
{{ row.metrics && row.metrics.rmse ? row.metrics.rmse.toFixed(4) : 'N/A' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="metrics.mape" label="MAPE (%)">
|
||||
<template #default="{ row }">
|
||||
{{ row.metrics && row.metrics.mape ? (row.metrics.mape * 100).toFixed(2) : 'N/A' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="150">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="startPrediction(row.version)"
|
||||
:loading="predicting && form.version === row.version"
|
||||
>
|
||||
<el-icon><TrendCharts /></el-icon>
|
||||
开始预测
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
@ -150,9 +164,6 @@ const form = reactive({
|
||||
analyze_result: true
|
||||
})
|
||||
|
||||
const canPredict = computed(() => {
|
||||
return form.model_type && form.version
|
||||
})
|
||||
|
||||
const fetchModelTypes = async () => {
|
||||
try {
|
||||
@ -176,9 +187,7 @@ const fetchAvailableVersions = async () => {
|
||||
const response = await axios.get(url)
|
||||
if (response.data.status === 'success') {
|
||||
availableVersions.value = response.data.data.versions || []
|
||||
if (response.data.data.latest_version) {
|
||||
form.version = response.data.data.latest_version
|
||||
}
|
||||
// No longer setting a default version, the user will choose from the list.
|
||||
}
|
||||
} catch (error) {
|
||||
availableVersions.value = []
|
||||
@ -189,16 +198,19 @@ const fetchAvailableVersions = async () => {
|
||||
|
||||
const handleModelTypeChange = () => {
|
||||
form.version = ''
|
||||
availableVersions.value = []
|
||||
predictionResult.value = null;
|
||||
fetchAvailableVersions()
|
||||
}
|
||||
|
||||
const startPrediction = async () => {
|
||||
const startPrediction = async (version) => {
|
||||
form.version = version; // Keep track of which version is running
|
||||
try {
|
||||
predicting.value = true
|
||||
const payload = {
|
||||
training_mode: 'global', // 明确指定训练模式
|
||||
model_type: form.model_type,
|
||||
version: form.version,
|
||||
version: version,
|
||||
future_days: form.future_days,
|
||||
history_lookback_days: form.history_lookback_days,
|
||||
start_date: form.start_date,
|
||||
@ -361,13 +373,14 @@ watch(() => form.model_type, () => {
|
||||
.model-selection-section h4 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.prediction-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
.versions-list-section {
|
||||
margin-top: 20px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
}
|
||||
.versions-list-section h4 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.prediction-chart {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
@ -44,25 +44,7 @@
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20" v-if="form.model_type">
|
||||
<el-col :span="5">
|
||||
<el-form-item label="模型版本">
|
||||
<el-select
|
||||
v-model="form.version"
|
||||
placeholder="选择版本"
|
||||
style="width: 100%"
|
||||
:disabled="!availableVersions.length"
|
||||
:loading="versionsLoading"
|
||||
>
|
||||
<el-option
|
||||
v-for="version in availableVersions"
|
||||
:key="version"
|
||||
:label="version"
|
||||
:value="version"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="预测天数">
|
||||
<el-input-number
|
||||
v-model="form.future_days"
|
||||
@ -72,7 +54,7 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="历史天数">
|
||||
<el-input-number
|
||||
v-model="form.history_lookback_days"
|
||||
@ -82,7 +64,7 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="起始日期">
|
||||
<el-date-picker
|
||||
v-model="form.start_date"
|
||||
@ -95,7 +77,7 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="预测分析">
|
||||
<el-switch
|
||||
v-model="form.analyze_result"
|
||||
@ -108,17 +90,45 @@
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div class="prediction-actions">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="large"
|
||||
@click="startPrediction"
|
||||
:loading="predicting"
|
||||
:disabled="!canPredict"
|
||||
>
|
||||
<el-icon><TrendCharts /></el-icon>
|
||||
开始预测
|
||||
</el-button>
|
||||
<div v-if="availableVersions.length > 0" class="versions-list-section">
|
||||
<h4>📦 可用模型版本</h4>
|
||||
<el-table :data="availableVersions" style="width: 100%" v-loading="versionsLoading">
|
||||
<el-table-column prop="store_name" label="店铺名称" width="220"></el-table-column>
|
||||
<el-table-column prop="version" label="版本号" width="180"></el-table-column>
|
||||
<el-table-column prop="created_at" label="创建时间" width="200">
|
||||
<template #default="{ row }">
|
||||
{{ new Date(row.created_at).toLocaleString() }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="metrics.mae" label="MAE">
|
||||
<template #default="{ row }">
|
||||
{{ row.metrics && row.metrics.mae ? row.metrics.mae.toFixed(4) : 'N/A' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="metrics.rmse" label="RMSE">
|
||||
<template #default="{ row }">
|
||||
{{ row.metrics && row.metrics.rmse ? row.metrics.rmse.toFixed(4) : 'N/A' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="metrics.mape" label="MAPE (%)">
|
||||
<template #default="{ row }">
|
||||
{{ row.metrics && row.metrics.mape ? (row.metrics.mape * 100).toFixed(2) : 'N/A' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="150">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="startPrediction(row.version)"
|
||||
:loading="predicting && form.version === row.version"
|
||||
>
|
||||
<el-icon><TrendCharts /></el-icon>
|
||||
开始预测
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
@ -162,9 +172,6 @@ const form = reactive({
|
||||
analyze_result: true
|
||||
})
|
||||
|
||||
const canPredict = computed(() => {
|
||||
return form.store_id && form.model_type && form.version
|
||||
})
|
||||
|
||||
const fetchModelTypes = async () => {
|
||||
try {
|
||||
@ -188,9 +195,7 @@ const fetchAvailableVersions = async () => {
|
||||
const response = await axios.get(url)
|
||||
if (response.data.status === 'success') {
|
||||
availableVersions.value = response.data.data.versions || []
|
||||
if (response.data.data.latest_version) {
|
||||
form.version = response.data.data.latest_version
|
||||
}
|
||||
// No longer setting a default version, the user will choose from the list.
|
||||
}
|
||||
} catch (error) {
|
||||
availableVersions.value = []
|
||||
@ -203,21 +208,25 @@ const handleStoreChange = () => {
|
||||
form.model_type = ''
|
||||
form.version = ''
|
||||
availableVersions.value = []
|
||||
predictionResult.value = null;
|
||||
}
|
||||
|
||||
const handleModelTypeChange = () => {
|
||||
form.version = ''
|
||||
availableVersions.value = []
|
||||
predictionResult.value = null;
|
||||
fetchAvailableVersions()
|
||||
}
|
||||
|
||||
const startPrediction = async () => {
|
||||
const startPrediction = async (version) => {
|
||||
form.version = version; // Keep track of which version is running
|
||||
try {
|
||||
predicting.value = true
|
||||
const payload = {
|
||||
training_mode: form.training_mode,
|
||||
store_id: form.store_id,
|
||||
model_type: form.model_type,
|
||||
version: form.version,
|
||||
version: version,
|
||||
future_days: form.future_days,
|
||||
history_lookback_days: form.history_lookback_days,
|
||||
start_date: form.start_date,
|
||||
@ -380,13 +389,14 @@ watch([() => form.store_id, () => form.model_type], () => {
|
||||
.model-selection-section h4 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.prediction-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
.versions-list-section {
|
||||
margin-top: 20px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
}
|
||||
.versions-list-section h4 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.prediction-chart {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
Binary file not shown.
@ -3536,6 +3536,11 @@ def get_model_versions_api(product_id, model_type):
|
||||
result = model_manager.list_models(product_id=product_id, model_type=model_type)
|
||||
models = result.get('models', [])
|
||||
|
||||
# Convert metrics to native Python types to ensure JSON serialization
|
||||
for model in models:
|
||||
if 'metrics' in model and model['metrics']:
|
||||
model['metrics'] = {k: float(v) for k, v in model['metrics'].items()}
|
||||
|
||||
# Sort models: 'best' version first, then by creation date descending
|
||||
models.sort(key=lambda x: (x.get('version') != 'best', x.get('created_at', '')), reverse=True)
|
||||
|
||||
@ -3567,15 +3572,27 @@ def get_store_model_versions_api(store_id, model_type):
|
||||
)
|
||||
models = result.get('models', [])
|
||||
|
||||
versions = sorted(list(set(m['version'] for m in models)), key=lambda v: (v != 'best', v))
|
||||
latest_version = versions[0] if versions else None
|
||||
# Add store_name to each model
|
||||
from utils.multi_store_data_utils import get_store_name
|
||||
for model in models:
|
||||
model['store_name'] = get_store_name(model.get('store_id')) or model.get('store_id')
|
||||
|
||||
# Convert metrics to native Python types to ensure JSON serialization
|
||||
for model in models:
|
||||
if 'metrics' in model and model['metrics']:
|
||||
model['metrics'] = {k: float(v) for k, v in model['metrics'].items()}
|
||||
|
||||
# Sort models: 'best' version first, then by creation date descending
|
||||
models.sort(key=lambda x: (x.get('version') != 'best', x.get('created_at', '')), reverse=True)
|
||||
|
||||
latest_version = models[0]['version'] if models else None
|
||||
|
||||
return jsonify({
|
||||
"status": "success",
|
||||
"data": {
|
||||
"store_id": store_id,
|
||||
"model_type": model_type,
|
||||
"versions": versions,
|
||||
"versions": models, # Return the full list of model details
|
||||
"latest_version": latest_version
|
||||
}
|
||||
})
|
||||
@ -3599,15 +3616,22 @@ def get_global_model_versions_api(model_type):
|
||||
if aggregation_method:
|
||||
models = [m for m in models if m.get('aggregation_method') == aggregation_method]
|
||||
|
||||
versions = sorted(list(set(m['version'] for m in models)), key=lambda v: (v != 'best', v))
|
||||
latest_version = versions[0] if versions else None
|
||||
# Convert metrics to native Python types
|
||||
for model in models:
|
||||
if 'metrics' in model and model['metrics']:
|
||||
model['metrics'] = {k: float(v) for k, v in model['metrics'].items()}
|
||||
|
||||
# Sort models: 'best' version first, then by creation date descending
|
||||
models.sort(key=lambda x: (x.get('version') != 'best', x.get('created_at', '')), reverse=True)
|
||||
|
||||
latest_version = models[0]['version'] if models else None
|
||||
|
||||
return jsonify({
|
||||
"status": "success",
|
||||
"data": {
|
||||
"model_type": model_type,
|
||||
"aggregation_method": aggregation_method,
|
||||
"versions": versions,
|
||||
"versions": models, # Return the full list of model details
|
||||
"latest_version": latest_version
|
||||
}
|
||||
})
|
||||
|
@ -214,6 +214,27 @@ def get_available_stores(file_path: str = None) -> List[Dict[str, Any]]:
|
||||
print(f"获取店铺列表失败: {e}")
|
||||
return []
|
||||
|
||||
def get_store_name(store_id: str, file_path: str = None) -> Optional[str]:
|
||||
"""
|
||||
根据店铺ID获取店铺名称
|
||||
|
||||
参数:
|
||||
store_id: 店铺ID
|
||||
file_path: 数据文件路径
|
||||
|
||||
返回:
|
||||
str: 店铺名称,如果找不到则返回None
|
||||
"""
|
||||
try:
|
||||
stores = get_available_stores(file_path)
|
||||
for store in stores:
|
||||
if store['store_id'] == store_id:
|
||||
return store['store_name']
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"获取店铺名称失败: {e}")
|
||||
return None
|
||||
|
||||
def get_available_products(file_path: str = None,
|
||||
store_id: Optional[str] = None) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
|
Loading…
x
Reference in New Issue
Block a user