Gainward777 commited on
Commit
f556b0c
·
verified ·
1 Parent(s): 742dd58

Upload 6 files

Browse files
preprocess/utils/common/brand_matching.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from tqdm import tqdm
2
+ import re
3
+ from ahocorasick import Automaton
4
+ from rapidfuzz import fuzz, process
5
+
6
+
7
+ def contains_full_word(word, text, case_sensitive=True):
8
+ """
9
+ Проверяет, содержится ли слово word в строке text как отдельное слово.
10
+ Параметр case_sensitive задаёт, учитывать ли регистр.
11
+ """
12
+ flags = 0 if case_sensitive else re.IGNORECASE
13
+ pattern = r'\b' + re.escape(word) + r'\b'
14
+ return re.search(pattern, text, flags) is not None
15
+
16
+
17
+ def unwrap_brands(products):
18
+ res={}
19
+ #brands=items['brand'].unique()
20
+ new_brands=sorted([x for x in products['brand'].unique() if isinstance(x, str)], key=len)
21
+ #items['new_brand'].unique() if isinstance(x, str)], key=len)
22
+
23
+ for i in tqdm(new_brands):
24
+ for j in new_brands:
25
+ if contains_full_word(i, j, case_sensitive=False):
26
+ if i != j:
27
+ #if len(i)>1:#i != 'А' and i != "Я":
28
+ res[j]=i
29
+ return res
30
+
31
+
32
+
33
+ def split_n_match(products, items, th_len=3):
34
+ result={}
35
+ conditionally_spited=[]
36
+ for i in tqdm(items['brand'].unique()):
37
+ if '/' in i:
38
+ conditionally_spited.append(i)
39
+ for i in tqdm(products['brand'].unique()):
40
+ for j in conditionally_spited:
41
+ if len(i)>th_len and contains_full_word(i,j):
42
+ result[j]=i
43
+ return result
44
+
45
+
46
+
47
+ def fill_brands_in_dataframe(brands, df, col_name='new_brand', is_brand=True):
48
+ """
49
+ Заполняет колонку 'brand' в DataFrame найденными брендами.
50
+
51
+ :param brands: Список брендов.
52
+ :param df: DataFrame с колонками ['id', 'brand', 'name', ...].
53
+ :return: DataFrame с обновлённой колонкой 'brand'.
54
+ """
55
+ # Инициализируем автомат для быстрого поиска брендов
56
+ automaton = Automaton()
57
+
58
+ # Добавляем бренды в автомат
59
+ for idx, brand in enumerate(brands):
60
+ if isinstance(brand, str) and brand:
61
+ automaton.add_word(brand.lower(), (idx, brand))
62
+
63
+ automaton.make_automaton()
64
+
65
+ def find_brand(name):
66
+ """
67
+ Находит лучший бренд для данного имени.
68
+ """
69
+ matched_brands = set()
70
+ for _, (_, brand) in automaton.iter(name.lower()):
71
+ # Проверяем, что бренд встречается как отдельное слово
72
+ if re.search(rf'\b{re.escape(brand.lower())}\b', name.lower()):
73
+ matched_brands.add(brand)
74
+
75
+ # Возвращаем бренд с максимальной длиной (более точное совпадение)
76
+ return max(matched_brands, key=len) if matched_brands else None
77
+
78
+ # Обновляем колонку brand только для пустых значений
79
+ # df['new_brand'] = df.apply(
80
+ # lambda row: find_brand(row['name']), #if pd.isna(row['brand']) else row['brand'],
81
+ # axis=1
82
+ # )
83
+ if is_brand==True:
84
+ df[col_name] = df.apply(lambda row: find_brand(row['name']) or row['brand'], axis=1)
85
+ else:
86
+ df[col_name] = df.apply(lambda row: find_brand(row['name']) or None, axis=1)
87
+
88
+
89
+
90
+ def get_same_brands(products, items):
91
+ comp_list=[]
92
+ #not_comp_prods=[]
93
+ #not_comp_items=[]
94
+ prod_brand_list=list(products['brand'].unique())
95
+ items_brand_list=list(items['new_brand'].unique())
96
+ for i in tqdm(prod_brand_list):
97
+ if i in items_brand_list:
98
+ comp_list.append(i)
99
+
100
+ return comp_list, prod_brand_list, items_brand_list
101
+
102
+
103
+ def match_brands_improved(items_brands, prods_brands, threshold=85):
104
+ """
105
+ Улучшенный алгоритм сопоставления брендов с учётом нечёткого поиска и фильтрации ошибок.
106
+
107
+ :param items_brands: Список брендов из датафрейма items.
108
+ :param prods_brands: Список брендов из датафрейма prods.
109
+ :param threshold: Порог сходства для нечёткого поиска.
110
+ :return: Словарь соответствий {бренд из items: ближайший бренд из prods}.
111
+ """
112
+ brand_mapping = {}
113
+
114
+ for item_brand in tqdm(items_brands):
115
+ if isinstance(item_brand, str):
116
+ # Разделяем бренд на части
117
+ parts = [part.strip() for part in re.split(r"[\/\(\)]", item_brand) if part.strip()]
118
+ best_match = None
119
+ best_score = 0
120
+
121
+ for part in parts:
122
+ match, score, _ = process.extractOne(part, prods_brands, scorer=fuzz.ratio)
123
+ # Фильтрация по длине строк и порогу
124
+ if score >= threshold and abs(len(part) - len(match)) / len(part) <= 0.3:
125
+ if score > best_score:
126
+ best_match = match
127
+ best_score = score
128
+
129
+ # Сохранение результата
130
+ if best_match:
131
+ brand_mapping[item_brand] = best_match#, best_score)
132
+
133
+ return brand_mapping
134
+
135
+
136
+
137
+
preprocess/utils/common/extracters.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+
3
+
4
+ def extract_years(text):
5
+ """
6
+ Извлекает сочетание числа и слова, указывающего возраст (например: '50 лет', '21 years').
7
+ """
8
+ # Регулярное выражение ищет числа и слова 'лет' или 'years' с учетом регистра
9
+ match = re.search(r'\b(?<!\d)(\d{1,2})\s*(лет|years)\b', text, re.IGNORECASE)
10
+ if match:
11
+ # Приводим слово 'лет' или 'years' к исходному регистру
12
+ return f"{match.group(1)} {match.group(2)}"
13
+ return None
14
+
15
+ def extract_production_year(text):
16
+ """
17
+ Извлекает год производства (четырехзначное число в диапазоне 1900–2099) из строки.
18
+ Например: '2019'.
19
+ """
20
+ match = re.search(r'\b(19\d{2}|20\d{2})\b', text)
21
+ if match:
22
+ return match.group(1)
23
+ return None
24
+
25
+ def extract_alcohol_content(text):
26
+ """
27
+ Извлекает содержание алкоголя из строки.
28
+ Например: '40%'.
29
+ """
30
+ match = re.search(r'(\d{1,2}(?:[.,]\d+)?\s*%)', text)
31
+ if match:
32
+ # Заменяем запятую на точку для единообразия (если нужно)
33
+ return match.group(1).replace(' ', '').replace(',', '.')
34
+ return None
35
+
36
+
37
+ def is_volume(value):
38
+ """
39
+ Проверяет, является ли значение валидным объемом (<= 10 литров).
40
+ """
41
+ try:
42
+ volume = float(value)
43
+ return volume if volume <= 10 else None
44
+ except ValueError:
45
+ return None
46
+
47
+ def extract_volume_or_number(text):
48
+ """
49
+ Извлекает объем в литрах или число с плавающей точкой из строки.
50
+ Например: '0,75л', '0.5', или '1,5 л'.
51
+ """
52
+ # Попытка найти объем с буквой 'л' или без пробела перед ней
53
+ match_with_l = re.search(r'(\d+(?:[\.,]\d+)?\s*[лЛ]|(?:\d+(?:[\.,]\d+)?[лЛ]))', text)
54
+ if match_with_l:
55
+ return is_volume(match_with_l.group(1).replace(',', '.').replace('л', '').replace('Л', '').strip())
56
+
57
+ # Если не найдено, ищем просто число с плавающей точкой
58
+ match_number = re.search(r'(?<!№)\b(\d{1,2}(?:[\.,]\d+))\b(?!\s*(№|-er|er|\d{3,}))', text)
59
+ if match_number:
60
+ return is_volume(match_number.group(1).replace(',', '.'))
61
+
62
+ return None
63
+
64
+
65
+
66
+
preprocess/utils/common/parallel_brand_mutching.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ from ahocorasick import Automaton
3
+ from rapidfuzz import fuzz, process
4
+ from unidecode import unidecode
5
+ from pqdm.threads import pqdm
6
+
7
+
8
+ def normalize(text):
9
+ """
10
+ Приводит текст к нижнему регистру и транслитерирует его в латиницу.
11
+ """
12
+ return unidecode(text.lower())
13
+
14
+ def build_regex_for_brands(brands):
15
+ """
16
+ Нормализует бренды и создаёт одно регулярное выражение для точного поиска.
17
+ Возвращает скомпилированный паттерн и словарь: нормализованное название -> оригинальное название.
18
+ """
19
+ norm_to_brand = {}
20
+ for brand in brands:
21
+ norm_brand = normalize(brand)
22
+ if norm_brand not in norm_to_brand:
23
+ norm_to_brand[norm_brand] = brand
24
+ pattern = re.compile(r'\b(?:' + '|'.join(re.escape(nb) for nb in norm_to_brand.keys()) + r')\b')
25
+ return pattern, norm_to_brand
26
+
27
+ def process_string(s, regex_pattern, norm_to_brand, norm_brand_list, index_to_brand, threshold):
28
+ """
29
+ Обрабатывает одну строку:
30
+ 1. Пытается найти бренд через регулярное выражение.
31
+ 2. Если точного совпадения нет – разбивает строку и выполняет нечёткий поиск.
32
+ Возвращает кортеж: (исходная строка, найденный бренд или None).
33
+ """
34
+ norm_s = normalize(s)
35
+ # Пытаемся найти бренд через регулярное выражение
36
+ match = regex_pattern.search(norm_s)
37
+ if match:
38
+ return s, norm_to_brand[match.group(0)]
39
+
40
+ # Если точного совпадения нет, разбиваем строку по разделителям и анализируем части
41
+ parts = [part.strip() for part in re.split(r"[\/\(\)]", s) if part.strip()]
42
+ parts.append(s) # анализ всей строки
43
+ best_match = None
44
+ best_score = 0
45
+ for part in parts:
46
+ norm_part = normalize(part)
47
+ res = process.extractOne(norm_part, norm_brand_list, scorer=fuzz.ratio, score_cutoff=threshold)
48
+ if res is not None:
49
+ match_norm, score, idx = res
50
+ if score > best_score:
51
+ best_match = index_to_brand[idx]
52
+ best_score = score
53
+ if best_score == 100:
54
+ break
55
+ if best_match:
56
+ return s, best_match
57
+ return s, None
58
+
59
+ def check_brands_in_strings_pqdm(strings, brands, threshold=85, n_jobs=8):
60
+ """
61
+ Поиск брендов в строках с учетом вариантов написания и транслитерации.
62
+ Использует предварительный поиск через регулярное выражение и, при необходимости,
63
+ нечёткий поиск. Обработка выполняется параллельно с отображением прогресса с помощью pqdm.
64
+
65
+ :param strings: Список строк для поиска брендов.
66
+ :param brands: Список брендов для поиска.
67
+ :param threshold: Порог сходства для нечёткого поиска.
68
+ :param n_jobs: Число рабочих потоков (или процессов, если использовать pqdm.processes).
69
+ :return: Словарь вида {строка: найденный бренд}.
70
+ """
71
+ # Подготавливаем список нормализованных брендов и сопоставление индексов с оригинальными брендами.
72
+ norm_brand_list = []
73
+ index_to_brand = []
74
+ for brand in brands:
75
+ norm_brand = normalize(brand)
76
+ norm_brand_list.append(norm_brand)
77
+ index_to_brand.append(brand)
78
+
79
+ # Создаем комбинированный паттерн для точного поиска.
80
+ regex_pattern, norm_to_brand = build_regex_for_brands(brands)
81
+
82
+ # Определяем вспомогательную функцию, закрывающую необходимые параметры.
83
+ def process_string_wrapper(s):
84
+ return process_string(s, regex_pattern, norm_to_brand, norm_brand_list, index_to_brand, threshold)
85
+
86
+ # Обрабатываем строки параллельно с отображением прогресса.
87
+ results = pqdm(strings, process_string_wrapper, n_jobs=n_jobs)
88
+
89
+ brand_mapping = {}
90
+ for s, matched_brand in results:
91
+ if matched_brand:
92
+ brand_mapping[s] = matched_brand
93
+ return brand_mapping
94
+
95
+
96
+
97
+
preprocess/utils/common/top_inserts.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from preprocess.utils.common.extracters import *
2
+ from preprocess.utils.common.utils import *
3
+ from preprocess.utils.common.parallel_brand_matching import *
4
+ from tqdm import tqdm
5
+ import re
6
+ import math
7
+ import numpy as np
8
+
9
+
10
+
11
+ def top_inserts_matching(other_brands, p_brands, items, th=65):
12
+ replaced={}
13
+ for i in other_brands:
14
+ l=i.split('/')
15
+ if len(l)>2:
16
+ replaced[l[0].replace('Шато','')]=i
17
+ else:
18
+ if 'Шато' in i:
19
+ replaced[i.replace('Шато','')]=i
20
+
21
+ ob=[i.split('/')[0].replace('Шато','') for i in other_brands]
22
+ ob_in_pb=check_brands_in_strings_pqdm(ob, p_brands, threshold=th)
23
+
24
+ result={}
25
+ for k in ob_in_pb.keys():
26
+ if k in replaced.keys():
27
+ result[replaced[k]]=ob_in_pb[k]
28
+ else:
29
+ result[k]=ob_in_pb[k]
30
+
31
+ items.loc[items['new_name'].isin(result.keys()), 'new_brand'] = items['new_name'].map(result)
32
+
33
+
34
+ def process_unbrended_names(items, p_brands, process_text, types, grape_varieties, onther_words):
35
+ result={}
36
+ for n in tqdm(items[items['new_brand'].isna()]['name'].values):
37
+
38
+ name, alcohol, volume_or_number, years, production_year, gb, color, sour=process_text(n)
39
+ #name, alcohol, volume_or_number, years, production_year, gb, color, sour=prcess_text('Вино Токай Фурминт п/сл. бел.0.75л')
40
+ name=trim_name(name, types)
41
+ name=trim_name(name, grape_varieties)
42
+ name=trim_name(name, onther_words)
43
+ name=name.replace('.','').replace(',','').replace('(','').replace(')','')
44
+ #result.append(clean_wine_name(name).strip())
45
+ result[n]=clean_wine_name(name).strip()
46
+
47
+
48
+
49
+ items['new_name']=None
50
+ items.loc[items['name'].isin(result.keys()), 'new_name'] = items['name'].map(result)
51
+
52
+ u_nn=list(items[~items['new_name'].isna()]['new_name'].unique())
53
+ res={}
54
+ for i in tqdm(u_nn):
55
+ lenta=len(items[items['new_name']==i])
56
+ if lenta>1:
57
+ res[i]=lenta
58
+
59
+ th=math.sqrt(((np.array(list(res.values())).mean()+np.array(list(res.values())).std())**2)//2)
60
+ other_brands=[i for i,j in res.items() if j>th]
61
+
62
+ reess=check_brands_in_strings_pqdm(other_brands, p_brands)
63
+
64
+ items.loc[items['new_name'].isin(reess.keys()), 'new_brand'] = items['new_name'].map(reess)
65
+
66
+ top_inserts_matching(other_brands, p_brands, items)
preprocess/utils/common/utils.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ from tqdm import tqdm
3
+
4
+
5
+ def remove_quotes(text):
6
+ return re.sub(r'["\']', '', text)
7
+
8
+
9
+ def remove_l(text):
10
+ result = re.sub(r'\bл\b', '', text, flags=re.IGNORECASE)
11
+
12
+ # Убираем возможные лишние пробелы, возникающие после удаления
13
+ result = re.sub(r'\s{2,}', ' ', result).strip()
14
+ return result
15
+
16
+
17
+ def clean_wine_name(name):
18
+ """
19
+ Удаляет в конце строки отдельно стоящие буквы (однобуквенные слова), не входящие в состав других слов.
20
+ Например, "токай л" превратится в "токай".
21
+ """
22
+ # Регулярное выражение ищет:
23
+ # \s+ – один или несколько пробельных символов;
24
+ # \b – граница слова;
25
+ # [A-Za-zА-ЯЁа-яё] – ровно одна буква (латинская или кириллическая);
26
+ # \b – граница слова;
27
+ # \s*$ – любые пробелы до конца строки.
28
+ return re.sub(r'\s+\b[A-Za-zА-ЯЁа-яё]\b\s*$', '', name)
29
+
30
+
31
+ def find_full_word(text, word_list):
32
+ """
33
+ Ищет первое полное вхождение слова из word_list в строке text.
34
+ Возвращает найденное слово или None, если совпадение не найдено.
35
+ """
36
+ for word in word_list:
37
+ pattern = r'\b' + re.escape(word) + r'\b'
38
+ if re.search(pattern, text, re.IGNORECASE):
39
+ return word
40
+ return None
41
+
42
+
43
+ def merge_wine_type(items, colors=None, color_merge_dict=None):
44
+ result=[]
45
+ for row in tqdm(items.iterrows()):
46
+ try:
47
+ if row[1]['type_wine'] is not None:
48
+ color=find_full_word(row[1]['type_wine'], colors)
49
+ if color is not None:
50
+ result.append(color)
51
+ else:
52
+ color=find_full_word(row[1]['name'], colors)
53
+ if color is not None:
54
+ result.append(color)
55
+ else:
56
+ result.append(None)
57
+ else:
58
+ color=find_full_word(row[1]['name'], colors)
59
+ if color is not None:
60
+ result.append(color)
61
+ else:
62
+ result.append(None)
63
+ except Exception as ex:
64
+ print(ex)
65
+ result.append(None)
66
+
67
+ items['new_type_wine']=result
68
+ items['new_type_wine']=items['new_type_wine'].replace(color_merge_dict)
69
+
70
+
71
+ def merge_types(items, products):
72
+ alco_types=[i.strip().lower() for i in products['type'].unique()]
73
+ alco_types.append('ликёр')
74
+ result=[]
75
+ for row in tqdm(items.iterrows()):
76
+ try:
77
+ type_in_name=find_full_word(row[1]['name'], alco_types)
78
+ if type_in_name is not None:
79
+ result.append(type_in_name)
80
+ continue
81
+ if row[1]['type'] is not None:
82
+ type_in_type=find_full_word(row[1]['type'], alco_types)
83
+ if type_in_type is not None:
84
+ result.append(type_in_type)
85
+ else:
86
+ result.append(row[1]['type'])
87
+ else:
88
+ result.append(None)
89
+ except Exception as ex:
90
+ print(ex)
91
+ result.append(None)
92
+
93
+ items['new_type']=result
94
+ items['new_type']=items['new_type'].replace({'ликёр': 'ликер', None: 'unmatched'})
95
+
96
+
97
+ def trim_name(text, words_to_remove):
98
+ """
99
+ Удаляет из текста только те слова, которые полностью совпадают с элементами списка words_to_remove.
100
+
101
+ :param text: Исходная строка.
102
+ :param words_to_remove: Список слов, которые необходимо удалить.
103
+ :return: Обновлённая строка с удалёнными словами.
104
+ """
105
+ # Создаём регулярное выражение, которое ищет любое из указанных слов как отдельное слово.
106
+ # Используем re.escape, чтобы экранировать спецсимволы в словах.
107
+ pattern = r'\b(?:' + '|'.join(re.escape(word) for word in words_to_remove) + r')\b'
108
+ #print(pattern)
109
+
110
+ # Заменяем найденные полные слова на пустую строку.
111
+ new_text = re.sub(pattern, '', text, flags=re.IGNORECASE)
112
+
113
+ # Убираем лишние пробелы, возникающие после удаления слов.
114
+ new_text = re.sub(r'\s+', ' ', new_text).strip()
115
+
116
+ return new_text
117
+
118
+
119
+ def name_trimmer(df, prcess_text, types_and_others):
120
+ result={}
121
+ gbs=[]
122
+ sours=[]
123
+ for idx, row in tqdm(df.iterrows()):
124
+ text, alcohol, volume_or_number, years, production_year, gb, color, sour=prcess_text(str(row['name']))
125
+ text=trim_name(text, types_and_others).replace(',','').replace('.','')
126
+ result[row['id']]=text.lower().strip() #remove_l(text).lower().strip()
127
+
128
+ gbs.append(gb)
129
+ sours.append(sour)
130
+ return result, gbs, sours
preprocess/utils/items/attrs.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def check_spark(row, col_name='name', types=['Игристое', 'игр']):
2
+ if col_name in row.keys():
3
+ for t in types:
4
+ if t.lower() in row[col_name].lower() and 'Пилигрим' not in row[col_name].lower():
5
+ return 'Игристое'
6
+ return None
7
+
8
+
9
+ def check_color_and_sour(row, col_name='type_wine', types=['Белое', 'Розовое', 'Красное']):
10
+ if col_name in row.keys():
11
+ for t in types:
12
+ if t.lower() in row[col_name].lower():
13
+ return 'Вино'
14
+ return None
15
+
16
+
17
+ def is_type_exist(row, types):
18
+ for t in types:
19
+ if t.lower() in row['type'].lower(): # Сравнение без учета регистра
20
+ return t
21
+ return None
22
+
23
+ def check_type(row, types):
24
+ #checker=False
25
+ for t in types:
26
+ if t.lower() in row['name'].lower(): # Сравнение без учета регистра
27
+ return t
28
+ return None
29
+
30
+ def get_type(row, types):
31
+ if 'type' not in row.keys():
32
+ return check_type(row, types)
33
+ elif 'type' in row.keys():
34
+ semi_res=is_type_exist(row, types)
35
+ if semi_res!=None:
36
+ return semi_res
37
+ else:
38
+ return check_type(row, types)
39
+ return None
40
+