From e844debfaa96500d8607c959774e85d69f99488a Mon Sep 17 00:00:00 2001 From: LYFxiaoan Date: Wed, 23 Jul 2025 11:38:23 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E5=BA=97=E9=93=BA=E3=80=81?= =?UTF-8?q?=E5=85=A8=E5=B1=80=E5=88=97=E8=A1=A8=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../views/prediction/GlobalPredictionView.vue | 101 ++++++++++-------- .../views/prediction/StorePredictionView.vue | 98 +++++++++-------- prediction_history.db | Bin 266240 -> 278528 bytes server/api.py | 36 +++++-- server/utils/multi_store_data_utils.py | 21 ++++ 5 files changed, 162 insertions(+), 94 deletions(-) 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 1f58e6f181720c8a0ed302850b3742fbee0d310c..80743a9ba433fb46d732f0c90173e842e700e9da 100644 GIT binary patch delta 4771 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 Pdw`Nn?Av?TSxPtn;`KYL 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]]: """