DmitrMakeev commited on
Commit
1055419
·
verified ·
1 Parent(s): 2ad3481

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +97 -168
app.py CHANGED
@@ -721,17 +721,29 @@ class NutrientCalculator:
721
  def __init__(self, volume_liters: float = 1.0):
722
  self.volume = volume_liters
723
  self.results: Dict[str, Dict[str, Any]] = {}
724
- self.target_profile = DEFAULT_VALUES['BASE_PROFILE'].copy()
725
- self.actual_profile = {k: 0.0 for k in DEFAULT_VALUES['BASE_PROFILE']}
 
726
  self.total_ec = 0.0
 
 
 
 
 
 
 
 
 
 
 
727
  self.compensation_weights = {
728
- 'KNO3': 0.5,
729
- 'CaNO3': 0.3,
730
- 'K2SO4': 0.2
731
  }
732
- self.fertilizers: Dict[str, Dict[str, float]] = {}
733
 
734
  def _label(self, element: str) -> str:
 
735
  labels = {
736
  'N (NO3-)': 'NO3',
737
  'N (NH4+)': 'NH4'
@@ -739,6 +751,7 @@ class NutrientCalculator:
739
  return labels.get(element, element)
740
 
741
  def set_compensation_weights(self, kno3_weight: float, cano3_weight: float, k2so4_weight: float):
 
742
  total = kno3_weight + cano3_weight + k2so4_weight
743
  self.compensation_weights = {
744
  'KNO3': kno3_weight / total,
@@ -746,84 +759,13 @@ class NutrientCalculator:
746
  'K2SO4': k2so4_weight / total
747
  }
748
 
749
- def _apply(self, fert_name: str, main_element: str, required_ppm: float):
750
- if required_ppm <= 0:
751
- return
752
-
753
- try:
754
- content = self.fertilizers[fert_name][main_element]
755
- grams = (required_ppm * self.volume) / (content * 1000)
756
-
757
- if fert_name not in self.results:
758
- self.results[fert_name] = {
759
- 'граммы': 0.0,
760
- 'миллиграммы': 0,
761
- 'вклад в EC': 0.0
762
- }
763
- for element in self.fertilizers[fert_name]:
764
- self.results[fert_name][f'внесет {self._label(element)}'] = 0.0
765
-
766
- self.results[fert_name]['граммы'] += grams
767
- self.results[fert_name]['миллиграммы'] = int(self.results[fert_name]['граммы'] * 1000)
768
-
769
- fert_ec = 0.0
770
- for element, percent in self.fertilizers[fert_name].items():
771
- added_ppm = (grams * percent * 1000) / self.volume
772
- self.results[fert_name][f'внесет {self._label(element)}'] += added_ppm
773
- self.actual_profile[element] += added_ppm
774
- fert_ec += added_ppm * DEFAULT_VALUES['EC_COEFFICIENTS'].get(element, 0.0015)
775
-
776
- self.results[fert_name]['вклад в EC'] += fert_ec
777
- self.total_ec += fert_ec
778
-
779
- except KeyError as e:
780
- logger.error(f"Key error in _apply: {str(e)}")
781
- raise
782
-
783
- def _balance_nitrogen_with_compensation(self):
784
- try:
785
- # Вносим NH4+
786
- nh4_needed = self.target_profile['N (NH4+)'] - self.actual_profile['N (NH4+)']
787
- if nh4_needed > 0.1:
788
- self._apply("Аммоний азотнокислый", "N (NH4+)", nh4_needed)
789
-
790
- # Вносим NO3- с компенсацией
791
- no3_needed = self.target_profile['N (NO3-)'] - self.actual_profile['N (NO3-)']
792
- if no3_needed > 0.1:
793
- # Через CaNO3 (если нужен Ca)
794
- ca_part = self.compensation_weights['CaNO3'] * no3_needed
795
- ca_needed = self.target_profile['Ca'] - self.actual_profile['Ca']
796
- if ca_needed > 0.1:
797
- max_ca_no3 = (ca_needed / self.fertilizers["Кальциевая селитра"]["Ca"]) * \
798
- self.fertilizers["Кальциевая селитра"]["N (NO3-)"]
799
- ca_no3_part = min(ca_part, max_ca_no3)
800
- self._apply("Кальциевая селитра", "N (NO3-)", ca_no3_part)
801
- no3_needed -= ca_no3_part
802
-
803
- # Через KNO3 (если нужен K)
804
- kno3_part = self.compensation_weights['KNO3'] * no3_needed
805
- k_remaining = self.target_profile['K'] - self.actual_profile['K']
806
- if k_remaining > 0.1:
807
- max_k_no3 = (k_remaining / self.fertilizers["Калий азотнокислый"]["K"]) * \
808
- self.fertilizers["Калий азотнокислый"]["N (NO3-)"]
809
- k_no3_part = min(kno3_part, max_k_no3)
810
- self._apply("Калий азотнокислый", "N (NO3-)", k_no3_part)
811
- no3_needed -= k_no3_part
812
-
813
- # Остаток через K2SO4 (если разрешено весами)
814
- if no3_needed > 0.1 and self.compensation_weights['K2SO4'] > 0:
815
- pass # Логика компенсации через K2SO4
816
-
817
- except Exception as e:
818
- logger.error(f"Error in _balance_nitrogen_with_compensation: {str(e)}")
819
- raise
820
-
821
  def calculate(self) -> Dict[str, Any]:
 
822
  try:
823
  # 1. Вносим Mg и S
824
  self._apply("Сульфат магния", "Mg", self.target_profile['Mg'])
825
 
826
- # 2. Балансируем азот
827
  self._balance_nitrogen_with_compensation()
828
 
829
  # 3. Вносим Ca (остаток)
@@ -836,7 +778,7 @@ class NutrientCalculator:
836
  if p_needed > 0.1:
837
  self._apply("Монофосфат калия", "P", p_needed)
838
 
839
- # 5. Корректируем K
840
  k_needed = self.target_profile['K'] - self.actual_profile['K']
841
  if k_needed > 0.1:
842
  self._apply("Калий сернокислый", "K", k_needed)
@@ -844,19 +786,18 @@ class NutrientCalculator:
844
  return self.results
845
 
846
  except Exception as e:
847
- logger.error(f"Calculation error: {str(e)}")
848
  raise
849
 
850
- def calculate_ec(self) -> float:
851
- return round(self.total_ec, 2)
852
 
853
  def round_floats(obj: Union[float, Dict, List], ndigits: int = 3) -> Union[float, Dict, List]:
854
- """Рекурсивно округляет float значения"""
855
  if isinstance(obj, float):
856
  return round(obj, ndigits)
857
  elif isinstance(obj, dict):
858
  return {k: round_floats(v, ndigits) for k, v in obj.items()}
859
- elif isinstance(obj, list):
860
  return [round_floats(x, ndigits) for x in obj]
861
  return obj
862
 
@@ -864,113 +805,101 @@ def round_floats(obj: Union[float, Dict, List], ndigits: int = 3) -> Union[float
864
  def handle_calculation():
865
  try:
866
  data = request.get_json()
867
- logger.info(f"Received request data: {data}")
868
 
869
- # Валидация базовой структуры
870
- if not data or not isinstance(data, dict):
871
- logger.error("Invalid JSON format")
872
- return jsonify({'error': 'Invalid JSON format'}), 400
873
 
874
- # Проверка обязательных разделов
875
- required_sections = ['fertilizerConstants', 'profileSettings']
876
- for section in required_sections:
877
- if section not in data or not isinstance(data[section], dict):
878
- logger.error(f"Missing or invalid section: {section}")
879
- return jsonify({'error': f'Missing or invalid {section}'}), 400
880
-
881
- # Извлечение данных
882
- try:
883
- rounding_precision = int(data['profileSettings'].get('rounding_precision', 3))
884
- volume_liters = float(data['profileSettings'].get('liters', DEFAULT_VALUES['VOLUME_LITERS']))
885
- total_nitrogen = float(data['profileSettings'].get('TOTAL_NITROG', DEFAULT_VALUES['TOTAL_NITROGEN']))
886
- no3_ratio = float(data['profileSettings'].get('NO3_RAT', DEFAULT_VALUES['NO3_RATIO']))
887
- nh4_ratio = DEFAULT_VALUES['NH4_RATIO']
888
- except (ValueError, TypeError) as e:
889
- logger.error(f"Invalid numeric value: {str(e)}")
890
- return jsonify({'error': f'Invalid numeric value: {str(e)}'}), 400
891
-
892
- # Проверка удобрений
893
- required_fertilizers = {
894
- "Кальциевая селитра": ["N (NO3-)", "Ca"],
895
- "Калий азотнокислый": ["N (NO3-)", "K"],
896
- "��ммоний азотнокислый": ["N (NO3-)", "N (NH4+)"],
897
- "Сульфат магния": ["Mg", "S"],
898
- "Монофосфат калия": ["P", "K"],
899
- "Калий сернокислый": ["K", "S"]
900
- }
901
 
902
- for fert, elements in required_fertilizers.items():
903
- if fert not in data['fertilizerConstants']:
904
- logger.error(f"Missing fertilizer: {fert}")
905
- return jsonify({'error': f'Missing fertilizer: {fert}'}), 400
906
- for element in elements:
907
- if element not in data['fertilizerConstants'][fert]:
908
- logger.error(f"Missing element {element} in {fert}")
909
- return jsonify({'error': f'Missing element {element} in {fert}'}), 400
910
-
911
- # Создание калькулятора
912
- calculator = NutrientCalculator(volume_liters=volume_liters)
913
- calculator.fertilizers = data['fertilizerConstants']
914
 
915
- # Установка целевого профиля
916
  target_profile = {
917
- 'P': float(data['profileSettings'].get('P', DEFAULT_VALUES['BASE_PROFILE']['P'])),
918
- 'K': float(data['profileSettings'].get('K', DEFAULT_VALUES['BASE_PROFILE']['K'])),
919
- 'Mg': float(data['profileSettings'].get('Mg', DEFAULT_VALUES['BASE_PROFILE']['Mg'])),
920
- 'Ca': float(data['profileSettings'].get('Ca', DEFAULT_VALUES['BASE_PROFILE']['Ca'])),
921
- 'S': float(data['profileSettings'].get('S', DEFAULT_VALUES['BASE_PROFILE']['S'])),
922
- 'N (NO3-)': total_nitrogen * (no3_ratio / (no3_ratio + nh4_ratio)),
923
- 'N (NH4+)': total_nitrogen * (nh4_ratio / (no3_ratio + nh4_ratio))
924
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
925
  calculator.target_profile = target_profile
926
-
927
- # Установка весов компенсации (��сли есть)
928
- if 'compensationWeights' in data['profileSettings']:
929
- try:
930
- weights = data['profileSettings']['compensationWeights']
931
- calculator.set_compensation_weights(
932
- float(weights.get('KNO3', 0.5)),
933
- float(weights.get('CaNO3', 0.3)),
934
- float(weights.get('K2SO4', 0.2))
935
- )
936
- except Exception as e:
937
- logger.warning(f"Invalid compensation weights: {str(e)}")
938
-
939
- # Выполнение расчета
940
- try:
941
- results = calculator.calculate()
942
- except Exception as e:
943
- logger.error(f"Calculation failed: {str(e)}\n{traceback.format_exc()}")
944
- return jsonify({'error': 'Calculation failed'}), 500
945
-
946
- # Формирование ответа
947
  response = {
948
  'actual_profile': calculator.actual_profile,
949
  'fertilizers': results,
950
  'total_ec': calculator.calculate_ec(),
951
  'total_ppm': sum(calculator.actual_profile.values()),
952
  'nitrogen_ratios': {
953
- 'NO3_RATIO': no3_ratio,
954
- 'NH4_RATIO': nh4_ratio,
955
- 'TOTAL_NITROGEN': total_nitrogen
956
  }
957
  }
958
-
959
- # Округление значений
960
  rounded_response = round_floats(response, rounding_precision)
961
 
962
- # Коррекция миллиграммов
963
  if 'fertilizers' in rounded_response:
964
  for fert in rounded_response['fertilizers'].values():
965
  if 'миллиграммы' in fert:
966
  fert['миллиграммы'] = int(round(fert['граммы'] * 1000))
967
-
968
- logger.info("Calculation completed successfully")
969
  return jsonify(rounded_response)
970
 
971
  except Exception as e:
972
- logger.error(f"Server error: {str(e)}\n{traceback.format_exc()}")
973
- return jsonify({'error': 'Internal server error'}), 500
974
 
975
  if __name__ == '__main__':
976
  app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 7860)))
 
721
  def __init__(self, volume_liters: float = 1.0):
722
  self.volume = volume_liters
723
  self.results: Dict[str, Dict[str, Any]] = {}
724
+ self.target_profile = BASE_PROFILE.copy()
725
+ self.actual_profile = {k: 0.0 for k in BASE_PROFILE}
726
+ self.fertilizers = NUTRIENT_CONTENT_IN_FERTILIZERS
727
  self.total_ec = 0.0
728
+
729
+ # Расчет азота
730
+ total_parts = NO3_RATIO + NH4_RATIO
731
+ self.target_profile['N (NO3-)'] = TOTAL_NITROGEN * (NO3_RATIO / total_parts)
732
+ self.target_profile['N (NH4+)'] = TOTAL_NITROGEN * (NH4_RATIO / total_parts)
733
+ self.initial_n_profile = {
734
+ "NO3-": self.target_profile['N (NO3-)'],
735
+ "NH4+": self.target_profile['N (NH4+)']
736
+ }
737
+
738
+ # Настройки компенсации по умолчанию
739
  self.compensation_weights = {
740
+ 'KNO3': 0.5, # Вес калийной селитры (0-1)
741
+ 'CaNO3': 0.3, # Вес кальциевой селитры (0-1)
742
+ 'K2SO4': 0.2 # Вес сульфата калия (0-1)
743
  }
 
744
 
745
  def _label(self, element: str) -> str:
746
+ """Форматирование названий элементов для вывода"""
747
  labels = {
748
  'N (NO3-)': 'NO3',
749
  'N (NH4+)': 'NH4'
 
751
  return labels.get(element, element)
752
 
753
  def set_compensation_weights(self, kno3_weight: float, cano3_weight: float, k2so4_weight: float):
754
+ """Установка весов для компенсации элементов"""
755
  total = kno3_weight + cano3_weight + k2so4_weight
756
  self.compensation_weights = {
757
  'KNO3': kno3_weight / total,
 
759
  'K2SO4': k2so4_weight / total
760
  }
761
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
762
  def calculate(self) -> Dict[str, Any]:
763
+ """Основной метод расчета с новой логикой"""
764
  try:
765
  # 1. Вносим Mg и S
766
  self._apply("Сульфат магния", "Mg", self.target_profile['Mg'])
767
 
768
+ # 2. Балансируем азот с учетом компенсации
769
  self._balance_nitrogen_with_compensation()
770
 
771
  # 3. Вносим Ca (остаток)
 
778
  if p_needed > 0.1:
779
  self._apply("Монофосфат калия", "P", p_needed)
780
 
781
+ # 5. Корректируем K через сульфат калия (если остался дефицит)
782
  k_needed = self.target_profile['K'] - self.actual_profile['K']
783
  if k_needed > 0.1:
784
  self._apply("Калий сернокислый", "K", k_needed)
 
786
  return self.results
787
 
788
  except Exception as e:
789
+ print(f"Ошибка при расчёте: {str(e)}")
790
  raise
791
 
792
+ # ... остальные методы класса NutrientCalculator ...
 
793
 
794
  def round_floats(obj: Union[float, Dict, List], ndigits: int = 3) -> Union[float, Dict, List]:
795
+ """Рекурсивно округляет все float значения в структуре данных"""
796
  if isinstance(obj, float):
797
  return round(obj, ndigits)
798
  elif isinstance(obj, dict):
799
  return {k: round_floats(v, ndigits) for k, v in obj.items()}
800
+ elif isinstance(obj, (list, tuple)):
801
  return [round_floats(x, ndigits) for x in obj]
802
  return obj
803
 
 
805
  def handle_calculation():
806
  try:
807
  data = request.get_json()
 
808
 
809
+ # Получаем параметр точности округления (по умолчанию 3)
810
+ rounding_precision = int(data['profileSettings'].get('rounding_precision', 3))
 
 
811
 
812
+ # Проверка обязательных полей
813
+ if not data or 'fertilizerConstants' not in data or 'profileSettings' not in data:
814
+ return jsonify({'error': 'Неверный формат данных'}), 400
815
+
816
+ # Извлекаем данные из запроса
817
+ fertilizer_data = data['fertilizerConstants']
818
+ profile_data = data['profileSettings']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
819
 
820
+ # Устанавливаем константы из запроса
821
+ TOTAL_NITROGEN = float(profile_data.get('TOTAL_NITROG', 125.0))
822
+ NO3_RATIO = float(profile_data.get('NO3_RAT', 8.25))
823
+ VOLUME_LITERS = float(profile_data.get('liters', 100))
824
+ NH4_RATIO = 1.00 # Фиксированное значение
 
 
 
 
 
 
 
825
 
826
+ # Формируем целевой профиль
827
  target_profile = {
828
+ 'P': float(profile_data.get('P', 31.0)),
829
+ 'K': float(profile_data.get('K', 210.0)),
830
+ 'Mg': float(profile_data.get('Mg', 24.0)),
831
+ 'Ca': float(profile_data.get('Ca', 84.0)),
832
+ 'S': float(profile_data.get('S', 56.439)),
833
+ 'N (NO3-)': 0, # Будет рассчитано в калькуляторе
834
+ 'N (NH4+)': 0 # Будет рассчитано в калькуляторе
835
  }
836
+
837
+ # Обновляем константы удобрений
838
+ NUTRIENT_CONTENT_IN_FERTILIZERS = {
839
+ "Кальциевая селитра": {
840
+ "N (NO3-)": float(fertilizer_data["Кальциевая селитра"].get("N (NO3-)", 0.11863)),
841
+ "Ca": float(fertilizer_data["Кальциевая селитра"].get("Ca", 0.16972))
842
+ },
843
+ "Калий азотнокислый": {
844
+ "N (NO3-)": float(fertilizer_data["Калий азотнокислый"].get("N (NO3-)", 0.13854)),
845
+ "K": float(fertilizer_data["Калий азотнокислый"].get("K", 0.36672))
846
+ },
847
+ "Аммоний азотнокислый": {
848
+ "N (NO3-)": float(fertilizer_data["Аммоний азотнокислый"].get("N (NO3-)", 0.17499)),
849
+ "N (NH4+)": float(fertilizer_data["Аммоний азотнокислый"].get("N (NH4+)", 0.17499))
850
+ },
851
+ "Сульфат магния": {
852
+ "Mg": float(fertilizer_data["Сульфат магния"].get("Mg", 0.1022)),
853
+ "S": float(fertilizer_data["Сульфат магния"].get("S", 0.13483))
854
+ },
855
+ "Монофосфат калия": {
856
+ "P": float(fertilizer_data["Монофосфат калия"].get("P", 0.22761)),
857
+ "K": float(fertilizer_data["Монофосфат калия"].get("K", 0.28731))
858
+ },
859
+ "Калий сернокислый": {
860
+ "K": float(fertilizer_data["Калий сернокислый"].get("K", 0.44874)),
861
+ "S": float(fertilizer_data["Калий сернокислый"].get("S", 0.18401))
862
+ }
863
+ }
864
+
865
+ # Создаем и настраиваем калькулятор
866
+ calculator = NutrientCalculator(volume_liters=VOLUME_LITERS)
867
  calculator.target_profile = target_profile
868
+ calculator.fertilizers = NUTRIENT_CONTENT_IN_FERTILIZERS
869
+
870
+ # Устанавливаем параметры азота
871
+ calculator.target_profile['N (NO3-)'] = TOTAL_NITROGEN * (NO3_RATIO / (NO3_RATIO + NH4_RATIO))
872
+ calculator.target_profile['N (NH4+)'] = TOTAL_NITROGEN * (NH4_RATIO / (NO3_RATIO + NH4_RATIO))
873
+
874
+ # Выполняем расчет
875
+ results = calculator.calculate()
876
+
877
+ # Формируем ответ
 
 
 
 
 
 
 
 
 
 
 
878
  response = {
879
  'actual_profile': calculator.actual_profile,
880
  'fertilizers': results,
881
  'total_ec': calculator.calculate_ec(),
882
  'total_ppm': sum(calculator.actual_profile.values()),
883
  'nitrogen_ratios': {
884
+ 'NO3_RATIO': NO3_RATIO,
885
+ 'NH4_RATIO': NH4_RATIO,
886
+ 'TOTAL_NITROGEN': TOTAL_NITROGEN
887
  }
888
  }
889
+
890
+ # Округляем все числовые значения
891
  rounded_response = round_floats(response, rounding_precision)
892
 
893
+ # Для миллиграммов применяем целочисленное округление
894
  if 'fertilizers' in rounded_response:
895
  for fert in rounded_response['fertilizers'].values():
896
  if 'миллиграммы' in fert:
897
  fert['миллиграммы'] = int(round(fert['граммы'] * 1000))
898
+
 
899
  return jsonify(rounded_response)
900
 
901
  except Exception as e:
902
+ return jsonify({'error': str(e)}), 500
 
903
 
904
  if __name__ == '__main__':
905
  app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 7860)))