DIng 5 місяців тому
батько
коміт
42e5f38198
57 змінених файлів з 719 додано та 376 видалено
  1. 1 1
      .idea/AcidificationModel.iml
  2. 1 1
      .idea/misc.xml
  3. BIN
      api/SoilAcidification.db
  4. BIN
      api/__pycache__/run.cpython-38.pyc
  5. 2 2
      api/api_db.py
  6. 3 4
      api/app/database_models.py
  7. 144 69
      api/app/model.py
  8. 71 120
      api/app/routes.py
  9. 135 2
      api/app/utils.py
  10. BIN
      api/model_optimize/data/data_filt - 副本.xlsx
  11. BIN
      api/model_optimize/data/data_reflux2.xlsx
  12. 92 16
      api/model_optimize/data_increase.py
  13. 2 1
      api/model_optimize/data_increase_5cv_score.py
  14. 108 0
      api/model_optimize/learning_rate.py
  15. BIN
      api/pkl/default_model_0104_1718.pkl
  16. BIN
      api/pkl/default_model_0104_1720.pkl
  17. BIN
      api/pkl/default_model_0104_2141.pkl
  18. BIN
      api/pkl/rf_model_0104_0932.pkl
  19. BIN
      api/pkl/rf_model_0104_1145.pkl
  20. BIN
      api/pkl/rf_model_0104_1149.pkl
  21. BIN
      api/pkl/rf_model_0104_1237.pkl
  22. BIN
      api/pkl/rf_model_0104_1259.pkl
  23. BIN
      api/pkl/rf_model_0104_1415.pkl
  24. BIN
      api/pkl/rf_model_0104_1418.pkl
  25. BIN
      api/pkl/rf_model_0104_1420.pkl
  26. BIN
      api/pkl/rf_model_0104_2207.pkl
  27. BIN
      api/pkl/rf_model_0107_0052.pkl
  28. BIN
      api/pkl/rf_model_0107_0054.pkl
  29. BIN
      api/pkl/rf_model_0107_0123.pkl
  30. BIN
      api/pkl/rf_model_1229_2010.pkl
  31. BIN
      api/pkl/rf_model_1229_2014.pkl
  32. 12 0
      api/uploads/data/X_test_reduce.csv
  33. 8 0
      api/uploads/data/X_test_reflux.csv
  34. 12 0
      api/uploads/data/Y_test_reduce.csv
  35. 8 0
      api/uploads/data/Y_test_reflux.csv
  36. BIN
      api/uploads/datasets/dataset_1.xlsx
  37. BIN
      api/uploads/datasets/dataset_34.xlsx
  38. BIN
      api/uploads/datasets/dataset_6.xlsx
  39. BIN
      api/uploads/datasets/dataset_7.xlsx
  40. BIN
      api/uploads/datasets/dataset_None.xlsx
  41. 1 2
      app.json
  42. 8 8
      pages/Visualizatio/Visualizatio.js
  43. 10 12
      pages/Visualization/Visualization.js
  44. 0 2
      pages/Visualization/Visualization.wxml
  45. 3 0
      pages/admin/admin.js
  46. 1 1
      project.config.json
  47. 1 1
      project.private.config.json
  48. 16 34
      shoping/AcidNeutralizationModel/AcidNeutralizationModel.js
  49. 18 5
      shoping/AcidNeutralizationModel/AcidNeutralizationModel.wxml
  50. 4 7
      shoping/Calculation/Calculation.js
  51. 0 44
      shoping/Calculation/Calculation.wxml
  52. 35 15
      shoping/Home/Home.js
  53. 0 6
      shoping/Soil Acidification Iterative Evolution/Soil Acidification Iterative Evolution.wxml
  54. 12 12
      shoping/Soil Acidification/Soil Acidification.wxml
  55. 1 1
      shoping/Soil Acidification/Soil Acidification.wxss
  56. 9 9
      shoping/Soil Deacidification/Soil Deacidification.wxml
  57. 1 1
      shoping/Soil Deacidification/Soil Deacidification.wxss

+ 1 - 1
.idea/AcidificationModel.iml

@@ -4,7 +4,7 @@
     <content url="file://$MODULE_DIR$">
       <excludeFolder url="file://$MODULE_DIR$/.venv" />
     </content>
-    <orderEntry type="jdk" jdkName="Python 3.8 (AcidificationModel2)" jdkType="Python SDK" />
+    <orderEntry type="jdk" jdkName="Python 3.8" jdkType="Python SDK" />
     <orderEntry type="sourceFolder" forTests="false" />
   </component>
 </module>

+ 1 - 1
.idea/misc.xml

@@ -3,7 +3,7 @@
   <component name="Black">
     <option name="sdkName" value="pythonProject1" />
   </component>
-  <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.8 (AcidificationModel2)" project-jdk-type="Python SDK" />
+  <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.8" project-jdk-type="Python SDK" />
   <component name="PyCharmProfessionalAdvertiser">
     <option name="shown" value="true" />
   </component>

BIN
api/SoilAcidification.db


BIN
api/__pycache__/run.cpython-38.pyc


+ 2 - 2
api/api_db.py

@@ -64,7 +64,7 @@ def get_column_names(table_name):
     else:
         return [], []  # 不支持的表名,返回空列表
 
-
+# 模板下载接口
 @app.route('/download_template', methods=['GET'])
 def download_template():
         """
@@ -114,7 +114,7 @@ def download_template():
             return send_file(output, as_attachment=True, download_name=f'{table_name}_template.xlsx',
                              mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
 
-# 模板下载接口
+# 模板导入接口
 @app.route('/import_data', methods=['POST'])
 def import_data():
     """

+ 3 - 4
api/app/database_models.py

@@ -30,7 +30,8 @@ class Models(Base):
     Description: Mapped[Optional[str]] = mapped_column(Text)
     DatasetID: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey('Datasets.Dataset_ID'))
     ModelFilePath: Mapped[Optional[str]] = mapped_column(Text)
-    Data_type: Mapped[Optional[str]] = mapped_column(Text)  # 新增字段
+    Data_type: Mapped[Optional[str]] = mapped_column(Text)
+    Performance_score: Mapped[Optional[float]] = mapped_column(Text)
 
     ModelParameters: Mapped[List['ModelParameters']] = relationship('ModelParameters', back_populates='Models_')
 
@@ -54,10 +55,8 @@ class CurrentReflux(Base):
     CL: Mapped[float] = mapped_column(Float)
     CEC: Mapped[float] = mapped_column(Float)
     H_plus: Mapped[float] = mapped_column(Float)
-    HN: Mapped[float] = mapped_column(Float)
+    N: Mapped[float] = mapped_column(Float)
     Al3_plus: Mapped[float] = mapped_column(Float)
-    Free_alumina: Mapped[float] = mapped_column(Float)
-    Free_iron_oxides: Mapped[float] = mapped_column(Float)
     Delta_pH: Mapped[float] = mapped_column(Float)
 
 

+ 144 - 69
api/app/model.py

@@ -3,100 +3,136 @@ import os
 import pickle
 import pandas as pd
 from flask_sqlalchemy.session import Session
-from sklearn.ensemble import RandomForestRegressor
+from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
+from sklearn.metrics import r2_score
 from sklearn.model_selection import train_test_split, cross_val_score
 from sqlalchemy import text
+from xgboost import XGBRegressor
 
 from .database_models import Models, Datasets
 
 
-
 # 加载模型
-def load_model(model_name):
-    file_path = f'model_optimize/pkl/{model_name}.pkl'
-    with open(file_path, 'rb') as f:
+def load_model(session, model_id):
+    model = session.query(Models).filter(Models.ModelID == model_id).first()
+    
+    if not model:
+        raise ValueError(f"Model with ID {model_id} not found.")
+    with open(model.ModelFilePath, 'rb') as f:
         return pickle.load(f)
 
 
 # 模型预测
-def predict(input_data: pd.DataFrame, model_name):
+def predict(session, input_data: pd.DataFrame, model_id):
     # 初始化模型
-    model = load_model(model_name)  # 根据指定的模型名加载模型
-    predictions = model.predict(input_data)
+    ML_model = load_model(session, model_id)  # 根据指定的模型名加载模型
+    # model = load_model(model_id)  # 根据指定的模型名加载模型
+    predictions = ML_model.predict(input_data)
     return predictions.tolist()
 
+# 计算模型评分
+def calculate_model_score(model_info):
+    # 加载模型
+    with open(model_info.ModelFilePath, 'rb') as f:
+        ML_model = pickle.load(f)
+    # print("Model requires the following features:", model.feature_names_in_)
+    # 数据准备
+    if model_info.Data_type == 'reflux':  # 反酸数据集
+        # 加载保存的 X_test 和 Y_test
+        X_test = pd.read_csv('uploads/data/X_test_reflux.csv')
+        Y_test = pd.read_csv('uploads/data/Y_test_reflux.csv')
+        print(X_test.columns)  # 在测试时使用的数据的列名
+        y_pred = ML_model.predict(X_test)
+    elif model_info.Data_type == 'reduce':  # 降酸数据集
+        # 加载保存的 X_test 和 Y_test
+        X_test = pd.read_csv('uploads/data/X_test_reduce.csv')
+        Y_test = pd.read_csv('uploads/data/Y_test_reduce.csv')
+        print(X_test.columns)  # 在测试时使用的数据的列名
+        y_pred = ML_model.predict(X_test)
+
+
+    # 计算 R² 分数
+    r2 = r2_score(Y_test, y_pred)
+    return r2
 
 
 def train_and_save_model(session, model_type, model_name, model_description, data_type, dataset_id=None):
-    if not dataset_id:
-        # 直接创建新的数据集并复制数据
-        dataset_id = save_current_dataset(session, data_type)
+    try:
+        if not dataset_id:
+            # 创建新的数据集并复制数据,此过程将不立即提交
+            dataset_id = save_current_dataset(session, data_type, commit=False)
 
-    # 从新复制的数据集表中加载数据
-    dataset_table_name = f"dataset_{dataset_id}"
-    dataset = pd.read_sql_table(dataset_table_name, session.bind)
+            if data_type == 'reflux':
+                current_table = 'current_reflux'
+            elif data_type == 'reduce':
+                current_table = 'current_reduce'
 
-    if dataset.empty:
-        raise ValueError(f"Dataset {dataset_id} is empty or not found.")
+            # 从current数据集表中加载数据
+            dataset = pd.read_sql_table(current_table, session.bind)
 
-    # 数据准备
-    X = dataset.iloc[:, :-1]
-    y = dataset.iloc[:, -1]
+        elif dataset_id:
+            # 从新复制的数据集表中加载数据
+            dataset_table_name = f"dataset_{dataset_id}"
+            dataset = pd.read_sql_table(dataset_table_name, session.bind)
 
-    # 训练模型
-    model = train_model_by_type(X, y, model_type)
+            if dataset.empty:
+                raise ValueError(f"Dataset {dataset_id} is empty or not found.")
 
-    # 保存模型到数据库
-    save_model(session, model, model_name, model_type, model_description, dataset_id, data_type)
+        if data_type == 'reflux':
+            X = dataset.iloc[:, 1:-1]
+            y = dataset.iloc[:, -1]
+        elif data_type == 'reduce':
+            X = dataset.iloc[:, 2:]
+            y = dataset.iloc[:, 1]
 
+        # 训练模型
+        model = train_model_by_type(X, y, model_type)
 
-# # 保存模型参数
-    # save_model_parameters(model, saved_model.ModelID)
+        # 保存模型到数据库
+        model_id = save_model(session, model, model_name, model_type, model_description, dataset_id, data_type)
+
+        # 所有操作成功后,手动提交事务
+        session.commit()
+        return model_name, model_id
+    except Exception as e:
+        # 如果在任何阶段出现异常,回滚事务
+        session.rollback()
+        raise e  # 可选择重新抛出异常或处理异常
 
-    # # 计算评估指标(如MSE)
-    # y_pred = model.predict(X)
-    # mse = mean_squared_error(y, y_pred)
-    #
-    # return saved_model, mse
 
-def save_current_dataset(session, data_type):
+
+def save_current_dataset(session, data_type, commit=True):
     """
-    创建一个新的数据集条目,并复制对应的数据类型表的数据。
+    创建一个新的数据集条目,并复制对应的数据类型表的数据,但不立即提交事务。
 
     Args:
     session (Session): SQLAlchemy session对象。
-    data_type (str): 数据集的类型,如 'reduce' 或 'reflux'。
+    data_type (str): 数据集的类型。
+    commit (bool): 是否在函数结束时提交事务。
 
     Returns:
     int: 新保存的数据集的ID。
     """
-    # 创建一个新的数据集条目
     new_dataset = Datasets(
-        Dataset_name=f"{data_type}_dataset_{datetime.datetime.now():%Y%m%d_%H%M%S}",  # 使用当前时间戳生成独特的名称
+        Dataset_name=f"{data_type}_dataset_{datetime.datetime.now():%Y%m%d_%H%M%S}",
         Dataset_description=f"Automatically generated dataset for type {data_type}",
-        Row_count=0,  # 初始行数为0,将在复制数据后更新
-        Status='pending',  # 初始状态为pending
+        Row_count=0,
+        Status='pending',
         Dataset_type=data_type
     )
 
-    # 添加到数据库并提交以获取ID
     session.add(new_dataset)
-    session.flush()  # flush用于立即执行SQL并获取ID,但不提交事务
+    session.flush()
 
-    # 获取新数据集的ID
     dataset_id = new_dataset.Dataset_ID
-
-    # 复制数据到新表
-    source_table = data_type_table_mapping(data_type)  # 假设有函数映射数据类型到表名
+    source_table = data_type_table_mapping(data_type)
     new_table_name = f"dataset_{dataset_id}"
-    copy_table_sql = f"CREATE TABLE {new_table_name} AS SELECT * FROM {source_table};"
-    session.execute(text(copy_table_sql))
+    session.execute(text(f"CREATE TABLE {new_table_name} AS SELECT * FROM {source_table};"))
 
-    # 更新新数据集的状态和行数
-    update_sql = f"UPDATE datasets SET status='processed', row_count=(SELECT count(*) FROM {new_table_name}) WHERE dataset_id={dataset_id};"
-    session.execute(text(update_sql))
+    session.execute(text(f"UPDATE datasets SET status='processed', row_count=(SELECT count(*) FROM {new_table_name}) WHERE dataset_id={dataset_id};"))
 
-    session.commit()
+    if commit:
+        session.commit()
 
     return dataset_id
 
@@ -128,7 +164,7 @@ def train_model_by_type(X, y, model_type):
 
 
 def train_random_forest(X_train, y_train):
-    best_score = 0
+    best_score = -float('inf')
     best_n_estimators = None
     best_max_depth = None
     random_state = 43
@@ -145,7 +181,7 @@ def train_random_forest(X_train, y_train):
 
     # 在找到的最佳树的数量基础上,筛选最佳的最大深度
     best_score = 0  # 重置最佳得分,为最大深度优化做准备
-    for max_depth in range(1, 30, 1):
+    for max_depth in range(1, 5, 1):
         model = RandomForestRegressor(n_estimators=best_n_estimators, max_depth=max_depth, random_state=random_state)
         score = cross_val_score(model, X_train, y_train, cv=5).mean()
         if score > best_score:
@@ -157,25 +193,64 @@ def train_random_forest(X_train, y_train):
     # 使用最佳的树的数量和最大深度训练最终模型
     best_model = RandomForestRegressor(n_estimators=best_n_estimators, max_depth=best_max_depth,
                                        random_state=random_state)
+
+    # 传入列名进行训练
+    best_model.fit(X_train, y_train)
+    # 指定传入的特征名
+    best_model.feature_names_in_ = X_train.columns
+    return best_model
+
+
+def train_xgboost(X_train, y_train):
+    best_score = -float('inf')
+    best_params = {'learning_rate': None, 'max_depth': None}
+    random_state = 43
+
+    for learning_rate in [0.01, 0.05, 0.1, 0.2]:
+        for max_depth in range(3, 10):
+            model = XGBRegressor(learning_rate=learning_rate, max_depth=max_depth, random_state=random_state)
+            score = cross_val_score(model, X_train, y_train, cv=5).mean()
+            if score > best_score:
+                best_score = score
+                best_params['learning_rate'] = learning_rate
+                best_params['max_depth'] = max_depth
+
+    print(f"Best parameters: {best_params}, Score: {best_score}")
+
+    # 使用找到的最佳参数训练最终模型
+    best_model = XGBRegressor(learning_rate=best_params['learning_rate'], max_depth=best_params['max_depth'],
+                              random_state=random_state)
     best_model.fit(X_train, y_train)
 
     return best_model
 
 
-def train_xgboost(X_train, y_train, X_test, y_test):
-    # XGBoost训练过程
-    # (将类似上面的代码添加到这里)
-    pass
+def train_gradient_boosting(X_train, y_train):
+    best_score = -float('inf')
+    best_params = {'learning_rate': None, 'max_depth': None}
+    random_state = 43
 
+    for learning_rate in [0.01, 0.05, 0.1, 0.2]:
+        for max_depth in range(3, 10):
+            model = GradientBoostingRegressor(learning_rate=learning_rate, max_depth=max_depth, random_state=random_state)
+            score = cross_val_score(model, X_train, y_train, cv=5).mean()
+            if score > best_score:
+                best_score = score
+                best_params['learning_rate'] = learning_rate
+                best_params['max_depth'] = max_depth
 
-def train_gradient_boosting(X_train, y_train, X_test, y_test):
-    # 梯度提升树训练过程
-    # (将类似上面的代码添加到这里)
-    pass
+    print(f"Best parameters: {best_params}, Score: {best_score}")
 
-def save_model(session, model, model_name, model_type, model_description, dataset_id, data_type, custom_path='pkl'):
+    # 使用找到的最佳参数训练最终模型
+    best_model = GradientBoostingRegressor(learning_rate=best_params['learning_rate'], max_depth=best_params['max_depth'],
+                                           random_state=random_state)
+    best_model.fit(X_train, y_train)
+
+    return best_model
+
+def save_model(session, model, model_name, model_type, model_description, dataset_id, data_type, custom_path='pkl', commit=False):
     """
-    保存模型到数据库,并将模型文件保存到磁盘。
+    保存模型到数据库,并将模型文件保存到磁盘,但不立即提交事务
 
     :param session: 数据库会话
     :param model: 要保存的模型对象
@@ -184,21 +259,21 @@ def save_model(session, model, model_name, model_type, model_description, datase
     :param model_description: 模型的描述信息
     :param dataset_id: 数据集ID
     :param custom_path: 保存模型的路径
+    :param commit: 是否提交事务
     :return: 返回保存的模型文件路径
     """
-    # 根据模型类型设置文件名前缀
     prefix_dict = {
         'RandomForest': 'rf_model_',
         'XGBRegressor': 'xgbr_model_',
         'GBSTRegressor': 'gbstr_model_'
     }
-    prefix = prefix_dict.get(model_type, 'default_model_')  # 如果model_type不在字典中,默认前缀
+    prefix = prefix_dict.get(model_type, 'default_model_')
 
     try:
         # 确保路径存在
         os.makedirs(custom_path, exist_ok=True)
 
-        # 获取当前时间戳(格式:月日时分)
+        # 获取当前时间戳
         timestamp = datetime.datetime.now().strftime('%m%d_%H%M')
 
         # 拼接完整的文件名
@@ -222,15 +297,15 @@ def save_model(session, model, model_name, model_type, model_description, datase
 
         # 添加记录到数据库
         session.add(new_model)
-        session.commit()
+        session.flush()
 
-        # 返回文件路径
-        return file_name
+        # 返回模型ID
+        return new_model.ModelID
 
     except Exception as e:
-        session.rollback()
         print(f"Error saving model: {str(e)}")
-        raise e  # 显式抛出异常供调用者处理
+        raise
+
 
 
 

+ 71 - 120
api/app/routes.py

@@ -1,17 +1,17 @@
 import sqlite3
-
 from flask import Blueprint, request, jsonify, current_app
-from .model import predict, train_and_save_model
+from .model import predict, train_and_save_model, calculate_model_score
 import pandas as pd
 from . import db  # 从 app 包导入 db 实例
 from sqlalchemy.engine.reflection import Inspector
 from .database_models import Models, ModelParameters, Datasets, CurrentReduce, CurrentReflux
 import os
-from .utils import create_dynamic_table, allowed_file
+from .utils import create_dynamic_table, allowed_file, infer_column_types, rename_columns_for_model_predict, \
+    clean_column_names, rename_columns_for_model, insert_data_into_dynamic_table, insert_data_into_existing_table, \
+    predict_to_Q
 from sqlalchemy.orm import sessionmaker
-from sqlalchemy.schema import MetaData, Table
 import logging
-from sqlalchemy import text, select
+from sqlalchemy import text, select, MetaData, Table
 
 # 配置日志
 logging.basicConfig(level=logging.DEBUG)
@@ -19,16 +19,6 @@ logger = logging.getLogger(__name__)
 # 创建蓝图 (Blueprint),用于分离路由
 bp = Blueprint('routes', __name__)
 
-def infer_column_types(df):
-    type_map = {
-        'object': 'str',
-        'int64': 'int',
-        'float64': 'float',
-        'datetime64[ns]': 'datetime'  # 适应Pandas datetime类型
-    }
-    # 提取列和其数据类型
-    return {col: type_map.get(str(df[col].dtype), 'str') for col in df.columns}
-
 
 @bp.route('/upload-dataset', methods=['POST'])
 def upload_dataset():
@@ -92,21 +82,21 @@ def upload_dataset():
         }), 201
 
     except Exception as e:
-        if session:
-            session.rollback()
+        session.rollback()
         logging.error('Failed to process the dataset upload:', exc_info=True)
         return jsonify({'error': str(e)}), 500
     finally:
         session.close()
 
+
 @bp.route('/train-and-save-model', methods=['POST'])
 def train_and_save_model_endpoint():
     # 创建 sessionmaker 实例
     Session = sessionmaker(bind=db.engine)
     session = Session()
 
-    # 从请求中解析参数
     data = request.get_json()
+    # 从请求中解析参数
     model_type = data.get('model_type')
     model_name = data.get('model_name')
     model_description = data.get('model_description')
@@ -127,96 +117,70 @@ def train_and_save_model_endpoint():
     finally:
         session.close()
 
+@bp.route('/predict', methods=['POST'])
+def predict_route():
+    # 创建 sessionmaker 实例
+    Session = sessionmaker(bind=db.engine)
+    session = Session()
+    try:
+        data = request.get_json()
+        model_id = data.get('model_id')          # 提取模型名称
+        parameters = data.get('parameters', {})  # 提取所有变量
+
+        # 根据model_id获取模型Data_type
+        model_info = session.query(Models).filter(Models.ModelID == model_id).first()
+        if not model_info:
+            return jsonify({'error': 'Model not found'}), 404
+        data_type = model_info.Data_type
+
+        input_data = pd.DataFrame([parameters])  # 转换参数为DataFrame
+        # 如果为reduce,则不需要传入target_ph
+        if data_type == 'reduce':
+            # 获取传入的init_ph、target_ph参数
+            init_ph = float(parameters.get('init_pH', 0.0))  # 默认值为0.0,防止None导致错误
+            target_ph = float(parameters.get('target_pH', 0.0))  # 默认值为0.0,防止None导致错误
+
+            # 从输入数据中删除'target_pH'列
+            input_data = input_data.drop('target_pH', axis=1, errors='ignore')  # 使用errors='ignore'防止列不存在时出错
+
+        input_data_rename = rename_columns_for_model_predict(input_data, data_type)  # 重命名列名以匹配模型字段
+        predictions = predict(session, input_data_rename, model_id)  # 调用预测函数
+
+        if data_type == 'reduce':
+            predictions = predictions[0]
+            # 将预测结果转换为Q
+            predictions = predict_to_Q(predictions, init_ph, target_ph)
+            print(predictions)
+
+        return jsonify({'result': predictions}), 200
+    except Exception as e:
+        logging.error('Failed to predict:', exc_info=True)
+        return jsonify({'error': str(e)}), 400
+
+# 为指定模型计算评分Performance_score,需要提供model_id
+@bp.route('/score-model/<int:model_id>', methods=['POST'])
+def score_model(model_id):
+    # 创建 sessionmaker 实例
+    Session = sessionmaker(bind=db.engine)
+    session = Session()
+    try:
+        model_info = session.query(Models).filter(Models.ModelID == model_id).first()
+        if not model_info:
+            return jsonify({'error': 'Model not found'}), 404
+
+        # 计算模型评分
+        score = calculate_model_score(model_info)
 
-def clean_column_names(dataframe):
-    # Strip whitespace and replace non-breaking spaces and other non-printable characters
-    dataframe.columns = [col.strip().replace('\xa0', '') for col in dataframe.columns]
-    return dataframe
-
-
-def rename_columns_for_model(dataframe, dataset_type):
-    if dataset_type == 'reduce':
-        rename_map = {
-            '1/b': 'Q_over_b',
-            'pH': 'pH',
-            'OM': 'OM',
-            'CL': 'CL',
-            'H': 'H',
-            'Al': 'Al'
-        }
-    elif dataset_type == 'reflux':
-        rename_map = {
-            'OM g/kg': 'OM',
-            'CL g/kg': 'CL',
-            'CEC cmol/kg': 'CEC',
-            'H+ cmol/kg': 'H_plus',
-            'HN mg/kg': 'HN',
-            'Al3+cmol/kg': 'Al3_plus',
-            'Free alumina g/kg': 'Free_alumina',
-            'Free iron oxides g/kg': 'Free_iron_oxides',
-            'ΔpH': 'Delta_pH'
-        }
-
-    # 使用 rename() 方法更新列名
-    dataframe = dataframe.rename(columns=rename_map)
-    return dataframe
-
-
-def insert_data_into_existing_table(session, dataframe, model_class):
-    """Insert data from a DataFrame into an existing SQLAlchemy model table."""
-    for index, row in dataframe.iterrows():
-        record = model_class(**row.to_dict())
-        session.add(record)
-
-def insert_data_into_dynamic_table(session, dataset_df, dynamic_table_class):
-    for _, row in dataset_df.iterrows():
-        record_data = row.to_dict()
-        session.execute(dynamic_table_class.__table__.insert(), [record_data])
-
-def insert_data_by_type(session, dataset_df, dataset_type):
-    if dataset_type == 'reduce':
-        for _, row in dataset_df.iterrows():
-            record = CurrentReduce(**row.to_dict())
-            session.add(record)
-    elif dataset_type == 'reflux':
-        for _, row in dataset_df.iterrows():
-            record = CurrentReflux(**row.to_dict())
-            session.add(record)
-
-
-def get_current_data(session, data_type):
-    # 根据数据类型选择相应的表模型
-    if data_type == 'reduce':
-        model = CurrentReduce
-    elif data_type == 'reflux':
-        model = CurrentReflux
-    else:
-        raise ValueError("Invalid data type provided. Choose 'reduce' or 'reflux'.")
-
-    # 从数据库中查询所有记录
-    result = session.execute(select(model))
-
-    # 将结果转换为DataFrame
-    dataframe = pd.DataFrame([dict(row) for row in result])
-    return dataframe
-
-def get_dataset_by_id(session, dataset_id):
-    # 动态获取表的元数据
-    metadata = MetaData(bind=session.bind)
-    dataset_table = Table(dataset_id, metadata, autoload=True, autoload_with=session.bind)
-
-    # 从数据库中查询整个表的数据
-    query = select(dataset_table)
-    result = session.execute(query).fetchall()
-
-    # 检查是否有数据返回
-    if not result:
-        raise ValueError(f"No data found for dataset {dataset_id}.")
-
-    # 将结果转换为DataFrame
-    dataframe = pd.DataFrame(result, columns=[column.name for column in dataset_table.columns])
-
-    return dataframe
+        # 更新模型记录中的评分
+        model_info.Performance_score = score
+        session.commit()
+
+        return jsonify({'message': 'Model scored successfully', 'score': score}), 200
+    except Exception as e:
+        logging.error('Failed to process the dataset upload:', exc_info=True)
+        return jsonify({'error': str(e)}), 400
+    finally:
+        session.close()
 
 
 @bp.route('/delete-dataset/<int:dataset_id>', methods=['DELETE'])
@@ -353,19 +317,6 @@ def get_model_parameters(model_id):
         return jsonify({'error': 'Internal server error', 'message': str(e)}), 500
 
 
-@bp.route('/predict', methods=['POST'])
-def predict_route():
-    try:
-        data = request.get_json()
-        model_name = data.get('model_name')  # 提取模型名称
-        parameters = data.get('parameters', {})  # 提取所有参数
-
-        input_data = pd.DataFrame([parameters])  # 转换参数为DataFrame
-        predictions = predict(input_data, model_name)  # 调用预测函数
-        return jsonify({'predictions': predictions}), 200
-    except Exception as e:
-        return jsonify({'error': str(e)}), 400
-
 
 # 定义添加数据库记录的 API 接口
 @bp.route('/add_item', methods=['POST'])

+ 135 - 2
api/app/utils.py

@@ -1,8 +1,12 @@
 from sqlalchemy.ext.declarative import declarative_base
-from sqlalchemy import Column, Integer, String, Float, DateTime
-from sqlalchemy import create_engine
+from sqlalchemy import Column, Integer, String, Float, DateTime, select, create_engine
 import uuid
 from datetime import datetime, timezone
+import pandas as pd
+from .database_models import CurrentReduce, CurrentReflux
+from sqlalchemy.schema import MetaData, Table
+
+
 
 Base = declarative_base()
 
@@ -55,3 +59,132 @@ def generate_unique_filename(filename):
     # 使用 UUID 和当前时间戳生成唯一文件名(使用 UTC 时区)
     unique_filename = f"{uuid.uuid4().hex}_{datetime.now(timezone.utc).strftime('%Y%m%d%H%M%S')}.{ext}"
     return unique_filename
+
+def infer_column_types(df):
+    type_map = {
+        'object': 'str',
+        'int64': 'int',
+        'float64': 'float',
+        'datetime64[ns]': 'datetime'  # 适应Pandas datetime类型
+    }
+    # 提取列和其数据类型
+    return {col: type_map.get(str(df[col].dtype), 'str') for col in df.columns}
+
+def clean_column_names(dataframe):
+    # Strip whitespace and replace non-breaking spaces and other non-printable characters
+    dataframe.columns = [col.strip().replace('\xa0', '') for col in dataframe.columns]
+    return dataframe
+
+
+# 建立excel文件的列名和数据库模型字段之间的映射
+def rename_columns_for_model(dataframe, dataset_type):
+    if dataset_type == 'reduce':
+        rename_map = {
+            '1/b': 'Q_over_b',
+            'pH': 'pH',
+            'OM': 'OM',
+            'CL': 'CL',
+            'H': 'H',
+            'Al': 'Al'
+        }
+    elif dataset_type == 'reflux':
+        rename_map = {
+            'OM': 'OM',
+            'CL': 'CL',
+            'CEC': 'CEC',
+            'H+': 'H_plus',
+            'N': 'N',
+            'Al3+': 'Al3_plus',
+            'ΔpH': 'Delta_pH'
+        }
+
+    # 使用 rename() 方法更新列名
+    dataframe = dataframe.rename(columns=rename_map)
+    return dataframe
+
+# 建立前端参数和模型预测字段之间的映射
+def rename_columns_for_model_predict(dataframe, dataset_type):
+    if dataset_type == 'reduce':
+        rename_map = {
+            'init_pH': 'pH',
+            'OM': 'OM',
+            'CL': 'CL',
+            'H': 'H',
+            'Al': 'Al'
+        }
+    elif dataset_type == 'reflux':
+        rename_map = {
+            "organic_matter": "OM",
+            "chloride": "CL",
+            "cec": "CEC",
+            "h_concentration": "H_plus",
+            "n": "N",
+            "al_concentration": "Al3_plus"
+        }
+
+    # 使用 rename() 方法更新列名
+    dataframe = dataframe.rename(columns=rename_map)
+    return dataframe
+
+
+def insert_data_into_existing_table(session, dataframe, model_class):
+    """Insert data from a DataFrame into an existing SQLAlchemy model table."""
+    for index, row in dataframe.iterrows():
+        record = model_class(**row.to_dict())
+        session.add(record)
+
+def insert_data_into_dynamic_table(session, dataset_df, dynamic_table_class):
+    for _, row in dataset_df.iterrows():
+        record_data = row.to_dict()
+        session.execute(dynamic_table_class.__table__.insert(), [record_data])
+
+def insert_data_by_type(session, dataset_df, dataset_type):
+    if dataset_type == 'reduce':
+        for _, row in dataset_df.iterrows():
+            record = CurrentReduce(**row.to_dict())
+            session.add(record)
+    elif dataset_type == 'reflux':
+        for _, row in dataset_df.iterrows():
+            record = CurrentReflux(**row.to_dict())
+            session.add(record)
+
+
+def get_current_data(session, data_type):
+    # 根据数据类型选择相应的表模型
+    if data_type == 'reduce':
+        model = CurrentReduce
+    elif data_type == 'reflux':
+        model = CurrentReflux
+    else:
+        raise ValueError("Invalid data type provided. Choose 'reduce' or 'reflux'.")
+
+    # 从数据库中查询所有记录
+    result = session.execute(select(model))
+
+    # 将结果转换为DataFrame
+    dataframe = pd.DataFrame([dict(row) for row in result])
+    return dataframe
+
+def get_dataset_by_id(session, dataset_id):
+    # 动态获取表的元数据
+    metadata = MetaData(bind=session.bind)
+    dataset_table = Table(dataset_id, metadata, autoload=True, autoload_with=session.bind)
+
+    # 从数据库中查询整个表的数据
+    query = select(dataset_table)
+    result = session.execute(query).fetchall()
+
+    # 检查是否有数据返回
+    if not result:
+        raise ValueError(f"No data found for dataset {dataset_id}.")
+
+    # 将结果转换为DataFrame
+    dataframe = pd.DataFrame(result, columns=[column.name for column in dataset_table.columns])
+
+    return dataframe
+
+def predict_to_Q(predictions, init_ph, target_ph):
+    # 将预测结果转换为Q
+    Q = predictions * (init_ph - target_ph)
+    return Q
+

BIN
api/model_optimize/data/data_filt - 副本.xlsx


BIN
api/model_optimize/data/data_reflux2.xlsx


+ 92 - 16
api/model_optimize/data_increase.py

@@ -31,18 +31,62 @@ import numpy as np
 from sklearn.metrics import mean_squared_error
 from pathlib import Path
 
-## 土壤反酸筛选数据
-data=pd.read_excel('model_optimize\data\data_filt.xlsx')   
+# ## 土壤反酸筛选数据  64个样本 9个特征(包含delta_ph)  105_day_ph
+# data=pd.read_excel('model_optimize\data\data_filt.xlsx')   
+
+# x = data.iloc[:,1:10]
+# print(x)
+# y = data.iloc[:,-1]
+# print(y)
+
+# # 为 x 赋予列名
+# x.columns = [
+#     'organic_matter',        # OM g/kg
+#     'chloride',              # CL g/kg
+#     'cec',                   # CEC cmol/kg
+#     'h_concentration',       # H+ cmol/kg
+#     'hn',                    # HN mg/kg
+#     'al_concentration',      # Al3+ cmol/kg
+#     'free_alumina',          # Free alumina g/kg
+#     'free_iron',             # Free iron oxides g/kg
+#     'delta_ph'               # ΔpH
+# ]
+
+# y.name = 'target_ph'
+
+
+# ## 土壤反酸筛选数据   64个样本 8个特征 delta_ph
+# # data=pd.read_excel('model_optimize\data\data_filt.xlsx')    # 64个样本
+# data=pd.read_excel('model_optimize\data\data_filt - 副本.xlsx')   # 60个样本(去除异常点)
 
-x = data.iloc[:,1:10]
-print(x)
 # x = data.iloc[:,1:9]
-y = data.iloc[:,-1]
+# print(x)
+
+# y = data.iloc[:,-2]
+# print(y)
+
+# # 为 x 赋予列名
+# x.columns = [
+#     'organic_matter',        # OM g/kg
+#     'chloride',              # CL g/kg
+#     'cec',                   # CEC cmol/kg
+#     'h_concentration',       # H+ cmol/kg
+#     'hn',                    # HN mg/kg
+#     'al_concentration',      # Al3+ cmol/kg
+#     'free_alumina',          # Free alumina g/kg
+#     'free_iron',             # Free iron oxides g/kg
+# ]
+
+# y.name = 'target_ph'
 
-y1 = data.iloc[:,-2]
-y2 = data.iloc[:,-1]
-# 绝对值
-y = abs(y1 - y2)
+
+
+## 土壤反酸筛选数据  最新数据:34个样本 6个特征 delta_ph
+data=pd.read_excel('model_optimize\data\data_reflux2.xlsx')   
+
+x = data.iloc[:,0:6]
+print(x)
+y = data.iloc[:,-1]
 
 print(y)
 
@@ -52,16 +96,14 @@ x.columns = [
     'chloride',              # CL g/kg
     'cec',                   # CEC cmol/kg
     'h_concentration',       # H+ cmol/kg
-    'hn',                    # HN mg/kg
+    'hn',                    # HN mg/kg    ....
     'al_concentration',      # Al3+ cmol/kg
-    'free_alumina',          # Free alumina g/kg
-    'free_iron',             # Free iron oxides g/kg
-    'delta_ph'               # ΔpH
 ]
 
-y.name = 'target_ph'
+y.name = 'delta_ph'
+
 
-# ## 精准降酸数据
+# ## 精准降酸数据  54个样本 5个特征  1/b
 # data=pd.read_excel('model_optimize\data\Acidity_reduce.xlsx')
 
 # x = data.iloc[:,1:]
@@ -88,7 +130,7 @@ XGB = XGBR(random_state=1)
 # GBSTR
 GBST = GBSTR(random_state=1)
 # KNN
-KNN = KNeighborsRegressor(n_neighbors=4)
+KNN = KNeighborsRegressor(n_neighbors=2)
 
 # 增量训练:每次增加10%的训练数据
 increment = 0.1  # 每次增加的比例
@@ -155,6 +197,40 @@ plt.title('Model Performance with Incremental Data')
 plt.legend()
 plt.grid(True)
 plt.show()
+# # 打印JavaScript中需要的数据格式
+# print("X轴数据(训练数据大小):", [f"{int(size * 100)}%" for size in train_sizes])
+# print("Random Forest R2分数:", r2_scores_rfc)
+# print("XGBoost R2分数:", r2_scores_xgb)
+# print("Gradient Boosting R2分数:", r2_scores_gbst)
+
+
+
+y_pred = rfc.predict(Xtest)  # 使用任意一个模型,这里以随机森林为例
+residuals = Ytest - y_pred
+
+plt.scatter(Ytest, y_pred, color='blue', alpha=0.5)
+plt.plot([min(Ytest), max(Ytest)], [min(Ytest), max(Ytest)], color='red', linestyle='--')  # 对角线 y=x
+plt.xlabel('True Values')
+plt.ylabel('Predicted Values')
+plt.title('True vs Predicted Values')
+plt.grid(True)
+plt.show()
+# # 生成 scatterData
+# scatter_data = [[float(true), float(pred)] for true, pred in zip(Ytest, y_pred)]
+# # 打印 scatterData(可直接复制到 JavaScript 代码中)
+# print("scatterData = ", scatter_data)
+
+# 保存 X_test 和 Y_test 为 CSV 文件
+X_test_df = pd.DataFrame(Xtest)
+Y_test_df = pd.DataFrame(Ytest)
+
+# 将 X_test 和 Y_test 保存为 CSV 文件,方便之后加载
+X_test_df.to_csv('X_test_reflux.csv', index=False)
+Y_test_df.to_csv('Y_test_reflux.csv', index=False)
+
+# 输出提示信息
+print("X_test 和 Y_test 已保存为 'X_test_reduce.csv' 和 'Y_test_reduce.csv'")
+
 
 
 # 选择后半部分数据

+ 2 - 1
api/model_optimize/data_increase_5cv_score.py

@@ -37,7 +37,8 @@ from pathlib import Path
 
 
 # 导入数据
-data = pd.read_excel('model_optimize/data/data_filt.xlsx')
+# data = pd.read_excel('model_optimize/data/data_filt.xlsx')
+data = pd.read_excel('data/data_filt.xlsx')
 x = data.iloc[:, 1:10]
 y = data.iloc[:, -1]
 

+ 108 - 0
api/model_optimize/learning_rate.py

@@ -0,0 +1,108 @@
+from sklearn.model_selection import learning_curve
+import numpy as np
+import matplotlib.pyplot as plt
+from sklearn.ensemble import RandomForestRegressor
+import pandas as pd
+from sklearn.model_selection import train_test_split
+
+
+## 精准降酸数据  54个样本 5个特征  1/b
+data=pd.read_excel('model_optimize\data\Acidity_reduce.xlsx')
+
+x = data.iloc[:,1:]
+y = data.iloc[:,0]
+# 为 x 赋予列名
+x.columns = [
+    'pH',        
+    'OM',              
+    'CL', 
+    'H',
+    'Al'
+]
+y.name = 'target'
+
+# ## 土壤反酸筛选数据  64个样本 9个特征(包含delta_ph)  105_day_ph
+# data=pd.read_excel('model_optimize\data\data_filt.xlsx')   
+
+# x = data.iloc[:,1:10]
+# print(x)
+# y = data.iloc[:,-1]
+# print(y)
+
+# # 为 x 赋予列名
+# x.columns = [
+#     'organic_matter',        # OM g/kg
+#     'chloride',              # CL g/kg
+#     'cec',                   # CEC cmol/kg
+#     'h_concentration',       # H+ cmol/kg
+#     'hn',                    # HN mg/kg
+#     'al_concentration',      # Al3+ cmol/kg
+#     'free_alumina',          # Free alumina g/kg
+#     'free_iron',             # Free iron oxides g/kg
+#     'delta_ph'               # ΔpH
+# ]
+
+# y.name = 'target_ph'
+
+# ## 土壤反酸筛选数据   64个样本 8个特征 delta_ph
+# # data=pd.read_excel('model_optimize\data\data_filt.xlsx')    # 64个样本
+# data=pd.read_excel('model_optimize\data\data_filt - 副本.xlsx')   # 60个样本(去除异常点)
+
+# x = data.iloc[:,1:9]
+# print(x)
+
+# y = data.iloc[:,-2]
+# print(y)
+
+# # 为 x 赋予列名
+# x.columns = [
+#     'organic_matter',        # OM g/kg
+#     'chloride',              # CL g/kg
+#     'cec',                   # CEC cmol/kg
+#     'h_concentration',       # H+ cmol/kg
+#     'hn',                    # HN mg/kg
+#     'al_concentration',      # Al3+ cmol/kg
+#     'free_alumina',          # Free alumina g/kg
+#     'free_iron',             # Free iron oxides g/kg
+# ]
+
+# y.name = 'target_ph'
+
+## 数据集划分
+Xtrain, Xtest, Ytrain, Ytest = train_test_split(x, y, test_size=0.2, random_state=42)
+
+
+# 模型:使用 RandomForestRegressor 举例
+rfc = RandomForestRegressor(random_state=1)
+
+# 计算学习曲线
+train_sizes, train_scores, test_scores = learning_curve(
+    rfc,                         # 使用的模型
+    Xtrain,                           # 训练特征
+    Ytrain,                           # 训练目标
+    cv=5,                         # 交叉验证折数
+    n_jobs=-1,                    # 使用所有可用的CPU核心进行并行计算
+    train_sizes=np.linspace(0.1, 1.0, 10)  # 训练集大小,从10%到100%,共10个点
+)
+
+# 获取 test_scores(交叉验证测试集的得分)
+print("test_scores: \n", test_scores)
+print("train_scores: \n", train_scores)
+
+import matplotlib.pyplot as plt
+import numpy as np
+
+# 绘制学习曲线
+plt.figure(figsize=(8, 6))
+
+# 绘制训练误差和测试误差
+plt.plot(train_sizes, np.mean(train_scores, axis=1), label="Training score", color="r")
+plt.plot(train_sizes, np.mean(test_scores, axis=1), label="Cross-validation score", color="g")
+
+# 绘制图形的细节
+plt.title("Learning Curve (Random Forest Regressor)")
+plt.xlabel("Training Size (%)")
+plt.ylabel("Score (R²)")
+plt.legend(loc="best")
+plt.grid(True)
+plt.show()

BIN
api/pkl/default_model_0104_1718.pkl


BIN
api/pkl/default_model_0104_1720.pkl


BIN
api/pkl/default_model_0104_2141.pkl


BIN
api/pkl/rf_model_0104_0932.pkl


BIN
api/pkl/rf_model_0104_1145.pkl


BIN
api/pkl/rf_model_0104_1149.pkl


BIN
api/pkl/rf_model_0104_1237.pkl


BIN
api/pkl/rf_model_0104_1259.pkl


BIN
api/pkl/rf_model_0104_1415.pkl


BIN
api/pkl/rf_model_0104_1418.pkl


BIN
api/pkl/rf_model_0104_1420.pkl


BIN
api/pkl/rf_model_0104_2207.pkl


BIN
api/pkl/rf_model_0107_0052.pkl


BIN
api/pkl/rf_model_0107_0054.pkl


BIN
api/pkl/rf_model_0107_0123.pkl


BIN
api/pkl/rf_model_1229_2010.pkl


BIN
api/pkl/rf_model_1229_2014.pkl


+ 12 - 0
api/uploads/data/X_test_reduce.csv

@@ -0,0 +1,12 @@
+pH,OM,CL,H,Al
+4.49,26.291,165.8,0.98064,2.51676
+4.3,13.15412,185.2,0.5184,1.79928
+4.31,12.11972,185.2,0.57888,1.80144
+4.6,18.51576,60.4,0.82512,2.82888
+4.69,16.7228,373.6,0.98928,2.53944
+5.14,12.7,314.0,0.946,0.738
+4.19,26.23928,173.2,0.76473,2.76399
+4.35,13.80924,364.2,0.59184,1.56924
+4.77,11.2,80.0,0.724,0.647
+4.7,31.32508,140.8,0.81216,3.67182
+4.61,18.77436,60.4,0.82512,2.81844

+ 8 - 0
api/uploads/data/X_test_reflux.csv

@@ -0,0 +1,8 @@
+organic_matter,chloride,cec,h_concentration,hn,al_concentration
+11.4,570.6,6.21,3.12,0.07897,0.733
+26.291,165.8,8.49278,0.98064,0.11042,2.51676
+18.77436,60.4,5.53292,0.82512,0.08111,2.82888
+16.24008,373.6,12.72498,1.24416,0.01119,2.36808
+24.51528,367.2,6.62326,0.68688,0.11604,2.1006
+26.42892,165.8,8.58576,0.97632,0.11149,2.46888
+18.51576,60.4,6.10716,0.82512,0.08874,2.82

+ 12 - 0
api/uploads/data/Y_test_reduce.csv

@@ -0,0 +1,12 @@
+target
+0.12931
+0.066298
+0.057692
+0.072464
+0.146414
+0.087263
+0.096983
+0.070505
+0.052497
+0.166515
+0.063291

+ 8 - 0
api/uploads/data/Y_test_reflux.csv

@@ -0,0 +1,8 @@
+delta_ph
+0.37667
+0.59667
+0.66
+0.53333
+0.18
+0.52
+0.57

BIN
api/uploads/datasets/dataset_1.xlsx


BIN
api/uploads/datasets/dataset_34.xlsx


BIN
api/uploads/datasets/dataset_6.xlsx


BIN
api/uploads/datasets/dataset_7.xlsx


BIN
api/uploads/datasets/dataset_None.xlsx


+ 1 - 2
app.json

@@ -22,8 +22,7 @@
     "shoping/Soil Acid Reduction Iterative Evolution/Soil Acid Reduction Iterative Evolution",
     "shoping/Staff/Staff",
     "shoping/EditProfile/EditProfile",
-    "pages/Result/Result",
-    "pages/ModelTrain/ModelTrain"
+    "pages/Result/Result"
   ],
   "window": {
     "backgroundTextStyle": "light",

+ 8 - 8
pages/Visualizatio/Visualizatio.js

@@ -33,7 +33,7 @@ Page({
 
   LoadData: function() {
     wx.request({
-      url: 'http://localhost:5000/tables',
+      url: 'http://localhost:5000/table',
       method: 'POST',
       header: {
         'Content-Type': 'application/json'
@@ -47,13 +47,13 @@ Page({
       if (res.data && Array.isArray(res.data.rows)) {
         const rows = res.data.rows.map(row => {
           return {
-            'id': row[0],
-            'Q_over_b':  row[1],
-            'pH': row[2],
-            'OM': row[3],
-            'CL': row[4],
-            'H': row[5],
-            'Al': row[6]
+            'id': row.id,
+            'Q_over_b':  row.Q_over_b,
+            'pH': row.pH,
+            'OM': row.OM,
+            'CL': row.CL,
+            'H': row.H,
+            'Al': row.Al
           };
         });
 

+ 10 - 12
pages/Visualization/Visualization.js

@@ -25,7 +25,7 @@ Page({
     // 中文表头内容,动态生成
     tableHeaders: [
       "序号", "有机质含量", "土壤粘粒重量", "阳离子交换量", "氢离子含量",
-      "硝态氮含量","铝离子含量", "游离氧化铝", "游离氧化铁", "酸碱差值",
+      "硝态氮含量","铝离子含量", "酸碱差值",
     ]
   },
 
@@ -36,7 +36,7 @@ Page({
 
   LoadData: function() {
     wx.request({
-      url: 'http://localhost:5000/tables',
+      url: 'http://localhost:5000/table',
       method: 'POST',
       header: {
         'Content-Type': 'application/json'
@@ -50,16 +50,14 @@ Page({
       if (res.data && Array.isArray(res.data.rows)) {
         const rows = res.data.rows.map(row => {
           return {
-            'id': row[0],
-            'OM': row[1],
-            'CL': row[2],
-            'CEC': row[3],
-            'H_plus': row[4],
-            'HN': row[5],
-            'Al3_plus': row[6],
-            'free_alumina': row[7],
-            'free_iron_oxides': row[8],
-            'Delta_pH': row[9],
+            'id': row.id,
+            'OM': row.OM,
+            'CL': row.CL,
+            'CEC': row.CEC,
+            'H_plus': row.H_plus,
+            'HN': row.N,
+            'Al3_plus': row.Al3_plus,
+            'Delta_pH': row.Delta_pH,
           };
         });
 

+ 0 - 2
pages/Visualization/Visualization.wxml

@@ -43,8 +43,6 @@
         <view class="table-cell">{{item.H_plus}}</view>
         <view class="table-cell">{{item.HN}}</view>
         <view class="table-cell">{{item.Al3_plus}}</view>
-        <view class="table-cell">{{item.free_alumina}}</view>
-        <view class="table-cell">{{item.free_iron_oxides}}</view>
         <view class="table-cell">{{item.Delta_pH}}</view>
       </view>
     </block>

+ 3 - 0
pages/admin/admin.js

@@ -53,6 +53,9 @@ Page({
         const updatedUsers = { ...validUsers, [username]: password }; // 仅更新当前登录的用户名
         wx.setStorageSync('validUsers', updatedUsers); // 更新本地存储
 
+        // 缓存登录记录
+        wx.setStorageSync('currentUser', username); // 将当前登录用户名缓存
+
         wx.switchTab({ // 跳转到 tabBar 页面
           url: '/pages/threshold/threshold'
         });

+ 1 - 1
project.config.json

@@ -26,7 +26,7 @@
   },
   "compileType": "miniprogram",
   "libVersion": "3.7.1",
-  "appid": "wxc75e060d54a6f54c",
+  "appid": "wx9714bd9b9d4a11c9",
   "projectname": "tabBar",
   "simulatorType": "wechat",
   "simulatorPluginLibVersion": {},

+ 1 - 1
project.private.config.json

@@ -1,6 +1,6 @@
 {
   "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
-  "projectname": "tabBar",
+  "projectname": "tabBar3",
   "setting": {
     "compileHotReLoad": true,
     "urlCheck": false

+ 16 - 34
shoping/AcidNeutralizationModel/AcidNeutralizationModel.js

@@ -6,6 +6,8 @@ Page({
     CL: '', // 土壤粘粒重量
     H: '', // 氢离子含量
     Al: '', // 铝离子含量
+    init_pH: '',
+    target_pH: '',
     showModal: false, // 控制弹窗显示与隐藏
   },
 
@@ -35,38 +37,25 @@ Page({
       ph: e.detail.value,
     });
   },
-
-  // 页面加载时处理传递过来的数据(仅初始化,不提示错误)
-  onLoad: function (options) {
-    const encodedResult = options.result || ''; // 如果没有结果传递,设置为空字符串
-    if (encodedResult) {
-      try {
-        // 解码URL编码的字符串
-        const decodedResult = decodeURIComponent(encodedResult);
-        console.log('解码后的数据:', decodedResult);
-
-        // 将解码后的字符串解析为JSON对象
-        const resultArray = JSON.parse(decodedResult);
-
-        // 检查数组是否有效并显示第一个结果
-        if (Array.isArray(resultArray) && resultArray.length > 0) {
-          this.setData({
-            result: resultArray[0].toString(), // 显示第一个结果
-          });
-        }
-      } catch (error) {
-        console.error('解析结果失败:', error);
-      }
-    }
+  onInitPhChange: function (e) {
+    this.setData({
+      init_pH: e.detail.value,
+    });
+  },
+  onTargetPhChange: function (e) {
+    this.setData({
+      target_pH: e.detail.value,
+    });
   },
 
   // 点击按钮后进行计算并提示结果
   calculate: function () {
     console.log('开始计算...');
     const data = {
-      model_name: 'rf_model_1214_1008',
+      model_id: 6,
       parameters: {
-        pH: this.data.ph,
+        init_pH: this.data.init_pH,
+        target_pH: this.data.target_pH,
         OM: this.data.OM,
         CL: this.data.CL,
         H: this.data.H,
@@ -82,24 +71,17 @@ Page({
         'content-type': 'application/json',
       },
       success: (res) => {
-        console.log('预测结果:', res.data.predictions);
+        console.log('预测结果:', res.data.result);
 
         // 更新计算结果
-        if (Array.isArray(res.data.predictions) && res.data.predictions.length > 0) {
           this.setData({
-            result: res.data.predictions[0].toString(),
+            result: res.data.result.toString(),
             showModal: true, // 显示弹窗
           });
           wx.showToast({
             title: '计算完成!结果已更新',
             icon: 'success',
           });
-        } else {
-          wx.showToast({
-            title: '服务器返回数据格式有误',
-            icon: 'none',
-          });
-        }
       },
       fail: (error) => {
         console.error('请求失败:', error);

+ 18 - 5
shoping/AcidNeutralizationModel/AcidNeutralizationModel.wxml

@@ -1,13 +1,26 @@
 <view class="page-body">
   <view class="white-box">
     <view class="input-row">
-      <view class="page-section-title">酸碱度 pH:</view>
+      <view class="page-section-title">初始 pH:
+      </view>
       <input 
-        class="input-field" 
-        data-field="ph" 
+        class="input-field"  
+        placeholder="4~6" 
+        value="{{init_pH}}" 
+        bindinput="onInitPhChange" 
+      />
+    </view>
+  </view>
+
+  <view class="white-box">
+    <view class="input-row">
+      <view class="page-section-title">目标 pH:
+      </view>
+      <input 
+        class="input-field"  
         placeholder="4~6" 
-        value="{{ph}}" 
-        bindinput="onPhChange" 
+        value="{{target_pH}}" 
+        bindinput="onTargetPhChange" 
       />
     </view>
   </view>

+ 4 - 7
shoping/Calculation/Calculation.js

@@ -79,17 +79,14 @@ Page({
   calculate: function() {
     console.log('开始计算...');
     const data = {
-      model_name: "RF_filt",
+      model_id: 13,
       parameters: {
         organic_matter: this.data.OM,
         chloride: this.data.CL,
         cec: this.data.CEC,
         h_concentration: this.data.H,
-        hn: this.data.HN,
+        n: this.data.HN,
         al_concentration: this.data.Al,
-        free_alumina: this.data.free_alumina,
-        free_iron: this.data.free_iron_oxides,
-        delta_ph: this.data.delta_ph,
       },
     };
 
@@ -104,8 +101,8 @@ Page({
         console.log('预测结果:', res.data.predictions);
 
         // 直接更新页面的 result
-        if (res.data.predictions && Array.isArray(res.data.predictions)) {
-          const result = res.data.predictions[0] ? res.data.predictions[0].toString() : '无结果';
+        if (res.data.result && Array.isArray(res.data.result)) {
+          const result = res.data.result[0] ? res.data.result[0].toString() : '无结果';
           this.setData({
             result: result,
             showResultPopup: true, // 显示弹窗

+ 0 - 44
shoping/Calculation/Calculation.wxml

@@ -89,50 +89,6 @@
   </view>
 </view>
 
-<view class="page-body">
-  <view class="white-box">
-    <view class="input-row">
-    <view class="page-section-title">游离氧化铝 FA:</view>
-    <input 
-      class="input-field" 
-      type="text" 
-      placeholder="4~6" 
-      value="{{free_alumina}}" 
-      bindinput="onFreeAluminaChange" 
-    />
-  </view>
-  </view>
-</view>
-
-<view class="page-body">
-  <view class="white-box">
-    <view class="input-row">
-    <view class="page-section-title">游离氧化铁 FIO:</view>
-    <input 
-      class="input-field" 
-      type="text" 
-      placeholder="4~6" 
-      value="{{free_iron_oxides}}" 
-      bindinput="onFreeIronOxidesChange" 
-    />
-  </view>
-  </view>
-</view>
-
-<view class="page-body">
-  <view class="white-box">
-    <view class="input-row">
-    <view class="page-section-title">pH差值:</view>
-    <input 
-      class="input-field" 
-      type="text" 
-      placeholder="4~6" 
-      value="{{delta_ph}}" 
-      bindinput="onDeltaPhChange" 
-    />
-  </view>
-  </view>
-</view>
 
 <!-- 弹窗部分 -->
   <view wx:if="{{showResultPopup}}" class="modal-overlay">

+ 35 - 15
shoping/Home/Home.js

@@ -1,37 +1,57 @@
 // pages/Home/Home.js
 Page({
   data: {
-    selected:0
-  },
-  SoilPro() {
-    wx.navigateTo({
-      url: '/shoping/SoilPro/SoilPro'
-    });
+    selected: 0
   },
+
+  // 页面显示时的逻辑
   onShow: function() {
+    // 检查缓存中的登录用户
+    const currentUser = wx.getStorageSync('currentUser');
+    
+    // 如果用户已登录,跳转到 /pages/threshold/threshold 页面
+    if (currentUser) {
+      wx.switchTab({
+        url: '/pages/threshold/threshold'
+      });
+      return;  // 停止后续代码执行
+    }
+
+    // 如果没有登录,则正常显示页面内容
     if (typeof this.getTabBar === 'function' && this.getTabBar()) {
       this.getTabBar().setData({
-        selected: 0  //这个数字是当前页面在tabBar中list数组的索引
-      })
+        selected: 0  // 当前页面在tabBar中的索引
+      });
     }
+
     // 隐藏返回首页按钮
-    if(wx.hideHomeButton){
-     wx.hideHomeButton();
-}
-},
-  Overview: function () {
+    if (wx.hideHomeButton) {
+      wx.hideHomeButton();
+    }
+  },
+
+  // 其他跳转方法
+  SoilPro() {
+    wx.navigateTo({
+      url: '/shoping/SoilPro/SoilPro'
+    });
+  },
+
+  Overview: function() {
     wx.navigateTo({
       url: '/shoping/Overview/Overview'
     });
   },
+
   ResearchFindings() {
     wx.navigateTo({
       url: '/shoping/ResearchFindings/ResearchFindings'
     });
   },
-  Unit_Team_Profile () {
+
+  Unit_Team_Profile() {
     wx.navigateTo({
       url: '/shoping/Unit Team Profile/Unit Team Profile'
     });
   }
-})
+});

+ 0 - 6
shoping/Soil Acidification Iterative Evolution/Soil Acidification Iterative Evolution.wxml

@@ -8,10 +8,4 @@
   <view class="chart-container">
     <ec-canvas id="scatterChart" canvas-id="scatterChart" ec="{{ecScatter}}"></ec-canvas>
   </view>
-  <view class="chart-container">
-    <ec-canvas id="pieChart" canvas-id="pieChart" ec="{{ecPie}}"></ec-canvas>
-  </view>
-  <view class="chart-container">
-    <ec-canvas id="barChart" canvas-id="barChart" ec="{{ecBar}}"></ec-canvas>
-  </view>
 </view>

+ 12 - 12
shoping/Soil Acidification/Soil Acidification.wxml

@@ -1,27 +1,23 @@
 <view class="containe">
   <text class="regular-text">
     <text class="sub-title">土壤反酸模型使用注意说明</text>
-感谢您选择使用“广东省生态环境与土壤研究所”研发的反酸模型,该模型基于土壤理化性质和机器学习算法构建,为用户精准预测土壤反酸后的 pH 值。为确保模型的有效性和结果的准确性,请在使用过程中注意以下事项:
+    感谢您选择使用“广东省生态环境与土壤研究所”研发的反酸模型。该模型基于土壤理化性质和机器学习算法,能够预测土壤反酸后的 pH 值。为确保模型的有效性,请注意以下事项:
 
 <text class="sub-title">1. 数据输入要求</text>
 <text class="sub-title"> \n土壤理化参数:</text>
 <text class="sub-title">\n土壤粘粒含量(%):</text>请确保输入数据经过实验室精确测量。
-<text class="sub-title">交换性铝离子浓度(cmol/kg):</text>建议使用标准土壤分析方法。
+<text class="sub-title">交换性铝离子浓度(cmol/kg):</text>建议使用标准土壤分析方法进行测定
 <text class="sub-title">有机质含量(%):</text>数据应基于准确的土壤样品测试。
 <text class="sub-title">游离氧化铝(g/kg):</text>确保数据来源可靠,测定方法符合国家或行业标准。
-\n输入值必须在合理范围内,避免因异常数据导致模型预测不准确。
 
-<text class="sub-title">2. 预测结果解读</text>
-模型输出的 pH 值是基于当前输入数据计算的预测值,可能因土壤的特殊性而有一定误差。
-使用结果时,应结合其他实际土壤管理措施,避免仅依赖预测值做决策。
+输入值必须在合理范围内,不应有异常数据,避免影响预测结果的准确性。
+<text class="sub-title">\n2. 预测结果解读</text>
+预测结果为模型根据输入数据计算的 pH 值,因土壤特殊性可能会有一定误差。
+结果应与实际土壤管理措施结合使用,不应仅依赖预测值进行决策。
 
 <text class="sub-title">3. 不确定性分析</text>
-该模型适用于研究中所涉及的强酸性旱地土壤(pH &lt; 5.5),其他类型的土壤预测可能偏离实际情况。
-本模型的输入变量基于已筛选的关键相关指标,超出这些变量范围的数据可能导致不准确的预测结果。
-
-<text class="sub-title">4. 模型进化能力</text>
-<text class="sub-title">\n模型迭代:</text>我们的模型将会随着用户数据量的增加和多样化,逐步迭代进化,提升预测的准确性和泛化能力。
-<text class="sub-title">用户贡献:</text>您提交的真实数据和反馈将为模型的优化和进步提供宝贵的支持。
+本模型适用于强酸性旱地土壤(pH < 5.5),对于其他类型的土壤,预测结果可能偏离实际情况。
+模型基于已筛选的关键指标,输入超出这些范围的数据可能导致不准确的预测结果。
     </text>
 </view>
 
@@ -35,6 +31,10 @@
 <view class="containe">
   <view class="containes">
   <text class="regular-text">
+    <text class="sub-title">4. 模型进化能力</text>
+<text class="sub-title">\n模型迭代:</text>我们的模型将会随着用户数据量的增加和多样化,逐步迭代进化,提升预测的准确性和泛化能力。
+<text class="sub-title">用户贡献:</text>您提交的真实数据和反馈将为模型的优化和进步提供宝贵的支持。
+
 <text class="sub-title">5. 技术支持</text>
 如果在使用过程中遇到问题或对模型预测有疑问,请联系技术支持团队。
 邮箱:support@example.com

+ 1 - 1
shoping/Soil Acidification/Soil Acidification.wxss

@@ -48,7 +48,7 @@
 }
 
 .navigat-arrow {
-  padding: 10px 15px;
+  padding: 1px 15px;
   width: 650rpx;
   height: 350rpx;
 }

+ 9 - 9
shoping/Soil Deacidification/Soil Deacidification.wxml

@@ -1,7 +1,7 @@
 <view class="containe">
   <text class="regular-text">
     <text class="sub-title">降酸模型使用说明</text>
-    感谢您选择使用“广东省生态环境与土壤研究所”研发的降酸模型,该模型以实现提升土壤 pH 的目标,通过输入土壤起始 pH 和目标 pH,精准计算不同类型碱性物料的施用量(包括石灰石粉、生石灰、熟石灰、白云石、以及已知 CaO 和 MgO 含量的碱性物料),为确保模型的效果与使用体验,请注意以下事项:
+    该模型旨在帮助提升土壤 pH 值,通过输入土壤起始 pH 和目标 pH,精准计算不同类型碱性物料的施用量(如石灰石粉、生石灰、熟石灰、白云石等)。为确保模型效果与使用体验,请注意以下事项:
 
 <text class="sub-title">1. 数据输入要求</text>
 <text class="sub-title"> \n必填参数:</text>
@@ -11,16 +11,12 @@
 \n输入数据必须符合实际测量值,避免因数据异常或错误导致计算结果偏差。
 
 <text class="sub-title">2. 使用范围</text>
-• 本模型适用于酸性土壤的碱性物料施用量计算,特别适用于<text> pH&lt;5.5</text> 的耕地土壤。
+• 本模型适用于酸性土壤的碱性物料施用量计算,尤其适用于 pH < 5.5 的耕地土壤。
 • 其他类型的土壤(如碱性或中性土壤)可能不适用。
 
-<text class="sub-title">3. 模型进化能力</text>
-•	本模型将持续根据用户上传的真实数据优化和进化,提升预测精度和适用范围。
-•	用户的数据贡献将为改良土壤健康提供宝贵支持。
-
-<text class="sub-title">4. 注意事项</text>
-•	输入的土壤起始 pH 和目标 pH 差值(ΔpH)不宜过大,以免因物料施用量过多导致次生盐碱化等问题。
-•	在计算结果应用于实际田间施用时,应结合当地的农业实践和技术指导。
+<text class="sub-title">3. 注意事项</text>
+•	输入土壤起始 pH 和目标 pH 差值(ΔpH)不宜过大,避免因物料施用量过多导致次生盐碱化等问题。
+• 在实际田间施用时,应结合当地农业实践和技术指导,确保施用量和操作方式的适用性与安全性。
     </text>
 </view>
 
@@ -34,6 +30,10 @@
 <view class="containe">
   <view class="containes">
   <text class="regular-text">
+    <text class="sub-title">4. 模型进化能力</text>
+•	本模型将持续根据用户上传的真实数据优化和进化,提升预测精度和适用范围。
+•	用户的数据贡献将为改良土壤健康提供宝贵支持。
+
 <text class="sub-title">5. 技术支持</text>
 如果在使用过程中遇到问题或对模型预测有疑问,请联系技术支持团队。
 邮箱:support@example.com

+ 1 - 1
shoping/Soil Deacidification/Soil Deacidification.wxss

@@ -48,7 +48,7 @@
 }
 
 .navigat-arrow {
-  padding: 15px;
+  padding: 1px 15px;
   width: 650rpx;
   height: 350rpx;
 }