Gainward777 commited on
Commit
0612fb9
·
verified ·
1 Parent(s): cbb77ba

Upload Funcs.py

Browse files
Files changed (1) hide show
  1. Funcs.py +197 -197
Funcs.py CHANGED
@@ -27,31 +27,31 @@ from collections import Counter
27
 
28
 
29
 
30
- def check_spark(row, col_name='name', types=['��������', '���']):
31
  if col_name in row.keys():
32
  for t in types:
33
- if t.lower() in row[col_name].lower() and '��������' not in row[col_name].lower():
34
- return '��������'
35
  return None
36
 
37
- def check_color_and_sour(row, col_name='type_wine', types=['�����', '�������', '�������']):
38
  if col_name in row.keys():
39
  for t in types:
40
  if t.lower() in row[col_name].lower():
41
- return '����'
42
  return None
43
 
44
 
45
  def is_type_exist(row, types):
46
  for t in types:
47
- if t.lower() in row['type'].lower(): # ��������� ��� ����� ��������
48
  return t
49
  return None
50
 
51
  def check_type(row, types):
52
  #checker=False
53
  for t in types:
54
- if t.lower() in row['name'].lower(): # ��������� ��� ����� ��������
55
  return t
56
  return None
57
 
@@ -69,19 +69,19 @@ def get_type(row, types):
69
 
70
  def extract_years(text):
71
  """
72
- ��������� ��������� ����� �����, ������������ ������� (��������: '50 ���', '21 years').
73
  """
74
- # ���������� ��������� ���� ����� ����� '���' ��� 'years' ������ ��������
75
- match = re.search(r'\b(?<!\d)(\d{1,2})\s*(���|years)\b', text, re.IGNORECASE)
76
  if match:
77
- # �������� ����� '���' ��� 'years' ��������� ��������
78
  return f"{match.group(1)} {match.group(2)}"
79
  return None
80
 
81
  def extract_production_year(text):
82
  """
83
- ��������� ��� ������������ (�������������� ����� ��������� 19002099) �� ������.
84
- ��������: '2019'.
85
  """
86
  match = re.search(r'\b(19\d{2}|20\d{2})\b', text)
87
  if match:
@@ -90,19 +90,19 @@ def extract_production_year(text):
90
 
91
  def extract_alcohol_content(text):
92
  """
93
- ��������� ���������� �������� �� ������.
94
- ��������: '40%'.
95
  """
96
  match = re.search(r'(\d{1,2}(?:[.,]\d+)?\s*%)', text)
97
  if match:
98
- # �������� ������� �� ����� ��� ������������ (���� �����)
99
  return match.group(1).replace(' ', '').replace(',', '.')
100
  return None
101
 
102
 
103
  def is_volume(value):
104
  """
105
- ���������, �������� �� �������� �������� ������� (<= 10 ������).
106
  """
107
  try:
108
  volume = float(value)
@@ -112,16 +112,16 @@ def is_volume(value):
112
 
113
  def extract_volume_or_number(text):
114
  """
115
- ��������� ����� ������ ��� ����� ��������� ������ �� ������.
116
- ��������: '0,75', '0.5', ��� '1,5 '.
117
  """
118
- # ������� ����� ����� ������ '' ��� ��� ������� ����� ���
119
- match_with_l = re.search(r'(\d+(?:[\.,]\d+)?\s*[��]|(?:\d+(?:[\.,]\d+)?[��]))', text)
120
  if match_with_l:
121
- return is_volume(match_with_l.group(1).replace(',', '.').replace('', '').replace('', '').strip())
122
 
123
- # ���� �� �������, ���� ������ ����� ��������� ������
124
- match_number = re.search(r'(?<!)\b(\d{1,2}(?:[\.,]\d+))\b(?!\s*(|-er|er|\d{3,}))', text)
125
  if match_number:
126
  return is_volume(match_number.group(1).replace(',', '.'))
127
 
@@ -130,37 +130,37 @@ def extract_volume_or_number(text):
130
 
131
  def get_sour(s):
132
  """
133
- ��������� �� ������ �������� �����, ���� ��� ������������ ��� ��������� �����.
134
- ���������� ������������� �������� �����/����� ��� ��������, ��� ����� ����� ����������
135
- ��������� ����� ��� ��������-�������� ��������.
136
 
137
  Args:
138
- s (str): �������� ������.
139
 
140
  Returns:
141
- str or None: ��������� �������� �����, ���� ��� ������������ ��� ��������� �����, ����� None.
142
  """
143
- # ������ �������� ����
144
  keywords = [
145
  r'brut',
146
  r'semi-sweet',
147
  r'sweet',
148
- r'����',
149
- r'�����',
150
- r'���������',
151
- r'�����������',
152
- r'�������',
153
- r'/���',
154
- r'/��',
155
- r'/',
156
- r'��',
157
- r'���'
158
  ]
159
 
160
- # �������� ������ �������������� ���������� �������� ����� �����,
161
- # ����� ���������, ��� ���������� �� �������� ������ ����� �������� �����.
162
- # (?<!\w) - ����� ����������� �� ������ ���� ������� [a-zA-Z0-9_]
163
- # (?!\w) - ����� ���������� �� ������ ���� ������� [a-zA-Z0-9_]
164
  pattern = re.compile(r'(?<!\w)(?:' + '|'.join(keywords) + r')(?!\w)', re.IGNORECASE)
165
 
166
  match = pattern.search(s)
@@ -172,29 +172,29 @@ def get_sour(s):
172
 
173
  def get_color(s):
174
  """
175
- ��������� ������, ���������� ���������� ���������� ��������,
176
- ���������� �� ���� ������� ���������.
177
 
178
  Args:
179
- strings (list): ������ �����.
180
 
181
  Returns:
182
- dict: �������, ��� ����� ������� �����, �������� ������ ������������ ���������� ��������.
183
  """
184
- # ������ �������� ���� ���� ��� ������
185
- keywords = [r'�������',
186
- r'�����',
187
- r'�������'
188
- r'��',
189
- r'���',
190
- r'����',
191
  r'rosso',
192
  r'roso',
193
  r'roseto',
194
  r'rosetto',
195
  r'red',
196
  r'white']
197
- # ������� ������ ����������� ���������
198
  pattern = re.compile('|'.join(keywords), re.IGNORECASE)
199
  #gift_box_phrases={}
200
  #for idx, s in enumerate(strings):
@@ -207,16 +207,16 @@ def get_color(s):
207
 
208
  def get_GB(s):
209
  """
210
- ��������� ������, ���������� ���������� ���������� ��������,
211
- ���������� �� ���� ������� ���������.
212
 
213
  Args:
214
- strings (list): ������ �����.
215
 
216
  Returns:
217
- dict: �������, ��� ����� ������� �����, �������� ������ ������������ ���������� ��������.
218
  """
219
- # ������ �������� ���� ���� ��� ������
220
  keywords = [r'cristal decanter in oak gift box',
221
  r'in the carton gift box with 2 glasses',
222
  r'decanter in the carton gift box',
@@ -239,30 +239,30 @@ def get_GB(s):
239
  r'in wood case'
240
  r'in wood box',
241
  r'in wood',
242
- r'����������� �������� ���������� �������� �� ����',
243
- r'�������� ���������� �������� �� �������',
244
- r' ���������� �������� �� ������� 2 ��������'
245
- r' ���������� �������� �� �������',
246
- r' ���������� �������� �� ����',
247
- r' ������ ���������� �������',
248
- r' ���������� ��������',
249
- r'���������� ��������',
250
- r'���������� �����',
251
- r' ���������� �������',
252
- r'���������� �������',
253
- r' /+2 ���������',
254
- r' / �� �������',
255
- r' /+�����',
256
- r' / (���.�������)',
257
- r' / ������',
258
- r' /',
259
- r' ',
260
- r'/��',
261
- r'/',
262
- r' ����',
263
- r'����',
264
- r'��']
265
- # ������� ������ ����������� ���������
266
  pattern = re.compile('|'.join(keywords), re.IGNORECASE)
267
  #gift_box_phrases={}
268
  #for idx, s in enumerate(strings):
@@ -291,7 +291,7 @@ def prcess_text(origin):
291
  if volume_or_number is not None:
292
  volume_with_comma=str(volume_or_number).replace('.', ',')
293
  text=text.replace(str(volume_or_number), '').replace(str(volume_with_comma), '')
294
- text=text.replace(str(volume_or_number)+' ', '').replace(str(volume_with_comma)+' ', '')
295
  # else:
296
  # volume_or_number=re_extract_volume(text)
297
  # if volume_or_number is not None:
@@ -299,7 +299,7 @@ def prcess_text(origin):
299
  # text=text.replace(str(volume_or_number), '').replace(str(volume_with_comma), '')
300
  years = extract_years(text)
301
  if years is not None:
302
- text=text.replace(str(years), '').replace(str('��������'), '').replace(str('��������'), '').replace(str('aging'), '')
303
  production_year = extract_production_year(text)
304
  if production_year is not None:
305
  text=text.replace(str(production_year), '')
@@ -322,30 +322,30 @@ def prcess_text(origin):
322
 
323
 
324
  def remove_l(text):
325
- result = re.sub(r'\b\b', '', text, flags=re.IGNORECASE)
326
 
327
- # ������� ��������� ������ �������, ����������� ����� ��������
328
  result = re.sub(r'\s{2,}', ' ', result).strip()
329
  return result
330
 
331
 
332
  def trim_name(text, words_to_remove):
333
  """
334
- ������� �� ������ ������ �� �����, ������� ��������� ��������� ���������� ������ words_to_remove.
335
 
336
- :param text: �������� ������.
337
- :param words_to_remove: ������ ����, ������� ���������� �������.
338
- :return: ���������� ������ ��������� �������.
339
  """
340
- # ������ ���������� ���������, ������� ���� ����� �� ��������� ���� ��� ��������� �����.
341
- # ���������� re.escape, ����� ������������ ����������� ������.
342
  pattern = r'\b(?:' + '|'.join(re.escape(word) for word in words_to_remove) + r')\b'
343
  #print(pattern)
344
 
345
- # �������� ��������� ������ ����� �� ������ ������.
346
  new_text = re.sub(pattern, '', text, flags=re.IGNORECASE)
347
 
348
- # ������� ������ �������, ����������� ����� �������� ����.
349
  new_text = re.sub(r'\s+', ' ', new_text).strip()
350
 
351
  return new_text
@@ -448,16 +448,16 @@ def process_products(products):
448
 
449
  def fill_brands_in_dataframe(brands, df, col_name='new_brand', is_brand=True):
450
  """
451
- ��������� ������� 'brand' DataFrame ���������� ��������.
452
 
453
- :param brands: ������ �������.
454
- :param df: DataFrame ��������� ['id', 'brand', 'name', ...].
455
- :return: DataFrame ���������� �������� 'brand'.
456
  """
457
- # �������������� ������� ��� �������� ������ �������
458
  automaton = Automaton()
459
 
460
- # ��������� ������ �������
461
  for idx, brand in enumerate(brands):
462
  if isinstance(brand, str) and brand:
463
  automaton.add_word(brand.lower(), (idx, brand))
@@ -466,18 +466,18 @@ def fill_brands_in_dataframe(brands, df, col_name='new_brand', is_brand=True):
466
 
467
  def find_brand(name):
468
  """
469
- ������� ������ ����� ��� ������� �����.
470
  """
471
  matched_brands = set()
472
  for _, (_, brand) in automaton.iter(name.lower()):
473
- # ���������, ��� ����� ����������� ��� ��������� �����
474
  if re.search(rf'\b{re.escape(brand.lower())}\b', name.lower()):
475
  matched_brands.add(brand)
476
 
477
- # ���������� ����� ������������ ������ (����� ������ ����������)
478
  return max(matched_brands, key=len) if matched_brands else None
479
 
480
- # ��������� ������� brand ������ ��� ������ ��������
481
  # df['new_brand'] = df.apply(
482
  # lambda row: find_brand(row['name']), #if pd.isna(row['brand']) else row['brand'],
483
  # axis=1
@@ -519,31 +519,31 @@ def get_same_brands(products, items):
519
 
520
  def match_brands_improved(items_brands, prods_brands, threshold=85):
521
  """
522
- ���������� �������� ������������� ������� ������ ��������� ������ ���������� ������.
523
 
524
- :param items_brands: ������ ������� �� ���������� items.
525
- :param prods_brands: ������ ������� �� ���������� prods.
526
- :param threshold: ����� �������� ��� ��������� ������.
527
- :return: ������� ������������ {����� �� items: ��������� ����� �� prods}.
528
  """
529
  brand_mapping = {}
530
 
531
  for item_brand in tqdm(items_brands):
532
  if isinstance(item_brand, str):
533
- # ��������� ����� �� �����
534
  parts = [part.strip() for part in re.split(r"[\/\(\)]", item_brand) if part.strip()]
535
  best_match = None
536
  best_score = 0
537
 
538
  for part in parts:
539
  match, score, _ = process.extractOne(part, prods_brands, scorer=fuzz.ratio)
540
- # ���������� �� ����� ����� ������
541
  if score >= threshold and abs(len(part) - len(match)) / len(part) <= 0.3:
542
  if score > best_score:
543
  best_match = match
544
  best_score = score
545
 
546
- # ���������� ����������
547
  if best_match:
548
  brand_mapping[item_brand] = best_match#, best_score)
549
 
@@ -552,14 +552,14 @@ def match_brands_improved(items_brands, prods_brands, threshold=85):
552
 
553
  def normalize(text):
554
  """
555
- �������� ����� ������� �������� ��������������� ��� ��������.
556
  """
557
  return unidecode(text.lower())
558
 
559
  def build_regex_for_brands(brands):
560
  """
561
- ����������� ������ ������ ���� ���������� ��������� ��� ������� ������.
562
- ���������� ���������������� ������� �������: ��������������� �������� -> ������������ ��������.
563
  """
564
  norm_to_brand = {}
565
  for brand in brands:
@@ -571,20 +571,20 @@ def build_regex_for_brands(brands):
571
 
572
  def process_string(s, regex_pattern, norm_to_brand, norm_brand_list, index_to_brand, threshold):
573
  """
574
- ������������ ���� ������:
575
- 1. �������� ����� ����� ����� ���������� ���������.
576
- 2. ���� ������� ���������� ��� ��������� ������ ��������� �������� �����.
577
- ���������� ������: (�������� ������, ��������� ����� ��� None).
578
  """
579
  norm_s = normalize(s)
580
- # �������� ����� ����� ����� ���������� ���������
581
  match = regex_pattern.search(norm_s)
582
  if match:
583
  return s, norm_to_brand[match.group(0)]
584
 
585
- # ���� ������� ���������� ���, ��������� ������ �� ������������ ����������� �����
586
  parts = [part.strip() for part in re.split(r"[\/\(\)]", s) if part.strip()]
587
- parts.append(s) # ������ ���� ������
588
  best_match = None
589
  best_score = 0
590
  for part in parts:
@@ -603,17 +603,17 @@ def process_string(s, regex_pattern, norm_to_brand, norm_brand_list, index_to_br
603
 
604
  def check_brands_in_strings_pqdm(strings, brands, threshold=85, n_jobs=8):
605
  """
606
- ����� ������� ������� ������ ��������� ��������� ��������������.
607
- ���������� ��������������� ����� ����� ���������� ��������� , ��� �������������,
608
- �������� �����. ��������� ����������� ����������� ������������ ��������� ������� pqdm.
609
-
610
- :param strings: ������ ����� ��� ������ �������.
611
- :param brands: ������ ������� ��� ������.
612
- :param threshold: ����� �������� ��� ��������� ������.
613
- :param n_jobs: ����� ������� ������� (��� ���������, ���� ������������ pqdm.processes).
614
- :return: ������� ���� {������: ��������� �����}.
615
  """
616
- # �������������� ������ ��������������� ������� ������������� �������� ������������� ��������.
617
  norm_brand_list = []
618
  index_to_brand = []
619
  for brand in brands:
@@ -621,14 +621,14 @@ def check_brands_in_strings_pqdm(strings, brands, threshold=85, n_jobs=8):
621
  norm_brand_list.append(norm_brand)
622
  index_to_brand.append(brand)
623
 
624
- # ������� ��������������� ������� ��� ������� ������.
625
  regex_pattern, norm_to_brand = build_regex_for_brands(brands)
626
 
627
- # ���������� ��������������� �������, ����������� ����������� ���������.
628
  def process_string_wrapper(s):
629
  return process_string(s, regex_pattern, norm_to_brand, norm_brand_list, index_to_brand, threshold)
630
 
631
- # ������������ ������ ����������� ������������ ���������.
632
  results = pqdm(strings, process_string_wrapper, n_jobs=n_jobs)
633
 
634
  brand_mapping = {}
@@ -640,34 +640,34 @@ def check_brands_in_strings_pqdm(strings, brands, threshold=85, n_jobs=8):
640
 
641
  def clean_wine_name(name):
642
  """
643
- ������� ����� ������ �������� ������� ����� (������������� �����), �� �������� ������ ������ ����.
644
- ��������, "����� " ����������� "�����".
645
  """
646
- # ���������� ��������� ����:
647
- # \s+ ���� ��� ��������� ���������� ��������;
648
- # \b ������� �����;
649
- # [A-Za-z-ߨ�-��] ����� ���� ����� (��������� ��� �������������);
650
- # \b ������� �����;
651
- # \s*$ ����� ������� �� ����� ������.
652
- return re.sub(r'\s+\b[A-Za-z-ߨ�-��]\b\s*$', '', name)
653
 
654
 
655
  def most_common_words(strings, top_n=None):
656
  """
657
- ���������� ������ �������� ����� ������������� ���� �� ������ �����.
658
 
659
- ���������:
660
- - strings: ������ �����
661
- - top_n: ���������� �������� ����� ������������� ����, ������� ���������� �������.
662
- ���� None, ������������ ��� �����, ��������������� �� �������.
663
 
664
- ����������:
665
- - ������ �������� (�����, �������)
666
  """
667
  all_words = []
668
  for s in tqdm(strings):
669
  s=str(s)
670
- # ��������� �����, �������� �� ������� �������� ������� ����������
671
  words = re.findall(r'\w+', s.lower())
672
  all_words.extend(words)
673
 
@@ -681,12 +681,12 @@ def top_inserts_matching(other_brands, p_brands, items, th=65):
681
  for i in other_brands:
682
  l=i.split('/')
683
  if len(l)>2:
684
- replaced[l[0].replace('����','')]=i
685
  else:
686
- if '����' in i:
687
- replaced[i.replace('����','')]=i
688
 
689
- ob=[i.split('/')[0].replace('����','') for i in other_brands]
690
  rr60_ob=check_brands_in_strings_pqdm(ob, p_brands, threshold=th)
691
 
692
  result={}
@@ -704,7 +704,7 @@ def process_unbrended_names(items, p_brands, types, grape_varieties, onther_word
704
  for n in tqdm(items[items['new_brand'].isna()]['name'].values):
705
 
706
  name, alcohol, volume_or_number, years, production_year, gb, color, sour=prcess_text(n)
707
- #name, alcohol, volume_or_number, years, production_year, gb, color, sour=prcess_text('���� ����� ������� /��. ���.0.75')
708
  name=trim_name(name, types)
709
  name=trim_name(name, grape_varieties)
710
  name=trim_name(name, onther_words)
@@ -735,8 +735,8 @@ def process_unbrended_names(items, p_brands, types, grape_varieties, onther_word
735
 
736
  def find_full_word(text, word_list):
737
  """
738
- ���� ������ ������ ��������� ����� �� word_list ������ text.
739
- ���������� ��������� ����� ��� None, ���� ���������� �� �������.
740
  """
741
  for word in word_list:
742
  pattern = r'\b' + re.escape(word) + r'\b'
@@ -780,7 +780,7 @@ def merge_wine_type(items, colors=None, color_merge_dict=None):
780
 
781
  def merge_types(items, products):
782
  alco_types=[i.strip().lower() for i in products['type'].unique()]
783
- alco_types.append('����')
784
  result=[]
785
  for row in tqdm(items.iterrows()):
786
  try:
@@ -801,13 +801,13 @@ def merge_types(items, products):
801
  result.append(None)
802
 
803
  items['new_type']=result
804
- items['new_type']=items['new_type'].replace({'����': '�����', None: 'unmatched'})
805
 
806
 
807
  def normalize_name(name):
808
  """
809
- ����������� ������: ���� �������������� ������� ����, ��������������� ��������,
810
- �������� ������� ��������.
811
  """
812
  try:
813
  if detect_language(name) == 'ru':
@@ -818,13 +818,13 @@ def normalize_name(name):
818
 
819
  def prepare_groups_with_ids(items_df):
820
  """
821
- ��������������� ����������� ������ �� items �� (new_brand, type, volume, new_type_wine, sour)
822
- ������ ���������������� ��������.
823
 
824
- ��������� ������� 'norm_name', ����� ������������� �������� name ���� ��� �������.
825
 
826
- :param items_df: DataFrame ��������� 'new_brand', 'type', 'name', 'id', 'volume', 'new_type_wine', 'sour'.
827
- :return: ������� {(new_brand, type, volume, new_type_wine, sour): [(id, name, norm_name, volume, new_type_wine, sour)]}.
828
  """
829
  items_df = items_df.copy()
830
  items_df['norm_name'] = items_df['name'].apply(normalize_name)
@@ -836,11 +836,11 @@ def prepare_groups_with_ids(items_df):
836
 
837
  def prepare_groups_by_alternative_keys(items_df):
838
  """
839
- ����������� ������ �� items �� (new_type_wine, new_type, volume, sour) ����������� id, new_brand,
840
- ������������� ���������������� �����.
841
 
842
- :param items_df: DataFrame ��������� 'new_brand', 'new_type_wine', 'new_type', 'volume', 'name', 'id', 'sour'.
843
- :return: ������� {(new_type_wine, new_type, volume, sour): [(id, new_brand, name, norm_name, volume, new_type_wine, sour)]}.
844
  """
845
  items_df = items_df.copy()
846
  items_df['norm_name'] = items_df['name'].apply(normalize_name)
@@ -853,26 +853,26 @@ def prepare_groups_by_alternative_keys(items_df):
853
 
854
  def new_find_matches_with_ids(products_df, items_groups, items_df, name_threshold=85):
855
  """
856
- ����� ���������� ����������� id ��������� ������, ��������� ������� ��������������
857
- ��������������� ������.
858
 
859
- ������������ ��� �������:
860
- - ������: ����� �� ������� (brand, type, volume, new_type_wine, sour);
861
- - ������: ��� ��������� ��� ���������� ���� �� �������������� ������� (new_type_wine, new_type, volume, sour),
862
- �������� ����� �������� �������.
863
 
864
- ��������� ������������ �� ������� norm_name, ��� ������ ������������ ������������ name.
865
 
866
- :param products_df: DataFrame ��������� 'id', 'brand', 'type', 'name', 'volume', 'new_type_wine', 'sour', 'new_type'.
867
- :param items_groups: �������, �������������� �������� prepare_groups_with_ids.
868
- :param items_df: DataFrame ������ ��������� 'id', 'new_brand', 'new_type_wine', 'new_type', 'volume', 'name', 'sour'.
869
- :param name_threshold: ����� �������� ��� fuzzy matching.
870
- :return: DataFrame ������������ ��������� 'matched_items' (������ ����������) 'alternative' (�������������� ����������).
871
  """
872
  results = []
873
- no_match_products = [] # ������ ��� �������� ��������� ��� ���������� �������� ������
874
 
875
- # ������ ������: ����� �� ������� (brand, type, volume, new_type_wine, sour)
876
  for idx, product in tqdm(products_df.iterrows(), total=len(products_df)):
877
  product_brand = product['brand']
878
  product_type = product['type']
@@ -884,7 +884,7 @@ def new_find_matches_with_ids(products_df, items_groups, items_df, name_threshol
884
  key = (product_brand, product_type, product_volume, product_type_wine, product_sour)
885
  items_data = items_groups.get(key, [])
886
  if items_data:
887
- # �������������: id, ������������ ���, ��������������� ���, volume, new_type_wine, sour
888
  items_ids, items_names, items_norm_names, items_volumes, item_type_wine, items_sour = zip(*items_data)
889
  else:
890
  items_ids, items_names, items_norm_names, items_volumes, item_type_wine, items_sour = ([], [], [], [], [], [])
@@ -911,13 +911,13 @@ def new_find_matches_with_ids(products_df, items_groups, items_df, name_threshol
911
  results.append({
912
  'product_id': product['id'],
913
  'matched_items': matched_items,
914
- 'alternative': [] # ����������� �� ������ �������
915
  })
916
 
917
- # ���������� �������������� ����������� �� (new_type_wine, new_type, volume, sour)
918
  groups_by_alternative_keys = prepare_groups_by_alternative_keys(items_df)
919
 
920
- # ������ ������: ��� ��������� ��� ���������� ���� �� �������������� �������
921
  for idx, product in tqdm(no_match_products):
922
  product_brand = product['brand']
923
  product_type_wine = product['new_type_wine']
@@ -928,7 +928,7 @@ def new_find_matches_with_ids(products_df, items_groups, items_df, name_threshol
928
 
929
  alt_key = (product_type_wine, product_type, product_volume, product_sour)
930
  type_items = groups_by_alternative_keys.get(alt_key, [])
931
- # ���������, �������� ����� �������� �������
932
  filtered_items = [item for item in type_items if item[1] != product_brand]
933
  if filtered_items:
934
  alt_ids, alt_brands, alt_names, alt_norm_names, alt_volumes, alt_type_wine, alt_sour = zip(*filtered_items)
@@ -960,8 +960,8 @@ def new_find_matches_with_ids(products_df, items_groups, items_df, name_threshol
960
 
961
  def contains_full_word(word, text, case_sensitive=True):
962
  """
963
- ���������, ���������� �� ����� word ������ text ��� ��������� �����.
964
- �������� case_sensitive �����, ��������� �� �������.
965
  """
966
  flags = 0 if case_sensitive else re.IGNORECASE
967
  pattern = r'\b' + re.escape(word) + r'\b'
@@ -978,7 +978,7 @@ def unwrap_brands(products):
978
  for j in new_brands:
979
  if contains_full_word(i, j, case_sensitive=False):
980
  if i != j:
981
- #if len(i)>1:#i != '' and i != "":
982
  res[j]=i
983
  return res
984
 
 
27
 
28
 
29
 
30
+ def check_spark(row, col_name='name', types=['Игристое', 'игр']):
31
  if col_name in row.keys():
32
  for t in types:
33
+ if t.lower() in row[col_name].lower() and 'Пилигрим' not in row[col_name].lower():
34
+ return 'Игристое'
35
  return None
36
 
37
+ def check_color_and_sour(row, col_name='type_wine', types=['Белое', 'Розовое', 'Красное']):
38
  if col_name in row.keys():
39
  for t in types:
40
  if t.lower() in row[col_name].lower():
41
+ return 'Вино'
42
  return None
43
 
44
 
45
  def is_type_exist(row, types):
46
  for t in types:
47
+ if t.lower() in row['type'].lower(): # Сравнение без учета регистра
48
  return t
49
  return None
50
 
51
  def check_type(row, types):
52
  #checker=False
53
  for t in types:
54
+ if t.lower() in row['name'].lower(): # Сравнение без учета регистра
55
  return t
56
  return None
57
 
 
69
 
70
  def extract_years(text):
71
  """
72
+ Извлекает сочетание числа и слова, указывающего возраст (например: '50 лет', '21 years').
73
  """
74
+ # Регулярное выражение ищет числа и слова 'лет' или 'years' с учетом регистра
75
+ match = re.search(r'\b(?<!\d)(\d{1,2})\s*(лет|years)\b', text, re.IGNORECASE)
76
  if match:
77
+ # Приводим слово 'лет' или 'years' к исходному регистру
78
  return f"{match.group(1)} {match.group(2)}"
79
  return None
80
 
81
  def extract_production_year(text):
82
  """
83
+ Извлекает год производства (четырехзначное число в диапазоне 19002099) из строки.
84
+ Например: '2019'.
85
  """
86
  match = re.search(r'\b(19\d{2}|20\d{2})\b', text)
87
  if match:
 
90
 
91
  def extract_alcohol_content(text):
92
  """
93
+ Извлекает содержание алкоголя из строки.
94
+ Например: '40%'.
95
  """
96
  match = re.search(r'(\d{1,2}(?:[.,]\d+)?\s*%)', text)
97
  if match:
98
+ # Заменяем запятую на точку для единообразия (если нужно)
99
  return match.group(1).replace(' ', '').replace(',', '.')
100
  return None
101
 
102
 
103
  def is_volume(value):
104
  """
105
+ Проверяет, является ли значение валидным объемом (<= 10 литров).
106
  """
107
  try:
108
  volume = float(value)
 
112
 
113
  def extract_volume_or_number(text):
114
  """
115
+ Извлекает объем в литрах или число с плавающей точкой из строки.
116
+ Например: '0,75л', '0.5', или '1,5 л'.
117
  """
118
+ # Попытка найти объем с буквой 'л' или без пробела перед ней
119
+ match_with_l = re.search(r'(\d+(?:[\.,]\d+)?\s*[лЛ]|(?:\d+(?:[\.,]\d+)?[лЛ]))', text)
120
  if match_with_l:
121
+ return is_volume(match_with_l.group(1).replace(',', '.').replace('л', '').replace('Л', '').strip())
122
 
123
+ # ��сли не найдено, ищем просто число с плавающей точкой
124
+ match_number = re.search(r'(?<!)\b(\d{1,2}(?:[\.,]\d+))\b(?!\s*(|-er|er|\d{3,}))', text)
125
  if match_number:
126
  return is_volume(match_number.group(1).replace(',', '.'))
127
 
 
130
 
131
  def get_sour(s):
132
  """
133
+ Извлекает из строки ключевое слово, если оно присутствует как отдельное слово.
134
+ Использует отрицательные просмотр назад/вперёд для проверки, что перед и после найденного
135
+ ключевого слова нет буквенно-цифровых символов.
136
 
137
  Args:
138
+ s (str): Исходная строка.
139
 
140
  Returns:
141
+ str or None: Найденное ключевое слово, если оно присутствует как отдельное слово, иначе None.
142
  """
143
+ # Список ключевых слов
144
  keywords = [
145
  r'brut',
146
  r'semi-sweet',
147
  r'sweet',
148
+ r'брют',
149
+ r'сухое',
150
+ r'полусухое',
151
+ r'полусладкое',
152
+ r'сладкое',
153
+ r'п/сух',
154
+ r'п/сл',
155
+ r'п/с',
156
+ r'сл',
157
+ r'сух'
158
  ]
159
 
160
+ # Собираем шаблон с использованием негативных просмотр назад и вперёд,
161
+ # чтобы убедиться, что совпадение не является частью более длинного слова.
162
+ # (?<!\w) - перед совпадением не должно быть символа [a-zA-Z0-9_]
163
+ # (?!\w) - после совпадения не должно быть символа [a-zA-Z0-9_]
164
  pattern = re.compile(r'(?<!\w)(?:' + '|'.join(keywords) + r')(?!\w)', re.IGNORECASE)
165
 
166
  match = pattern.search(s)
 
172
 
173
  def get_color(s):
174
  """
175
+ Извлекает строки, содержащие упоминания о подарочной упаковке,
176
+ и возвращает их в виде словаря с индексами.
177
 
178
  Args:
179
+ strings (list): Список строк.
180
 
181
  Returns:
182
+ dict: Словарь, где ключи индексы строк, а значения строки с упоминаниями о подарочной упаковке.
183
  """
184
+ # Список ключевых слов и фраз для поиска
185
+ keywords = [r'красное',
186
+ r'белое',
187
+ r'розовое'
188
+ r'кр',
189
+ r'бел',
190
+ r'розе',
191
  r'rosso',
192
  r'roso',
193
  r'roseto',
194
  r'rosetto',
195
  r'red',
196
  r'white']
197
+ # Создаем шаблон регулярного выражения
198
  pattern = re.compile('|'.join(keywords), re.IGNORECASE)
199
  #gift_box_phrases={}
200
  #for idx, s in enumerate(strings):
 
207
 
208
  def get_GB(s):
209
  """
210
+ Извлекает строки, содержащие упоминания о подарочной упаковке,
211
+ и возвращает их в виде словаря с индексами.
212
 
213
  Args:
214
+ strings (list): Список строк.
215
 
216
  Returns:
217
+ dict: Словарь, где ключи индексы строк, а значения строки с упоминаниями о подарочной упаковке.
218
  """
219
+ # Список ключевых слов и фраз для поиска
220
  keywords = [r'cristal decanter in oak gift box',
221
  r'in the carton gift box with 2 glasses',
222
  r'decanter in the carton gift box',
 
239
  r'in wood case'
240
  r'in wood box',
241
  r'in wood',
242
+ r'хрустальный декантер в подарочной упаковке из дуба',
243
+ r'декантер в подарочной упаковке из картона',
244
+ r'в подарочной упаковке из картона с 2 бокалами'
245
+ r'в подарочной упаковке из картона',
246
+ r'в подарочной упаковке из Дуба',
247
+ r'в П У графин и деревянная коробка',
248
+ r'в подарочной упаковке',
249
+ r'подарочная упаковка',
250
+ r'подарочный набор',
251
+ r'в деревянной коробке',
252
+ r'деревянная коробка',
253
+ r'в п/у+2 бокаланов',
254
+ r'в п/у из картона',
255
+ r'в п/у+бокал',
256
+ r'в п/у (дер.коробке)',
257
+ r'в п/у солома',
258
+ r'в п/у',
259
+ r'в п у',
260
+ r'п/уп',
261
+ r'п/у',
262
+ r'в тубе',
263
+ r'туба',
264
+ r'ПУ']
265
+ # Создаем шаблон регулярного выражения
266
  pattern = re.compile('|'.join(keywords), re.IGNORECASE)
267
  #gift_box_phrases={}
268
  #for idx, s in enumerate(strings):
 
291
  if volume_or_number is not None:
292
  volume_with_comma=str(volume_or_number).replace('.', ',')
293
  text=text.replace(str(volume_or_number), '').replace(str(volume_with_comma), '')
294
+ text=text.replace(str(volume_or_number)+' л', '').replace(str(volume_with_comma)+' л', '')
295
  # else:
296
  # volume_or_number=re_extract_volume(text)
297
  # if volume_or_number is not None:
 
299
  # text=text.replace(str(volume_or_number), '').replace(str(volume_with_comma), '')
300
  years = extract_years(text)
301
  if years is not None:
302
+ text=text.replace(str(years), '').replace(str('выдержка'), '').replace(str('Выдержка'), '').replace(str('aging'), '')
303
  production_year = extract_production_year(text)
304
  if production_year is not None:
305
  text=text.replace(str(production_year), '')
 
322
 
323
 
324
  def remove_l(text):
325
+ result = re.sub(r'\bл\b', '', text, flags=re.IGNORECASE)
326
 
327
+ # Убираем возможные лишние пробелы, возникающие после удаления
328
  result = re.sub(r'\s{2,}', ' ', result).strip()
329
  return result
330
 
331
 
332
  def trim_name(text, words_to_remove):
333
  """
334
+ Удаляет из текста только те слова, которые полностью совпадают с элементами списка words_to_remove.
335
 
336
+ :param text: Исходная строка.
337
+ :param words_to_remove: Список слов, которые необходимо удалить.
338
+ :return: Обновлённая строка с удалёнными словами.
339
  """
340
+ # Создаём регулярное выражение, которое ищет любое из указанных слов как отдельное слово.
341
+ # Используем re.escape, чтобы экранировать спецсимволы в словах.
342
  pattern = r'\b(?:' + '|'.join(re.escape(word) for word in words_to_remove) + r')\b'
343
  #print(pattern)
344
 
345
+ # Заменяем найденные полные слова на пустую строку.
346
  new_text = re.sub(pattern, '', text, flags=re.IGNORECASE)
347
 
348
+ # Убираем лишние пробелы, возникающие после удаления слов.
349
  new_text = re.sub(r'\s+', ' ', new_text).strip()
350
 
351
  return new_text
 
448
 
449
  def fill_brands_in_dataframe(brands, df, col_name='new_brand', is_brand=True):
450
  """
451
+ Заполняет колонку 'brand' в DataFrame найденными брендами.
452
 
453
+ :param brands: Список брендов.
454
+ :param df: DataFrame с колонками ['id', 'brand', 'name', ...].
455
+ :return: DataFrame с обновлённой колонкой 'brand'.
456
  """
457
+ # Инициализируем автомат для быстрого поиска брендов
458
  automaton = Automaton()
459
 
460
+ # Добавляем бренды в автомат
461
  for idx, brand in enumerate(brands):
462
  if isinstance(brand, str) and brand:
463
  automaton.add_word(brand.lower(), (idx, brand))
 
466
 
467
  def find_brand(name):
468
  """
469
+ Находит лучший бренд для данного имени.
470
  """
471
  matched_brands = set()
472
  for _, (_, brand) in automaton.iter(name.lower()):
473
+ # Проверяем, что бренд встречается как отдельное слово
474
  if re.search(rf'\b{re.escape(brand.lower())}\b', name.lower()):
475
  matched_brands.add(brand)
476
 
477
+ # Возвращаем бренд с максимальной длиной (более точное совпадение)
478
  return max(matched_brands, key=len) if matched_brands else None
479
 
480
+ # Обновляем колонку brand только для пустых значений
481
  # df['new_brand'] = df.apply(
482
  # lambda row: find_brand(row['name']), #if pd.isna(row['brand']) else row['brand'],
483
  # axis=1
 
519
 
520
  def match_brands_improved(items_brands, prods_brands, threshold=85):
521
  """
522
+ Улучшенный алгоритм сопоставления брендов с учётом нечёткого поиска и фильтрации ошибок.
523
 
524
+ :param items_brands: Список брендов из датафрейма items.
525
+ :param prods_brands: Список брендов из датафрейма prods.
526
+ :param threshold: Порог сходства для нечёткого поиска.
527
+ :return: Словарь соответствий {бренд из items: ближайший бренд из prods}.
528
  """
529
  brand_mapping = {}
530
 
531
  for item_brand in tqdm(items_brands):
532
  if isinstance(item_brand, str):
533
+ # Разделяем бренд на части
534
  parts = [part.strip() for part in re.split(r"[\/\(\)]", item_brand) if part.strip()]
535
  best_match = None
536
  best_score = 0
537
 
538
  for part in parts:
539
  match, score, _ = process.extractOne(part, prods_brands, scorer=fuzz.ratio)
540
+ # Фильтрация по длине строк и порогу
541
  if score >= threshold and abs(len(part) - len(match)) / len(part) <= 0.3:
542
  if score > best_score:
543
  best_match = match
544
  best_score = score
545
 
546
+ # Сохранение результата
547
  if best_match:
548
  brand_mapping[item_brand] = best_match#, best_score)
549
 
 
552
 
553
  def normalize(text):
554
  """
555
+ Приводит текст к нижнему регистру и транслитерирует его в латиницу.
556
  """
557
  return unidecode(text.lower())
558
 
559
  def build_regex_for_brands(brands):
560
  """
561
+ Нормализует бренды и создаёт одно регулярное выражение для точного поиска.
562
+ Возвращает скомпилированный паттерн и словарь: нормализованное название -> оригинальное название.
563
  """
564
  norm_to_brand = {}
565
  for brand in brands:
 
571
 
572
  def process_string(s, regex_pattern, norm_to_brand, norm_brand_list, index_to_brand, threshold):
573
  """
574
+ Обрабатывает одну строку:
575
+ 1. Пытается найти бренд через регулярное выражение.
576
+ 2. Если точного совпадения нет разбивает строку и выполняет нечёткий поиск.
577
+ Возвращает кортеж: (исходная строка, найденный бренд или None).
578
  """
579
  norm_s = normalize(s)
580
+ # Пытаемся найти бренд через регулярное выражение
581
  match = regex_pattern.search(norm_s)
582
  if match:
583
  return s, norm_to_brand[match.group(0)]
584
 
585
+ # Если точного совпадения нет, разбиваем строку по разделителям и анализируем части
586
  parts = [part.strip() for part in re.split(r"[\/\(\)]", s) if part.strip()]
587
+ parts.append(s) # анализ всей строки
588
  best_match = None
589
  best_score = 0
590
  for part in parts:
 
603
 
604
  def check_brands_in_strings_pqdm(strings, brands, threshold=85, n_jobs=8):
605
  """
606
+ Поиск брендов в строках с учетом вариантов написания и транслитерации.
607
+ Использует предварительный поиск через регулярное выражение и, при необходимости,
608
+ нечёткий поиск. Обработка выполняется параллельно с отображением прогресса с помощью pqdm.
609
+
610
+ :param strings: Список строк для поиска брендов.
611
+ :param brands: Список брендов для поиска.
612
+ :param threshold: Порог сходства для нечёткого поиска.
613
+ :param n_jobs: Число рабочих потоков (или процессов, если использовать pqdm.processes).
614
+ :return: Словарь вида {строка: найденный бренд}.
615
  """
616
+ # Подготавливаем список нормализованных брендов и сопоставление индексов с оригинальными брендами.
617
  norm_brand_list = []
618
  index_to_brand = []
619
  for brand in brands:
 
621
  norm_brand_list.append(norm_brand)
622
  index_to_brand.append(brand)
623
 
624
+ # Создаем комбинированный паттерн для точного поиска.
625
  regex_pattern, norm_to_brand = build_regex_for_brands(brands)
626
 
627
+ # Определяем вспомогательную функцию, закрывающую необходимые параметры.
628
  def process_string_wrapper(s):
629
  return process_string(s, regex_pattern, norm_to_brand, norm_brand_list, index_to_brand, threshold)
630
 
631
+ # Обрабатываем строки параллельно с отображением прогресса.
632
  results = pqdm(strings, process_string_wrapper, n_jobs=n_jobs)
633
 
634
  brand_mapping = {}
 
640
 
641
  def clean_wine_name(name):
642
  """
643
+ Удаляет в конце строки отдельно стоящие буквы (однобуквенные слова), не входящие в состав других слов.
644
+ Например, "токай л" превратится в "токай".
645
  """
646
+ # Регулярное выражение ищет:
647
+ # \s+ один или несколько пробельных символов;
648
+ # \b граница слова;
649
+ # [A-Za-zА-ЯЁа-яё] ровно одна буква (латинская или кириллическая);
650
+ # \b граница слова;
651
+ # \s*$ любые пробелы до конца строки.
652
+ return re.sub(r'\s+\b[A-Za-zА-ЯЁа-яё]\b\s*$', '', name)
653
 
654
 
655
  def most_common_words(strings, top_n=None):
656
  """
657
+ Возвращает список наиболее часто повторяющихся слов из списка строк.
658
 
659
+ Параметры:
660
+ - strings: список строк
661
+ - top_n: количество наиболее часто встречающихся слов, которые необходимо вернуть.
662
+ Если None, возвращаются все слова, отсортированные по частоте.
663
 
664
+ Возвращает:
665
+ - Список кортежей (слово, частота)
666
  """
667
  all_words = []
668
  for s in tqdm(strings):
669
  s=str(s)
670
+ # Извлекаем слова, приводим их к нижнему регистру и удаляем пунктуацию
671
  words = re.findall(r'\w+', s.lower())
672
  all_words.extend(words)
673
 
 
681
  for i in other_brands:
682
  l=i.split('/')
683
  if len(l)>2:
684
+ replaced[l[0].replace('Шато','')]=i
685
  else:
686
+ if 'Шато' in i:
687
+ replaced[i.replace('Шато','')]=i
688
 
689
+ ob=[i.split('/')[0].replace('Шато','') for i in other_brands]
690
  rr60_ob=check_brands_in_strings_pqdm(ob, p_brands, threshold=th)
691
 
692
  result={}
 
704
  for n in tqdm(items[items['new_brand'].isna()]['name'].values):
705
 
706
  name, alcohol, volume_or_number, years, production_year, gb, color, sour=prcess_text(n)
707
+ #name, alcohol, volume_or_number, years, production_year, gb, color, sour=prcess_text('Вино Токай Фурминт п/сл. бел.0.75л')
708
  name=trim_name(name, types)
709
  name=trim_name(name, grape_varieties)
710
  name=trim_name(name, onther_words)
 
735
 
736
  def find_full_word(text, word_list):
737
  """
738
+ Ищет первое полное вхождение слова из word_list в строке text.
739
+ Возвращает найденное слово или None, если совпадение не найдено.
740
  """
741
  for word in word_list:
742
  pattern = r'\b' + re.escape(word) + r'\b'
 
780
 
781
  def merge_types(items, products):
782
  alco_types=[i.strip().lower() for i in products['type'].unique()]
783
+ alco_types.append('ликёр')
784
  result=[]
785
  for row in tqdm(items.iterrows()):
786
  try:
 
801
  result.append(None)
802
 
803
  items['new_type']=result
804
+ items['new_type']=items['new_type'].replace({'ликёр': 'ликер', None: 'unmatched'})
805
 
806
 
807
  def normalize_name(name):
808
  """
809
+ Нормализует строку: если обнаруживается русский язык, транслитерирует её в латиницу,
810
+ приводит к нижнему регистру.
811
  """
812
  try:
813
  if detect_language(name) == 'ru':
 
818
 
819
  def prepare_groups_with_ids(items_df):
820
  """
821
+ Предварительная группировка данных из items по (new_brand, type, volume, new_type_wine, sour)
822
+ с учетом нормализованного названия.
823
 
824
+ Добавляем столбец 'norm_name', чтобы нормализовать значение name один раз заранее.
825
 
826
+ :param items_df: DataFrame с колонками 'new_brand', 'type', 'name', 'id', 'volume', 'new_type_wine', 'sour'.
827
+ :return: Словарь {(new_brand, type, volume, new_type_wine, sour): [(id, name, norm_name, volume, new_type_wine, sour)]}.
828
  """
829
  items_df = items_df.copy()
830
  items_df['norm_name'] = items_df['name'].apply(normalize_name)
 
836
 
837
  def prepare_groups_by_alternative_keys(items_df):
838
  """
839
+ Группировка данных из items по (new_type_wine, new_type, volume, sour) с сохранением id, new_brand,
840
+ оригинального и нормализованного имени.
841
 
842
+ :param items_df: DataFrame с колонками 'new_brand', 'new_type_wine', 'new_type', 'volume', 'name', 'id', 'sour'.
843
+ :return: Словарь {(new_type_wine, new_type, volume, sour): [(id, new_brand, name, norm_name, volume, new_type_wine, sour)]}.
844
  """
845
  items_df = items_df.copy()
846
  items_df['norm_name'] = items_df['name'].apply(normalize_name)
 
853
 
854
  def new_find_matches_with_ids(products_df, items_groups, items_df, name_threshold=85):
855
  """
856
+ Поиск совпадений с сохранением id найденных итемов, используя заранее подготовленные
857
+ нормализованные группы.
858
 
859
+ Производится два прохода:
860
+ - Первый: поиск по группам (brand, type, volume, new_type_wine, sour);
861
+ - Второй: для продуктов без совпадения ищем по альтернативным группам (new_type_wine, new_type, volume, sour),
862
+ исключая итемы с исходным брендом.
863
 
864
+ Сравнение производится по столбцу norm_name, а для вывода используется оригинальное name.
865
 
866
+ :param products_df: DataFrame с колонками 'id', 'brand', 'type', 'name', 'volume', 'new_type_wine', 'sour', 'new_type'.
867
+ :param items_groups: Словарь, сформированный функцией prepare_groups_with_ids.
868
+ :param items_df: DataFrame итемов с колонками 'id', 'new_brand', 'new_type_wine', 'new_type', 'volume', 'name', 'sour'.
869
+ :param name_threshold: Порог сходства для fuzzy matching.
870
+ :return: DataFrame с добавленными столбцами 'matched_items' (список совпадений) и 'alternative' (альтернативные совпадения).
871
  """
872
  results = []
873
+ no_match_products = [] # Список для хранения продуктов без совпадения в исходной группе
874
 
875
+ # Первый проход: поиск по группам (brand, type, volume, new_type_wine, sour)
876
  for idx, product in tqdm(products_df.iterrows(), total=len(products_df)):
877
  product_brand = product['brand']
878
  product_type = product['type']
 
884
  key = (product_brand, product_type, product_volume, product_type_wine, product_sour)
885
  items_data = items_groups.get(key, [])
886
  if items_data:
887
+ # Распаковываем: id, оригинальное имя, нормализованное имя, volume, new_type_wine, sour
888
  items_ids, items_names, items_norm_names, items_volumes, item_type_wine, items_sour = zip(*items_data)
889
  else:
890
  items_ids, items_names, items_norm_names, items_volumes, item_type_wine, items_sour = ([], [], [], [], [], [])
 
911
  results.append({
912
  'product_id': product['id'],
913
  'matched_items': matched_items,
914
+ 'alternative': [] # Заполняется во втором проходе
915
  })
916
 
917
+ # Подготовка альтернативной группировки по (new_type_wine, new_type, volume, sour)
918
  groups_by_alternative_keys = prepare_groups_by_alternative_keys(items_df)
919
 
920
+ # Второй проход: для продуктов без совпадений ищем по альтернативным группам
921
  for idx, product in tqdm(no_match_products):
922
  product_brand = product['brand']
923
  product_type_wine = product['new_type_wine']
 
928
 
929
  alt_key = (product_type_wine, product_type, product_volume, product_sour)
930
  type_items = groups_by_alternative_keys.get(alt_key, [])
931
+ # Фильтруем, исключая итемы с исходным брендом
932
  filtered_items = [item for item in type_items if item[1] != product_brand]
933
  if filtered_items:
934
  alt_ids, alt_brands, alt_names, alt_norm_names, alt_volumes, alt_type_wine, alt_sour = zip(*filtered_items)
 
960
 
961
  def contains_full_word(word, text, case_sensitive=True):
962
  """
963
+ Проверяет, содержится ли слово word в строке text как отдельное слово.
964
+ Параметр case_sensitive задаёт, учитывать ли регистр.
965
  """
966
  flags = 0 if case_sensitive else re.IGNORECASE
967
  pattern = r'\b' + re.escape(word) + r'\b'
 
978
  for j in new_brands:
979
  if contains_full_word(i, j, case_sensitive=False):
980
  if i != j:
981
+ #if len(i)>1:#i != 'А' and i != "Я":
982
  res[j]=i
983
  return res
984