diff --git a/UI/src/views/prediction/GlobalPredictionView.vue b/UI/src/views/prediction/GlobalPredictionView.vue index 98e5b08..1cddd84 100644 --- a/UI/src/views/prediction/GlobalPredictionView.vue +++ b/UI/src/views/prediction/GlobalPredictionView.vue @@ -34,25 +34,7 @@ - - - - - - - - + - + - + - + -
- - - 开始预测 - +
+

📦 可用模型版本

+ + + + + + + + + + + + + + + + + + + + +
@@ -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; } diff --git a/UI/src/views/prediction/StorePredictionView.vue b/UI/src/views/prediction/StorePredictionView.vue index 1eb0c1d..e7e42c0 100644 --- a/UI/src/views/prediction/StorePredictionView.vue +++ b/UI/src/views/prediction/StorePredictionView.vue @@ -44,25 +44,7 @@ - - - - - - - - + - + - + - +
-
- - - 开始预测 - +
+

📦 可用模型版本

+ + + + + + + + + + + + + + + + + + +
@@ -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; } diff --git a/prediction_history.db b/prediction_history.db index 1f58e6f..80743a9 100644 Binary files a/prediction_history.db and b/prediction_history.db differ diff --git a/server/api.py b/server/api.py index 7ce4ff8..32591af 100644 --- a/server/api.py +++ b/server/api.py @@ -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 } }) diff --git a/server/utils/multi_store_data_utils.py b/server/utils/multi_store_data_utils.py index facc2b3..67c0f1e 100644 --- a/server/utils/multi_store_data_utils.py +++ b/server/utils/multi_store_data_utils.py @@ -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]]: """