File size: 10,468 Bytes
3cc4e3f
cb92a0f
c5b2790
3cc4e3f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cb92a0f
5b19d8a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cb92a0f
 
 
 
 
 
5b19d8a
 
 
cb92a0f
 
 
 
 
 
95c9287
 
 
 
 
 
 
 
c5b2790
95c9287
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c5b2790
95c9287
d4bade4
 
 
 
 
 
95c9287
 
 
 
 
 
 
 
 
 
 
c5b2790
95c9287
 
 
c5b2790
d4bade4
 
 
 
 
 
 
 
c5b2790
 
95c9287
 
 
 
 
c5b2790
95c9287
d4bade4
95c9287
 
 
 
 
d4bade4
5b19d8a
 
 
d4bade4
 
5b19d8a
d4bade4
5b19d8a
 
 
3cc4e3f
95c9287
3cc4e3f
 
 
 
 
 
 
 
 
 
 
 
 
5b19d8a
3cc4e3f
 
 
 
 
 
 
 
 
95c9287
 
 
3cc4e3f
 
 
2956b24
 
 
 
 
 
 
 
 
95c9287
2956b24
 
95c9287
 
c5b2790
2956b24
 
 
c5b2790
3cc4e3f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2956b24
3cc4e3f
 
 
 
 
 
606ca5f
 
 
3cc4e3f
2956b24
 
 
3cc4e3f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c5b2790
2956b24
 
 
 
 
 
c5b2790
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
import re
from transliterate import translit, detect_language
from constants.constants import *
from tqdm import tqdm


def get_delimiter(file_path):
    with open(file_path, 'r', encoding="utf-8") as f:
        ln = f.readline()
        if ',' in ln:
            return ','
        if ';' in ln:
            return ';'
        if '\t' in ln:
            return '\t'
        if '|' in ln:
            return '|'

    raise ValueError(None, "Error parsing CSV file. Cannot detect delimiter")


def verify_csv(csv_file, fixed_csv=None):
    bad_lines = []
    lnnum = 1

    if fixed_csv:
        w = open(fixed_csv, "w", encoding="utf-8")

    with open(csv_file, "r", encoding="utf-8") as f:
        ln = f.readline()

        if fixed_csv:
            w.write(ln)

        while True:
            ln = f.readline()
            if len(ln) == 0:
                break

            if not ln.count('"') % 2 == 0:
                bad_lines.append("(" + str(lnnum) + "): " + ln)

                if fixed_csv:
                    w.write(ln.replace('"', '""', 1))
                #raise Exception("Incorrect quotes at line " + str(lnnum) + " in file [" + csv_file + "]")
            else:
                if fixed_csv:
                    w.write(ln)

            lnnum = lnnum + 1

    if fixed_csv:
        w.close()

    return bad_lines


def normalize_name(name):
    """
    Нормализует строку: если обнаруживается русский язык, транслитерирует её в латиницу,
    приводит к нижнему регистру.
    """
    try:
        if not isinstance(name, str):
            return name

        if detect_language(name) == 'ru':
            return translit(name, 'ru', reversed=True).lower()
    except Exception:
        pass
    return name.lower()

def preprocess_product_brand(name):
    if not isinstance(name, str):
        return str(name)

    name = name.lower()
    name = replace_interword_separators(name)
    return name


def preprocess_product_name(name):
    if not isinstance(name, str):
        return str(name)

    return name.lower()


def replace_interword_separators(name):
    return name.replace("'", "").replace("`", "").replace("’", "").replace('-', '')


def clean_text(text):
    text = remove_quotes(text)

    # Удаляем одиночную звездочку, остающуюся после объема вида "0.75*'
    text = re.sub(r'(\s|^)[\*;](\s|$)', ' ', text)

    # Убираем '()'
    text = re.sub(r'\(\s*\)', ' ', text)

    #text = clean_wine_name(text)

    # Убираем //
    text = re.sub(r'/\s*/', '/', text).strip()

    # Убираем /
    text = re.sub(r'\s+/\s+', ' ', text).strip()

    # Убираем дублирующиеся пробелы
    text = re.sub(r'\s+', ' ', text).strip()
    return text


def normalize_and_clean_brand(brand):
    if not brand:
        return brand

    brand = normalize_name(brand)
    brand = replace_interword_separators(brand)
    brand = clean_text(brand)
    return brand


def normalize_terms_and_attributes(name):
    if not name:
        return name

    for term in TERMS_AND_ATTRIBUTES_VARIATIONS:
        word = find_full_word(name, TERMS_AND_ATTRIBUTES_VARIATIONS[term])
        if word:
            name = name.replace(word, term.lower())
    return name


def normalize_and_clean_name(name):
    if not name:
        return name

    name = normalize_name(name)
    name = normalize_terms_and_attributes(name)
    name = replace_interword_separators(name)
    name = replace_brands_and_names_alternatives(name)
    name = clean_text(name)
    return name



def replace_brands_and_names_alternatives(name):
    if not name:
        return name

    for nnk in BRANDS_AND_NAMES_ALTERNATIVES_DICT:
        word = find_full_word(name, BRANDS_AND_NAMES_ALTERNATIVES_DICT[nnk])
        if word:
            name = name.replace(word, nnk.lower())
    return name


def remove_quotes(text):
    return re.sub(r'["\'«»]', ' ', text)


def clean_wine_name(name):
    """
    Удаляет в конце строки отдельно стоящие буквы (однобуквенные слова), не входящие в состав других слов.
    Например, "токай   л" превратится в "токай".
    """
    # Регулярное выражение ищет:
    # \s+        – один или несколько пробельных символов;
    # \b         – граница слова;
    # [A-Za-zА-ЯЁа-яё] – ровно одна буква (латинская или кириллическая);
    # \b         – граница слова;
    # \s*$       – любые пробелы до конца строки.
    return re.sub(r'\s+\b[A-Za-zА-ЯЁа-яё\-]\b\s*$', '', name)


def find_full_word(text, word_list):
    """
    Ищет первое полное вхождение слова из word_list в строке text.
    Возвращает найденное слово или None, если совпадение не найдено.
    """
    for word in word_list:
        pattern = r'\b' + re.escape(word) + r'\b'
        match = re.search(pattern, text, re.IGNORECASE)
        if match:
            return match.group(0)
    return None


def find_word(text, word_list):
    for word in word_list:
        if word in text:
            return word
    return None


def remove_full_words(text, word_list):
    """
    Ищет все полное вхождение слов из word_list в строке text и удаляет их
    """
    for word in word_list:
        if word:
            pattern = r'\b' + re.escape(word) + r'\b'
            text = re.sub(pattern, ' ', text, flags=re.IGNORECASE)

    return text


def merge_wine_type(items, colors=None, color_merge_dict=None):    
    result=[]
    for row in tqdm(items.iterrows()):
        try:
            #print("merge_wine_type:" + str(row))
            if row[1]['type_wine'] is not None:
                color=find_full_word(row[1]['type_wine'], colors)
                if color is not None:
                    result.append(color)
                else:
                    color=find_full_word(row[1]['name'], colors)
                    if color is not None:
                        result.append(color)
                    else:
                        result.append(None)
            else:
                color=find_full_word(row[1]['name'], colors)
                if color is not None:
                    result.append(color)
                else:
                    result.append(None)
        except Exception as ex:
            print("Error in merge_wine_type: " + str(ex))
            result.append(None)

    items['new_type_wine']=result
    items['new_type_wine']=items['new_type_wine'].replace(color_merge_dict)


def merge_types(items, products, type_merge_dict={}, sub_alco_types=["Бренди", "Шампань", "Шампанское"], product_types = None):
    pt = product_types if product_types else products['type'].unique()
    alco_types=[i.strip().lower() for i in pt]
    alco_types.append('ликёр')
    #alco_types.sort(reverse=True)
    alco_types = sorted(alco_types, reverse=True, key=len)

    result=[]

    for row in tqdm(items.iterrows()):
        try:
            # Parameter 'sub_alco_types' specifies specific alcohol types that usually specified
            # in product / item name along with "parent" type and in this case this subtype should have priority
            # For example, "Вино Шампано Ле Брён де Нёвиль", or "Бренди де Херес"
            if sub_alco_types:
                type_in_name=find_full_word(row[1]['name'], sub_alco_types)
                if type_in_name is not None:
                    result.append(type_in_name)
                    continue

            type_in_name=find_full_word(row[1]['name'], alco_types)
            if type_in_name is not None:
                result.append(type_in_name)
                continue
            if row[1]['type'] is not None:
                type_in_type=find_full_word(row[1]['type'], alco_types)
                if type_in_type is not None:
                    result.append(type_in_type)
                else:
                    result.append(row[1]['type'])
            else:
                result.append(None)
        except Exception as ex:
            print(ex)
            result.append(None)

    items['new_type']=result
    #items['new_type']=items['new_type'].replace({'ликёр': 'ликер', None: 'unmatched'})
    items['new_type'] = items['new_type'].replace(type_merge_dict)


def trim_name(text, words_to_remove):
    """
    Удаляет из текста только те слова, которые полностью совпадают с элементами списка words_to_remove.

    :param text: Исходная строка.
    :param words_to_remove: Список слов, которые необходимо удалить.
    :return: Обновлённая строка с удалёнными словами.
    """
    # Создаём регулярное выражение, которое ищет любое из указанных слов как отдельное слово.
    # Используем re.escape, чтобы экранировать спецсимволы в словах.
    pattern = r'\b(?:' + '|'.join(re.escape(word) for word in words_to_remove) + r')\b'
    #print("Pattern: " + pattern)

    # Заменяем найденные полные слова на пустую строку.
    new_text = re.sub(pattern, '', text, flags=re.IGNORECASE)

    # Убираем лишние пробелы, возникающие после удаления слов.
    new_text = re.sub(r'\s+', ' ', new_text).strip()

    return new_text


'''def is_string(val):
    if not val:
        return False
    elif isinstance(val, str):
        return True

    return False
'''


def detect_language_simple(text):
    for ch in text:
        if (ord(ch) >= ord('А') and ord(ch) <= ord('Я')) or \
                (ord(ch) >= ord('а') and ord(ch) <= ord('я')):
            return 'ru'
        elif (ord(ch) >= ord('A') and ord(ch) <= ord('Z')) or \
                (ord(ch) >= ord('a') and ord(ch) <= ord('z')):
            return 'en'

    return 'xx'