сделай как в документации функции оформи красиво чтоб читабельно и понятно было что они делают и раздели по разделам типа 
# ============================
# CREATING SQL-QUERY FUNCTIONS
# ============================
я делаю пакет и документацию к нему. вот функции
def generate_month_start_dates(start_date, end_date):
    # Преобразуем строки в объекты даты
    start_date = pd.to_datetime(start_date)
    end_date = pd.to_datetime(end_date)
    if start_date.day != 1:
        start_date = start_date.replace(day=1)
    month_starts = []
    current_date = start_date
    while current_date < end_date:
        month_starts.append(current_date)
        next_month = current_date.month % 12 + 1
        next_year = current_date.year + (current_date.month // 12)
        current_date = current_date.replace(year=next_year, month=next_month, day=1)
    
    formatted_dates = [date.strftime("'%Y-%m-%d'") for date in month_starts]
    # Преобразуем в строку для SQL-запроса
    date_list = ", ".join(formatted_dates)
    return date_list

def generate_dates(start_date, end_date):
    # Преобразуем строки в объекты даты
    start_date = pd.to_datetime(start_date)
    end_date = pd.to_datetime(end_date)

    dates = []
    current_date = start_date
    while current_date < end_date:
        dates.append(current_date)
        current_date = current_date + pd.Timedelta(days=1)
    dates.append(current_date)
    formatted_dates = [date.strftime("'%Y-%m-%d'") for date in dates]
    # Преобразуем в строку для SQL-запроса
    date_list = ", ".join(formatted_dates)
    return date_list



def query_params(model=None, strategy=None, step=None, versions='', paths=None, list_params=None, start=None, end=None, with_shadow=0, target_rquid='', target_chain='', target_inn='', target_deal='', conditions=''):
    '''Создать запрос для выгрузки параметров модели/стратегии. model=None, strategy=None, step=None, versions='' - список версий модели/стратегии,
    paths=None - список путей к параметрам. ДЛЯ ПАРАМЕТРОВ ШАГОВ В НАЧАЛЕ ПУТИ НАПИСАТЬ БУКВУ "s"!!!, list_params - кортеж, где 1 элемент путь к параметру, 2 сколько вытащить параметров из списка,
    start=None, end=None - даты начала и окончания выгрузки, with_shadow=0 - флаг учета тени (1 - выгружаем тень, 0 - нет), 
    target_rquid='' - список rquid запросов на модель/стратегию, target_chain='' - список chain_sid, target_inn='' - список ИНН, target_deal='' - список КФЮЛ id, conditions='' - список строк условий после агрегации
    '''
    if not (start and end):
        print('Не задан период выгрузки! Параметры start и end!')
        return
    if not paths:
        print('Пути параметров не заданы! Выгрузятся только ИНН и КФЮЛ id!!!')
        if model:
            paths = ['/quantitative_factors/inn', '/org_data/deal_id_list[0]/deal_id']
        elif strategy:
            paths = ['/customer_info/INN', '/deal_Info/deal_id_list[0]/deal_id']
        
    period = generate_month_start_dates(start, end) if not step else generate_dates(start, end)
    list_params_query = ""
    if list_params:
        for i in range(list_params[1]):
            num = '00' + str(i) if i < 10 else '0' + str(i) if i < 100 else i
            list_params_query += f"\n,max(case when (mp.xpath_code = '{list_params[0]}[{i}]') then (mp.{'model_exec_param_val' if model else 'strat_exec_param_val'}) end) as {list_params[0].split('/')[-1]}_{num}"
    
    path_query = ''
    strat_step_params = False
    for path in paths:
        if step:
            strat_step_params = not path.startswith('s')
        path_query += f"\n,max(case when ({'mp' if not strat_step_params else 'mps'}.xpath_code = '{path if strat_step_params else path[1:]}') then ({'mp' if not strat_step_params else 'mps'}.{'model_exec' if model else 'strat_exec' if strategy and strat_step_params else 'strat_step'}_param_val) end) as {path.split('/')[-1]}"
   
    query = f'''SELECT t.*
  FROM (SELECT m.chain_sid --ID цепочки вызовов
      {'' if target_rquid else '--'} ,max(m.{'model' if model else 'strat'}_exec_rqst_sid) as rquid --идентификатор запроса на модель/стратегию
      {'' if with_shadow else '--'} ,max(m.shadow_flag) as shadow_flag
      ,max({'ms' if step else 'm'}.{'model' if model else 'strat'}_exec_dttm) as exec_time --Дата и время расчёта
      ,max({'ms' if step else 'm'}.{'model' if model else 'strat'}_code) as {'model' if model else 'strat'}_code
      {path_query}
      {list_params_query}
    FROM {'exec_model' if model else 'exec_strat' if strategy and not step else 'strat_step'}_x_param mp -- Таблица с параметрами из расчётов моделей MEF (результат парсинга)
      INNER JOIN {'exec_model' if model else 'exec_strat' if strategy and not step else 'strat_step'} m ON m.{'model_exec' if model else 'strat_exec' if strategy and not step else 'strat_step'}_rqst_sid = mp.{'model_exec' if model else 'strat_exec' if strategy and not step else 'strat_step'}_rqst_sid -- Таблица с расчётами моделей MEF (общая информация)
      {'' if step else '--'} INNER JOIN exec_strat ms ON ms.strat_exec_rqst_sid = m.strat_exec_rqst_sid 
      {'' if step else '--'} INNER JOIN exec_strat_x_param mps ON ms.strat_exec_rqst_sid = mps.strat_exec_rqst_sid
    WHERE 1=1
      AND m.{'model_exec' if model else 'strat_exec' if strategy and not step else 'strat_step'}_part_dt IN ({period}) -- Интервал расчётов по месяцам
      AND m.{'model_exec' if model else 'strat_exec' if strategy and not step else 'strat_step'}_part_dt = mp.{'model_exec' if model else 'strat_exec' if strategy and not step else 'strat_step'}_part_dt
      AND m.{'model_part' if model else 'strat_part' if strategy and not step else 'business_step'}_code IN ('{model if model else strategy if strategy and not step else step}')
      {'' if step else '--'} AND ms.strat_part_code IN ('{strategy}')
      {'--' if step else ''} AND m.{'model_part' if model else 'strat_part'}_code = mp.{'model_part' if model else 'strat_part'}_code
      AND {'m.model_exec' if model else 'm.strat_exec' if strategy and not step else 'ms.strat_exec'}_rslt_code = 0 -- Расчёт успешный
      AND m{'s' if step else ''}.{'model_exec' if model else 'strat_exec'}_dttm BETWEEN '{start}' AND '{end}' -- Интервал расчётов
      {'' if versions else '--'} AND m.{'model' if model else 'strat'}_code IN ('{"', '".join(versions)}') -- версия, если нужно
      {'--' if with_shadow else ''} AND {'ms' if step else 'm'}.shadow_flag = 0 -- Расчёт не теневой
      {'' if target_rquid else '--'} AND m.{'model' if model else 'strat'}_exec_rqst_sid IN ('{"', '".join(target_rquid)}') -- фильтр по идентификатору запроса
      {'' if target_chain else '--'} AND m.chain_sid IN ('{"', '".join(target_chain)}') -- список chain_id сделок, которые нужно вытащить
    GROUP BY m.chain_sid
    {'' if target_inn or target_deal or conditions else '--'} HAVING
    {'' if target_inn else '--'} inn IN ('{"', '".join(target_inn)}')
    {'' if target_deal else '--'}{'AND' if target_inn else ''} deal_id IN ('{"', '".join(target_deal)}')
    {'AND' if target_inn or target_deal else '' + ' AND'.join(conditions) if conditions else ''}
    ) t
  ORDER BY t.exec_time'''

    return query
    
def query_json(model='', strategy='', steps='', versions='', cols=['chainid', 'exectime', 'inparams', 'outparams', 'modelid'], cols_mod='', cols_strat='', start=None, end=None, with_shadow=0, target_rquid='', target_chain=''):
    '''Создать запрос для выгрузки параметров модели/стратегии. model=None, strategy=None, 
    steps='' - список шагов, versions='' - список версий модели/стратегии,
    cols=None - список колонок или '*', cols_strat=None - колонки для стратегии если в связке с моделью, cols_mod=None - колонки модели если в связке со стратегией или '*',
    start=None, end=None - даты начала и окончания выгрузки, with_shadow=0 - флаг учета тени (1 - выгружаем тень, 0 - нет), 
    target_rquid='' - список rquid запросов на модель/стратегию, target_chain='' - список chain_sid
    '''
    if not (start and end):
        print('Не задан период выгрузки! Параметры start и end!')
        return
    if strategy and 'exectime' in cols:
        cols = ['chainid', 'dateexec', 'inparams', 'outparams', 'strategyid']
    
    if not (cols_strat or cols_mod):
        cols_mod = ['inparams', 'outparams', 'modelid'] if model and strategy else cols if model else ''
        cols_strat = cols if strategy else ''
        if (model and strategy):
            print('Не заданы колонки для модели и стратегии! Будут выгружены колонки, указанные в cols!')
    elif cols_strat and model and not cols_mod:
        print('Не заданы колонки для модели! Колонки будут одинаковые для модели и стратегии!')
        cols_mod = cols_strat
    elif cols_mod and strategy and not cols_strat:
        print('Не заданы колонки для стратегии! Колонки будут одинаковые для модели и стратегии!')
        cols_strat = cols_mod
    elif cols_mod and cols_strat:
        cols = None
        if not model:
            cols = cols_strat
        elif not strategy:
            cols = cols_mod
    elif (not cols_mod and model) or (not cols_strat and strategy): 
        print('Не заданы колонки для модели/стратегии!')
        
    symbols = {'-', '.', '/'}
    strategy_col = ''.join([i if i not in symbols else '_' for i in strategy])
    model_col = ''.join([i if i not in symbols else '_' for i in model])
    same_cols = set(cols_mod) & set(cols_strat)
    query = f'''
    SELECT 
    {'' if  strategy else '--'} {'s.' + ',s.'.join([i + f" as {i}_{strategy_col}" if "*" not in i else '*' for i in same_cols]) + ',' if same_cols else ''}{'s.' + ',s.'.join(sorted(set(cols_strat) - same_cols)) if set(cols_strat) - same_cols else ''}{',' if model or steps else ''} -- колонки стратегии
    {'' if model else '--'} {'m.' + ',m.'.join([i + f" as {i}_{model_col}" if "*" not in i else '*' for i in same_cols]) + ',' if same_cols else ''}{'m.' + ',m.'.join(sorted(set(cols_mod) - same_cols)) if set(cols_mod) - same_cols else ''} -- колонки модели
    {'st.outparams as out_step' if steps and strategy else 'st.chainid, st.stepbusinessid, st.dateexec, st.inparams, st.outparams' if steps else ''} 
    
    {'' if strategy else '--'} FROM strategydto s --Таблица с расчетами стратегий СПР
    {'' if model and not (strategy or steps) else '--'} FROM modeldto m --Таблица с расчетами моделей СПР
    {'' if steps and not (strategy or model) else '--'} FROM strategystepdto st --Таблица с расчетами шагов СПР
    
    {'' if strategy and model else '--'} JOIN modeldto m USING(chainid)
    {'' if strategy and steps else '--'} JOIN strategystepdto st USING(chainid)
    
    WHERE 1=1
    {'' if strategy else '--'} AND s.strategyid {"IN ('" + {"', '".join(versions)} + "')" if versions else "LIKE '" + strategy + "%'"} -- стратегия
    {'' if model else '--'} AND m.modelid {"IN ('" + {"', '".join(versions)} + "')" if versions else "LIKE '" + model + "%'"} -- модель
    {'' if steps else '--'} AND st.stepbusinessid IN ('{"', '".join(steps)}') -- шаг
    {'' if strategy else '--'} AND s.dateexec BETWEEN '{start}' AND '{end}' AND s.rscode = 0 -- Интервал расчётов
    {'' if model and not strategy else '--'} AND m.exectime BETWEEN '{start}' AND '{end}' AND m.rscode = 0 -- Интервал расчётов
    
    {'--' if with_shadow else ''} AND {'s' if strategy else 'm'}.shadow = 0 --Расчёт не теневой
    
    {'' if target_chain else '--'} AND {'s' if strategy else 'st' if steps else 'm'}.chainid IN ('{"', '".join(target_chain)}')
    {'' if target_rquid else '--'} AND {'s' if strategy else 'm'}.rquid IN ('{"', '".join(target_rquid)}')
    ORDER BY {'s.dateexec' if strategy else 'st.dateexec' if steps else 'm.exectime'}
    '''
    return query

def query_steps(strategy='DiME', target_chain='', start=None, end=None, with_shadow=0):
    '''Запрос выгрузку данных стратегии с шагами (агрегатами) для пакетного прогона на НТ стенде.
    strategy='DiME', target_chain='' - список chainid для запросов, 
    start=None, end=None - даты начала и окончания выгрузки, with_shadow=0 - флаг, учитывать ли теневые запросы'''
    
    if not (start and end):
        print('Не задан период выгрузки! Параметры start и end!')
        return
    aggregates = [('getcore', 'getIrcore'), ('getcore', 'getIrcore', 'getMcore'), ('getdm'), ('ColScore', 'Col2')]
    strategy_map = {'DigE': ('mmb-application', aggregates[0]), 'CRIAL': ('CRIAL', aggregates[0]), 'ResZ': ('ResZ', aggregates[1]), 'ReP': ('RP', aggregates[1]), 'ReMS': ('ReMS', aggregates[2]), 'MR0': ('M0', aggregates[3]), 'MR4': ('MR4', aggregates[3]), 'RM': ('RM', aggregates[0]), 'UER': ('mtion', aggregates[0] + ('seco2', 'cal2.0', 'ge2'))}
    try:
        step_strategy = strategy_map[strategy][0]
        steps = strategy_map[strategy][1]
    except KeyError: 
        print('Неверная стратегия! (для заявки указывается DE)')
        return
        
    query = f"""SELECT 
    r.chainid, r.strategyid, r.dateexec, {'r.inparams, r.outparams,' if step_strategy == strategy else f"MAX(CASE WHEN r.strategyid LIKE '{strategy}%' THEN r.inparams END) as inparams, MAX(CASE WHEN r.strategyid LIKE '{strategy}%' THEN r.outparams END) as outparams,"}
    """
    
    query += ",\n".join([f"MAX(CASE WHEN rd.stepbusinessid = '{step}' THEN rd.outparams ELSE 0 END) AS {step.replace('.', '_')}" for step in steps])
    
    query += f"""
    FROM strategydto r --Таблица с расчетами стратегий СПР
    JOIN strategystepdto rd --Таблица с расчетами шагов стратегий СПР
    USING(chainid)
    {'' if step_strategy != strategy else '--'}JOIN strategydto r1 ON r.chainid=r1.chainid AND r1.strategyid LIKE '{step_strategy}%'
    WHERE 1=1
    AND r.strategyid LIKE '{step_strategy if step_strategy == strategy else strategy}%'
    AND r.rscode = 0
    {'' if target_chain else '--'} AND r.chainid in ('{"', '".join(target_chain)}') -- фильтр на конкретные запросы (в первой таблице)
    {'--' if with_shadow else ''} AND r.shadow = 0
    AND r.dateexec between '{start}' AND '{end}' --Интервал расчётов!!!
    AND rd.stepbusinessid IN ('{"', '".join(steps)}')
    GROUP BY r.chainid, r.strategyid, r.dateexec {',r.inparams, r.outparams' if step_strategy == strategy else ''}
    ORDER BY r.dateexec
    """

    return query

def download_dictionaries(dict_names=None, start=None, end=None):
    '''Выгрузить с прома справочники в zip-архив. на выходе датафрейм с данными и архив, загруженный в файлы'''
    
    if not (dict_names and start and end):
        print('Не все параметры заданы! dict_names=список справочников, start=дата начала выгрузки, end=дата окончания выгрузки')
        return
    dicts = spark.sql(f'''
    SELECT m.name, m.version, m.uploadedby, m.uploadeddate, m.state, m.resultdescription, mp.content 
    FROM dictionaryversiondto m --Таблица с версиями справочников СПР
    INNER JOIN dictionarycontentdto mp on m.id=mp.dictionaryid --Таблицы с наполнением справочников
    WHERE 1=1
    AND m.name = ('{"', '".join(dict_names)}') -- справочник(и)
    AND m.resultcode = 0
    AND m.uploadeddate BETWEEN '{start}' AND '{end}' -- Интервал расчётов
    order by m.uploadeddate DESC
    ''').toPandas()
    
    # собираем, упаковываем все справочники в зип архив и загружаем
    with zipfile.ZipFile(f'dictionaries_archive{current_date}.zip', 'w') as zipf:
        for (name, version), group in dicts.groupby(['name', 'version']):
            file_name = f"{name}@{version}.txt"
            with open(file_name, 'w', encoding='utf-8') as f:
                for content in group['content']:
                    f.write(content + '\n')
            zipf.write(file_name)
            os.remove(file_name)
    return dicts

def find_differences(row, cols):
    """выписать различия в колонках в строку: параметр значение 1, значение 2
    row - применяем к каждой строке df с помощью функции apply
    cols - колонки, по которым ищем отличия, обычно задаем все колонки"""
    differences = []
    for col in cols:
        if '_SF' in col or '_prom' and 'chain' not in col and 'dateexec' not in col and 'exectime' not in col and 'inparams' not in col and 'outparams' not in col and 'rqsource' not in col and 'client_id' not in col and 'deal_id' not in col and 'inn' not in col:
            first_value = row[col]
            col_second = col.replace('_SF', '_DE').replace('_pm', '_T')
            if col_second in cols:
                second_value = row[col_second]
            else: continue
            if first_value != second_value and first_value != 'NULL' and second_value != 'NULL':
                differences.append(f"{col[:-5]} {first_value}, {second_value}")
    return ";\n".join(differences)

def query_file_params(models=False, strats=False, steps=False, file_path='params_package.csv', start=None, end=None, target_chain=''):
    '''Запрос на сбор значений параметров из файла для заявки.
    models=False, strats=False, steps=False - флаги моделей, стратегий и шагов, 
    file_path='params_package.csv', start=None, end=None - даты начала и окончания выгрузки, 
    target_chain='' - список chain_sid запросов
    '''
    if not (models or strats or steps):
        print('Не задан флаг для моделей, стратегий или шагов! models=True ИЛИ strats=True ИЛИ steps=True')
        return
    period = generate_month_start_dates(start, end) if not steps else generate_dates(start, end)
    request = pd.read_csv(file_path, sep=',') # подгружаем файл c параметрами, которые достаем
    # Список для хранения частей запроса
    model_names = request['model_param_code'].dropna().unique()
    # формирование запроса на стратегии для получения параметров из файла
    request_strats = request[request['strategy_step_param_code'].isin(['inputRequest', 'output_Params', 'outputResponse', 'outputParams'])]
    strategy_names = request_strats['strategy_param_code'].dropna().unique()
    # формирование запроса на шаги стратегии для получения параметров из файла (оочень долго грузит!!!)
    request_steps = request[~request['strategy_step_param_code'].isin(['inputRequest', 'output_Params', 'outputResponse', 'outputParams'])].dropna(subset=['strategy_step_param_code'])
    strat_step_names = request_steps['strategy_param_code'].dropna().unique()
    step_names = request_steps['strategy_step_param_code'].dropna().unique()
    step_path = {'gere': '/[0]/MMULT', 'geore': '/[0]/IULT', 'bks': '/[0]'}
    param_step_types = request_steps['params_type'].unique()
    
    query_lines = []

    # Заголовок запроса
    query_lines.append("""SELECT t.*
    FROM (SELECT m.chain_sid --ID цепочки вызовов""" + ("""
    ,max(m.model_exec_dttm) as exec_time --Дата и время расчёта
    ,max(case when (mp.xpath_code = '/quors/in') then (mp.model_exec_param_val) end) as in
    ,max(case when (mp.xpath_code = '/orta/dest[0]/ded') then (mp.model_exec_param_val) end) as de
    """ if models else ''))

    # Генерация CASE конструкций для каждого параметра
    for _, row in request.iterrows():
        if row[f"{'model' if models else 'strategy'}_param_code"] in (model_names if models else strategy_names if strats else strat_step_names):
            step_name_path = step_path[row['strategy_step_param_code']] if steps else ''
            xpath_code = step_name_path + row['xpath_param_code'] if steps else row['xpath_param_code']
            param_name = row['param_code']

            query_lines.append(f",max(case when (mp.xpath_code = '{xpath_code}') then (mp.{'model_exec' if models else 'strat_exec' if strats else 'strat_step'}_param_val) end) as {param_name}")
            
    # Оставшаяся часть запроса
    query_lines.append(f"""FROM {'exec_model' if models else 'exec_strat' if strats else 'strat_step'}_x_param mp 
      INNER JOIN {'exec_model' if models else 'exec_strat' if strats else 'strat_step'} m ON m.{'model_exec' if models else 'strat_exec' if strats else 'strat_step'}_rqst_sid = mp.{'model_exec' if models else 'strat_exec' if strats else 'strat_step'}_rqst_sid 
      {'' if steps else '--'} INNER JOIN strat ms ON ms.strat_exec_rqst_sid = m.strat_exec_rqst_sid 
      WHERE 1=1
      AND m.{'model_exec' if models else 'strat_exec' if strats else 'strat_step'}_part_dt IN ({period})
      AND m.{'model_exec' if models else 'strat_exec' if strats else 'strat_step'}_part_dt = mp.{'model_exec' if models else 'strat_exec' if strats else 'strat_step'}_part_dt
      AND m.{'model_part' if models else 'strat_part' if strats else 'step'}_code IN ('{"', '".join(model_names if models else strategy_names if strats else step_names)}')
    {'' if steps else '--'} AND ms.strat_part_code IN ('{"', '".join(strat_step_names)}')
    {'' if steps else '--'} AND mp.strat_step_param_type_name IN ('{"', '".join(param_step_types)}')
    {'--' if steps else ''} AND m.{'model_part' if models else 'strat_part'}_code = mp.{'model_part' if models else 'strat_part'}_code
    AND {'m.model_exec' if models else 'm.strat_exec' if strats else 'ms.strat_exec'}_rslt_code = 0 -- Расчёт успешный
    AND {'ms' if steps else 'm'}.shadow_flag = 0 -- Расчёт не теневой
    AND m{'s' if steps else ''}.{'model_exec' if models else 'strat_exec'}_dttm BETWEEN '{start}' AND '{end}' -- Интервал расчётов
    {'' if target_chain else '--'} AND m.chain_sid IN ('{"', '".join(target_chain)}') -- если есть конкретные чейн айди
      GROUP BY m.chain_sid) t
     {'--' if not models else ''} ORDER BY t.exec_time""")
    
    # Объединение всех частей в одну строку
    query = '\n'.join(query_lines)
    return query

def compare_json(data1, data2, path=""):
    """сравнение двух json-строк по содержанию"""
    non_matching_fields = {}

    if isinstance(data1, dict) and isinstance(data2, dict):
        # Объединяем ключи из обоих словарей для проверки
        all_keys = set(data1.keys()).union(set(data2.keys()))

        for key in all_keys:
            value1 = data1.get(key, None)
            value2 = data2.get(key, None)

            current_path = f"{path}.{key}" if path else key
            
            # Рекурсивный вызов для вложенных словарей
            nested_diff = compare_json(value1, value2, current_path)
            non_matching_fields.update(nested_diff)

    elif isinstance(data1, list) and isinstance(data2, list):
        # Проверяем содержимое списков
        min_length = min(len(data1), len(data2))
        if data1 and isinstance(data1[0], dict):
            try:
                data1 = sorted(data1, key=lambda x: x[list(data1[0].keys())[0]])
                data2 = sorted(data2, key=lambda x: x[list(data1[0].keys())[0]])
            except:
                if 'index' in data1[0].keys():
                    try:
                        data1 = sorted(data1, key=lambda x: x['index'])
                        data2 = sorted(data2, key=lambda x: x['index'])
                    except:
                        pass
        for i in range(min_length):
            
            for j in range(min_length):
#                 if (isinstance(data1[i], dict) and isinstance(data2[j], dict)) and data1[i].keys() == data2[j].keys():
#                     break
#                 if (isinstance(data1[i], (int, str)) and isinstance(data2[j], (int, str))) and data1[i] == data2[j]:
#                     break
                nested_diff = compare_json(data1[i], data2[j], f"{path}[{i}]")
                if not nested_diff or data1[i]==data2[j]:
                    break
            else:
                non_matching_fields.update(compare_json(data1[i], data2[i], f"{path}[{i}]"))
        
        if len(data1) != len(data2):
            non_matching_fields[path] = {
                '1': data1,
                '2': data2,
                'note': f"Different lengths: {len(data1)} vs {len(data2)}"
            }

    else:
        # Сравнение простых значений
        if data1 != data2:
            non_matching_fields[path] = {'1': data1, '2': data2}

    return non_matching_fields