```
import hashlib

from langdetect import detect, LangDetectException
from pymongo import MongoClient
from app.translate_loader import translate
from app.ner_loader import ner
from tqdm import tqdm
import numpy as np
import argparse
import random
import json


class InstructConverter:
    CONF_PROMPTS_PER_PRODUCT = 4
    # CONF_DIGITS_PRECISION = 5
    CONF_DIGITS_PRECISION = 2


    CONF_NUTRIENT_DICT = {'proteins': 'prots', 'fats': 'fats', 'carbohydrates': 'carbs'}
    def __init__(self):
        CONNECTION_STRING = 'mongodb+srv://doadmin:7pfg65eH480k2wl3@db-mongodb-fibert-data-lake-1a57fff3.mongo.ondigitalocean.com/admin?tls=true&authSource=admin&replicaSet=db-mongodb-fibert-data-lake'
        client = MongoClient(CONNECTION_STRING)
        self.db = client['nutrition_database']['products']

        self.user_prompts = []
        with open('./resources/static/data/food_requests.txt', 'r', encoding="utf-8") as file:
            for line in file:
                self.user_prompts.append(line.strip())

        self.cache = {}

    def replace_entities(self, input_string, product_names):
        ner_entities = self.ner_cached(input_string)
        if len(product_names) < len(ner_entities):
            return input_string

        generated_string = input_string[:]
        for i, entity in enumerate(ner_entities):
            generated_string = generated_string.replace(entity, product_names[i])

        return generated_string

    def count_entities(self, input_string):
        translated_string = translate(input_string)
        ner_entities = self.ner_cached(translated_string)
        return len(ner_entities)

    def ner_cached(self, input_string):
        input_tag = hashlib.md5(input_string.encode()).hexdigest()
        if input_tag in self.cache:
            return self.cache[input_tag]
        else:
            result = ner(input_string)
            self.cache[input_tag] = result
            return result

    def convert_ss(self, w=0.95, k=0.99):
        '''
        :param k:
        :param w: коэфицент неточности веса
        '''
        # user_prompts = []
        # with open('./resources/static/data/food_requests.txt', 'r', encoding="utf-8") as file:
        #     for user_prompt in file:
        #         try:
        #             if self.count_entities(user_prompt) == 1:
        #                 user_prompts.append(user_prompt)
        #         except ValueError:
        #             continue

        with open('./resources/static/data/train1_instruct_ss.jsonl', 'w') as f:
            for i, product in tqdm(enumerate(self.db.find())):
                random_user_prompts = np.random.choice(self.user_prompts, self.CONF_PROMPTS_PER_PRODUCT)
                for j, user_prompt in enumerate(random_user_prompts):

                    try:
                        if detect(user_prompt) != "en":
                            continue
                        if self.count_entities(user_prompt) != 1:
                            continue
                    except LangDetectException:
                        continue

                    product_name = product['product_name']
                    weight_rand = round(random.random() * 150 + 50, self.CONF_DIGITS_PRECISION)
                    weight_templates = [
                        f' {weight_rand} g',
                        f' {weight_rand} grams',
                        f' {weight_rand} grams of product',
                        f' weight: {weight_rand} g'
                    ]
                    product_name += random.choice(weight_templates)

                    if (i * 4 + j) % 20 == 0:
                        nutrient = np.random.choice(["proteins", "fats", "carbohydrates"])
                        nutr_weight = weight_rand * product['macronutra'][self.CONF_NUTRIENT_DICT[nutrient]] / 100
                        nutr_templates = [
                            f" {nutr_weight} g of {nutrient}",
                            f" {nutrient} {nutr_weight} g",
                            f" {nutr_weight} grams of {nutrient}",
                            f" {nutrient} in {nutr_weight} g"
                        ]
                        product_name += random.choice(nutr_templates)

                    generated_user_prompt = self.replace_entities(user_prompt, [product_name])
                    if generated_user_prompt == user_prompt:
                        continue

                    product_json = {
                        "messages": [
                            {
                                "role": "user",
                                "content": generated_user_prompt,
                            },
                            {
                                "role": "assistant",
                                "content": json.dumps([
                                    {
                                        "name": product['product_name'],
                                        "measure": "g",
                                        "weight": {
                                            "min": weight_rand,
                                            "max": weight_rand,
                                        },
                                        "prots_100g": {
                                            "min": product['macronutra']['prots'],
                                            "max": product['macronutra']['prots'],
                                        },
                                        "fats_100g": {
                                            "min": product['macronutra']['fats'],
                                            "max": product['macronutra']['fats'],
                                        },
                                        "carbs_100g": {
                                            "min": product['macronutra']['carbs'],
                                            "max": product['macronutra']['carbs'],
                                        },
                                        "pols_100g": {
                                            "min": product['macronutra']['polyols'],
                                            "max": product['macronutra']['polyols'],
                                        },
                                        "acid_100g": {
                                            "min": product['macronutra']['acids'],
                                            "max": product['macronutra']['acids'],
                                        },
                                        "fiber_100g": {
                                            "min": product['macronutra']['fiber'],
                                            "max": product['macronutra']['fiber'],
                                        },
                                        "alco_100g": {
                                            "min": product['macronutra']['alcohol'],
                                            "max": product['macronutra']['alcohol'],
                                        },
                                        "kcal_100g": {
                                            "min": product['custom_extra']['estimated_mean_energy_kcal'],
                                            "max": product['custom_extra']['estimated_mean_energy_kcal'],
                                        }
                                    },
                                ])
                            }
                        ]
                    }

                    f.write(json.dumps(product_json))
                    f.write("\n")

    def convert_bs(self, w=0.95, k=0.99):
        '''
        :param w: коэфицент неточности веса
        :param k: коэфицент неточности нутры
        '''

        with open('./resources/static/data/food_requests.txt', 'r', encoding="utf-8") as file:
            user_prompts = file.readlines()

        cur_user_prompt_idx = 0
        num_products = self.count_entities(user_prompts[cur_user_prompt_idx])
        cur_products_count = 0
        product_names = []

        product_json = {
            "messages": [
                {
                    "role": "user",
                },
                {
                    "role": "assistant",
                    "content": []
                }
            ]
        }

        with open('./static/data/train1_instruct_bs.jsonl', 'w') as f:
            for i, product in tqdm(enumerate(self.db.find())):
                if i > 0 and cur_products_count == num_products:
                    generated_user_prompt = self.replace_entities(
                        user_prompts[cur_user_prompt_idx % len(user_prompts)],
                        product_names
                    )

                    if generated_user_prompt != user_prompts[cur_user_prompt_idx % len(user_prompts)]:
                        if len(generated_user_prompt) > 0:
                            product_json['messages'][0]['content'] = generated_user_prompt
                            product_json['messages'][1]['content'] = json.dumps(product_json['messages'][1]['content'])

                            f.write(json.dumps(product_json))
                            f.write('\n')

                    cur_user_prompt_idx += 1
                    num_products = self.count_entities(user_prompts[cur_user_prompt_idx % len(user_prompts)])
                    cur_products_count = 0
                    product_names = []

                    product_json = {
                        "messages": [
                            {
                                "role": "user",
                            },
                            {
                                "role": "assistant",
                                "content": []
                            }
                        ]
                    }

                weight_rand = random.random() * 150 + 50
                weight_min = weight_rand * w
                weight_max = weight_rand / w

                if i % 20 == 0:
                    nutrient = np.random.choice(["proteins", "fats", "carbohydrates"])
                    nutr_weight = product['macronutra'][self.CONF_NUTRIENT_DICT[nutrient]] * weight_rand / 100

                    product_names.append(
                        f"{product['product_name']} {nutr_weight} g of {nutrient}"
                    )
                else:
                    product_names.append(product['product_name'])

                product_json['messages'][1]['content'].append(
                    {
                        "name": product['product_name'],
                        "measure": "g",
                        "weight": {
                            "min": weight_min,
                            "max": weight_max,
                        },
                        "prots_100g": {
                            "min": product['macronutra']['prots'] * k,
                            "max": product['macronutra']['prots'] / k,
                        },
                        "fats_100g": {
                            "min": product['macronutra']['fats'] * k,
                            "max": product['macronutra']['fats'] / k,
                        },
                        "carbs_100g": {
                            "min": product['macronutra']['carbs'] * k,
                            "max": product['macronutra']['carbs'] / k,
                        },
                        "pols_100g": {
                            "min": product['macronutra']['polyols'] * k,
                            "max": product['macronutra']['polyols'] / k,
                        },
                        "acid_100g": {
                            "min": product['macronutra']['acids'] * k,
                            "max": product['macronutra']['acids'] / k,
                        },
                        "fiber_100g": {
                            "min": product['macronutra']['fiber'] * k,
                            "max": product['macronutra']['fiber'] / k,
                        },
                        "alco_100g": {
                            "min": product['macronutra']['alcohol'] * k,
                            "max": product['macronutra']['alcohol'] / k,
                        },
                        "kcal_100g": {
                            "min": min(
                                product['custom_extra']['energy_kcal'],
                                product['custom_extra']['estimated_mean_energy_kcal']
                            ) * k,
                            "max": max(
                                product['custom_extra']['energy_kcal'],
                                product['custom_extra']['estimated_mean_energy_kcal']
                            ) / k,
                        },
                        "prots": {
                            "min": product['macronutra']['prots'] * (weight_min / 100) * k,
                            "max": product['macronutra']['prots'] * (weight_max / 100) / k,
                        },
                        "fats": {
                            "min": product['macronutra']['fats'] * (weight_min / 100) * k,
                            "max": product['macronutra']['fats'] * (weight_max / 100) / k,
                        },
                        "carbs": {
                            "min": product['macronutra']['carbs'] * (weight_min / 100) * k,
                            "max": product['macronutra']['carbs'] * (weight_max / 100) / k,
                        },
                        "pols": {
                            "min": product['macronutra']['polyols'] * (weight_min / 100) * k,
                            "max": product['macronutra']['polyols'] * (weight_max / 100) / k,
                        },
                        "acid": {
                            "min": product['macronutra']['acids'] * (weight_min / 100) * k,
                            "max": product['macronutra']['acids'] * (weight_max / 100) / k,
                        },
                        "fiber": {
                            "min": product['macronutra']['fiber'] * (weight_min / 100) * k,
                            "max": product['macronutra']['fiber'] * (weight_max / 100) / k,
                        },
                        "alco": {
                            "min": product['macronutra']['alcohol'] * (weight_min / 100) * k,
                            "max": product['macronutra']['alcohol'] * (weight_max / 100) / k,
                        },
                        "kcal": {
                            "min": min(
                                product['custom_extra']['energy_kcal'],
                                product['custom_extra']['estimated_mean_energy_kcal']
                            ) * (weight_min / 100) * k,
                            "max": max(
                                product['custom_extra']['energy_kcal'],
                                product['custom_extra']['estimated_mean_energy_kcal']
                            ) * (weight_max / 100) / k,
                        }
                    }
                )

                if i % 20 == 0:
                    product_json['messages'][1]['content'][-1][self.CONF_NUTRIENT_DICT[nutrient]]['min'] = nutr_weight
                    product_json['messages'][1]['content'][-1][self.CONF_NUTRIENT_DICT[nutrient]]['max'] = nutr_weight

                cur_products_count += 1


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument('convertion_type', type=str)
    args = parser.parse_args()

    converter = InstructConverter()
    if args.convertion_type == 'ss':
        converter.convert_ss()
    elif args.convertion_type == 'bs':
        converter.convert_bs()

```


Сделай экспертный рефакторинг данного кода, в частности:
Функция ss уже работает, перепиши функцию bs в соответствии.
В bs происходит несколько важных отличий. Во первых в ss мы берем только промпты в которых 1 entity. Для bs мы берем их сколько угодно, но в каждом промпте будет разное количество продуктов, и нам нужно читать из базы соответствующее число продуктов. Учитывай что промптов будет на несколько порядков меньше чем продуктов, поэтому промпты нужно водить по кругу.
Так же в bs у нас должны использоваться коэфиценты неточностей k и w.
Ещё в bs  каллории расчитываются по отличной от ss формуле, сохрани сложную формулу где выбор из energy kcal и est energy kcal
Убери из bs вывод пересчитанных к весу нутриентов
Добавь механизм с шаблонами веса и шаблонами нутриента из ss в bs
Настройки рандомизации веса (От и до) должны стать константными конфигами. Так же конфигом нужно сделать шанс добавления нутриента (Сейчас он 1 к 20)
И самое главное! Необходимо поправить цикл так, что бы функция count_entities и replace_enities вызывались только ВНУТРИ цикла продуктов. Нельзя использовать эти функции выше!