Исправления:

- Добавлен столбец 'Разбавление (x)' для каждого реагента
- Переработана логика расчёта с учётом разбавления
- Исправлен расчёт количества растворителя
- Растворитель отображается в первой строке таблицы
- Поддержка дробных значений разбавления
This commit is contained in:
2026-05-05 22:31:08 +05:00
parent cde52d1123
commit 6798fd5f63
5 changed files with 467 additions and 277 deletions
+140 -100
View File
@@ -1,150 +1,190 @@
import json
from reagent import Reagent
VOLUME_UNITS = {
'нл': 0.001, # 1 нл = 0.001 мкл
'мкл': 1.0, # базовая единица объёма
'мл': 1000.0, # 1 мл = 1000 мкл
'л': 1000000.0 # 1 л = 1 000 000 мкл
'мл': 1000.0, # 1 мл = 1000 мкл
'л': 1000000.0 # 1 л = 1 000 000 мкл
}
MASS_UNITS = {
'нг': 0.000001, # 1 нг = 0.001 мкг
'мкг': 0.001, # базовая единица массы
'мг': 1.0, # 1 мг = 1000 мкг
'г': 1000.0, # 1 г = 1 000 000 мкг
'кг': 1000000.0 # 1 кг = 1 000 000 000 мкг
'мкг': 0.001, # базовая единица массы
'мг': 1.0, # 1 мг = 1000 мкг
'г': 1000.0, # 1 г = 1 000 000 мкг
'кг': 1000000.0 # 1 кг = 1 000 000 000 мкг
}
class Reagent:
def __init__(self, name: str, percentage: float, unit: str, conversion_factor: float = 1.0):
self.name = name
self.percentage = percentage
self.unit = unit
self.conversion_factor = conversion_factor
def to_dict(self):
"""Преобразует реагент в словарь для сохранения в JSON"""
return {
'name': self.name,
'percentage': self.percentage,
'unit': self.unit,
'conversion_factor': self.conversion_factor
}
@classmethod
def from_dict(cls, data):
"""Создаёт реагент из словаря, загруженного из JSON"""
return cls(
name=data['name'],
percentage=data['percentage'],
unit=data['unit'],
conversion_factor=data['conversion_factor']
)
class Model:
def __init__(self):
self.reagents = []
self.total_amount = 1000.0 # по умолчанию 1000 мл
self.amount_unit = "мл"
self.solvent = "Вода"
def convert_amount(self, amount_mkl_or_mkg: float, target_unit: str, is_volume: bool) -> float:
self.total_amount = 100.0 # общее количество среды (по умолчанию — 1000 мл)
self.amount_unit = 'мл' # единица измерения общего количества
self.solvent = 'Вода' # растворитель по умолчанию
self.reagents = [] # список реагентов
def convert_amount(self, amount_base: float, target_unit: str, is_volume: bool) -> float:
"""
Пересчитывает количество из базовых единиц (мкл/мкг) в целевую единицу.
Args:
amount_mkl_or_mkg: количество в базовых единицах (мкл или мкг)
target_unit: целевая единица измерения
is_volume: True — объём, False — масса
Returns:
Количество в целевой единице
"""
if is_volume:
conversion_factor = VOLUME_UNITS.get(target_unit, 1.0)
else:
conversion_factor = MASS_UNITS.get(target_unit, 1.0)
return amount_mkl_or_mkg / conversion_factor
return amount_base / conversion_factor
def add_reagent(self, reagent: Reagent):
"""Добавляет реагент в список"""
self.reagents.append(reagent)
def calculate_amounts(self) -> list:
def calculate_amounts(self) -> tuple:
"""
Рассчитывает абсолютное количество каждого реагента с учётом единиц измерения.
Возвращает список количеств в единицах измерения реагента.
Рассчитывает:
1. Исходное количество реагента для неразбавленного состава
2. Количество разбавленного реагента, которое нужно взять
3. Количество растворителя с учётом добавленных разбавленных реагентов
Возвращает: (список количеств разбавленных реагентов, количество растворителя, процент растворителя)
"""
results = []
# Проверяем, есть ли реагенты
if not self.reagents:
return results, self.total_amount, 100.0
# Суммируем проценты всех реагентов
total_percentage = sum(reagent.percentage for reagent in self.reagents)
# Проверяем, что сумма процентов не превышает 100
if total_percentage > 100:
raise ValueError(f"Сумма процентов реагентов ({total_percentage:.2f}%) превышает 100%")
# Шаг 1: Рассчитываем общий объём среды в базовых единицах (мкл)
total_in_base = self.total_amount * VOLUME_UNITS[self.amount_unit]
# Шаг 2: Рассчитываем количество каждого реагента для неразбавленного состава
undiluted_amounts = []
for reagent in self.reagents:
# Базовый расчёт в процентах от общего количества
base_amount = (reagent.percentage / 100) * self.total_amount
# Расчёт количества реагента в базовых единицах
amount_in_base = (reagent.percentage / 100) * total_in_base
# Определяем, объём или масса
# Определение типа единицы измерения реагента
is_volume = reagent.unit in VOLUME_UNITS
# Применение коэффициента пересчёта
adjusted_amount = amount_in_base * reagent.conversion_factor
# Пересчёт в целевую единицу измерения
final_amount = self.convert_amount(adjusted_amount, reagent.unit, is_volume)
undiluted_amounts.append(final_amount)
# Шаг 3: Рассчитываем количество разбавленного реагента, которое нужно взять
# и общий объём вносимых разбавленных реагентов (в базовых единицах)
diluted_amounts = []
total_diluted_volume_base = 0
for i, reagent in enumerate(self.reagents):
dilution_factor = getattr(reagent, 'dilution_factor', 1.0)
if dilution_factor <= 0:
dilution_factor = 1.0
# Количество разбавленного реагента = исходное количество / фактор разбавления
diluted_amount = undiluted_amounts[i] * dilution_factor
diluted_amounts.append(diluted_amount)
# Вычисляем объём разбавленного реагента в базовых единицах (мкл)
is_volume = reagent.unit in VOLUME_UNITS
is_mass = reagent.unit in MASS_UNITS
if is_volume:
# Переводим общее количество среды в мкл для расчёта
total_in_mkl = self.total_amount * VOLUME_UNITS[self.amount_unit]
# Расчёт в мкл
amount_in_mkl = (reagent.percentage / 100) * total_in_mkl
# Переводим обратно в единицы реагента
final_amount = self.convert_amount(amount_in_mkl, reagent.unit, True)
elif is_mass:
# Переводим общее количество среды в мкг для расчёта (если среда в единицах объёма,
# предполагаем плотность = 1 г/мл)
if self.amount_unit in VOLUME_UNITS:
# Предполагаем плотность 1 г/мл: 1 мл ≈ 1 г
total_in_mkg = self.total_amount * VOLUME_UNITS[self.amount_unit] # в мкл
total_in_mkg *= 1000 # переводим в мкг (1 мкл воды ≈ 1 мкг)
else:
total_in_mkg = self.total_amount * MASS_UNITS[self.amount_unit]
# Расчёт в мкг
amount_in_mkg = (reagent.percentage / 100) * total_in_mkg
# Переводим в единицы реагента
final_amount = self.convert_amount(amount_in_mkg, reagent.unit, False)
# Если реагент в объёмных единицах, его объём = diluted_amount
reagent_volume_base = diluted_amount * VOLUME_UNITS[reagent.unit]
else:
# Если единица неизвестна, возвращаем базовый расчёт
final_amount = base_amount
# Если реагент в массовых единицах, считаем, что он вносит пренебрежимый объём
# или можно добавить коэффициент плотности, пока игнорируем
reagent_volume_base = 0
results.append(final_amount)
return results
total_diluted_volume_base += reagent_volume_base
# Шаг 4: Рассчитываем количество растворителя
# Растворитель = общий объём - объём всех разбавленных реагентов
solvent_volume_base = total_in_base - total_diluted_volume_base
def get_solvent_info(self) -> dict:
"""Возвращает информацию о растворителе: процент и количество"""
total_percentage = sum(r.percentage for r in self.reagents)
solvent_percentage = max(0, 100 - total_percentage) # не меньше 0%
solvent_amount = (solvent_percentage / 100) * self.total_amount
return {
'percentage': solvent_percentage,
'amount': solvent_amount,
'unit': self.amount_unit
}
# Переводим количество растворителя в целевую единицу измерения
solvent_amount = solvent_volume_base / VOLUME_UNITS[self.amount_unit]
# Если объём реагентов превышает общий объём, корректируем
if solvent_amount < 0:
solvent_amount = 0
# Процент растворителя в неразбавленном составе
solvent_percentage = 100 - total_percentage
return diluted_amounts, solvent_amount, solvent_percentage
def save_to_file(self, filename: str):
"""Сохраняет состав среды в JSONфайл"""
"""Сохраняет модель в JSON-файл"""
data = {
'total_amount': self.total_amount,
'amount_unit': self.amount_unit,
'solvent': self.solvent,
'reagents': [r.to_dict() for r in self.reagents]
'reagents': [
{
'name': r.name,
'percentage': r.percentage,
'unit': r.unit,
'conversion_factor': r.conversion_factor,
'dilution_factor': getattr(r, 'dilution_factor', 1.0)
} for r in self.reagents
]
}
with open(filename, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
json.dump(data, f, ensure_ascii=False, indent=4)
def load_from_file(self, filename: str):
"""Загружает состав среды из JSONфайла"""
"""Загружает модель из JSON-файла"""
with open(filename, 'r', encoding='utf-8') as f:
data = json.load(f)
# Восстанавливаем данные модели
self.total_amount = data['total_amount']
self.amount_unit = data['amount_unit']
self.solvent = data['solvent']
# Очищаем и заполняем список реагентов
self.reagents.clear()
for reagent_data in data['reagents']:
reagent = Reagent.from_dict(reagent_data)
for r_data in data['reagents']:
reagent = Reagent(
name=r_data['name'],
percentage=r_data['percentage'],
unit=r_data['unit'],
conversion_factor=r_data.get('conversion_factor', 1.0)
)
# Добавляем коэффициент разбавления
reagent.dilution_factor = r_data.get('dilution_factor', 1.0)
self.reagents.append(reagent)
def add_reagent(self, name: str, percentage: float, unit: str, conversion_factor: float = 1.0,
dilution_factor: float = 1.0):
"""Добавляет новый реагент в модель"""
reagent = Reagent(name, percentage, unit, conversion_factor)
reagent.dilution_factor = dilution_factor
self.reagents.append(reagent)
def remove_reagent(self, index: int):
"""Удаляет реагент по индексу"""
if 0 <= index < len(self.reagents):
del self.reagents[index]
def clear_reagents(self):
"""Очищает список реагентов"""
self.reagents.clear()
def get_reagent_count(self) -> int:
"""Возвращает количество реагентов в модели"""
return len(self.reagents)
def set_total_amount(self, amount: float, unit: str):
"""Устанавливает общее количество среды и единицу измерения"""
self.total_amount = amount
self.amount_unit = unit
def set_solvent(self, solvent_name: str):
"""Устанавливает название растворителя"""
self.solvent = solvent_name