DocUA commited on
Commit
3418cd7
·
1 Parent(s): 59a8fbf

feat: enhance text cleaning utility to remove HTML tags and entities, update documentation, and requirements.

Browse files
Files changed (8) hide show
  1. .gitignore +3 -0
  2. BATCH_TESTING_README.md +1 -1
  3. HELP.md +1 -1
  4. README.md +1 -1
  5. interface.py +38 -20
  6. prompts.py +22 -16
  7. requirements.txt +1 -0
  8. utils.py +17 -0
.gitignore CHANGED
@@ -52,3 +52,6 @@ logs/
52
 
53
  # Ігноруємо isolated проєкти (якщо вони є в репозиторії)
54
  isolated-lp-generation/
 
 
 
 
52
 
53
  # Ігноруємо isolated проєкти (якщо вони є в репозиторії)
54
  isolated-lp-generation/
55
+
56
+ # Ігноруємо тестові файли
57
+ data_test/
BATCH_TESTING_README.md CHANGED
@@ -43,7 +43,7 @@ id_lp,text
43
  4. **Завантажте CSV файл:**
44
  - Натисніть "📁 Завантажте CSV файл з тестовими даними"
45
  - Виберіть ваш CSV файл
46
- - Натисніть "📂 Завантажити CSV файл"
47
  - Перевірте попередній перегляд завантажених даних
48
 
49
  5. **Запустіть пакетне тестування:**
 
43
  4. **Завантажте CSV файл:**
44
  - Натисніть "📁 Завантажте CSV файл з тестовими даними"
45
  - Виберіть ваш CSV файл
46
+ - Натисніть "📂 Завантажити CSV/XLSX файл"
47
  - Перевірте попередній перегляд завантажених даних
48
 
49
  5. **Запустіть пакетне тестування:**
HELP.md CHANGED
@@ -268,7 +268,7 @@ id_lp,text
268
 
269
  1. Натисніть **"📁 Завантажте CSV файл з тестовими даними"**
270
  2. Виберіть ваш CSV файл
271
- 3. Натисніть **"📂 Завантажити CSV файл"**
272
  4. Перевірте попередній перегляд:
273
  - Кількість рядків
274
  - Список колонок
 
268
 
269
  1. Натисніть **"📁 Завантажте CSV файл з тестовими даними"**
270
  2. Виберіть ваш CSV файл
271
+ 3. Натисніть **"📂 Завантажити CSV/XLSX файл"**
272
  4. Перевірте попередній перегляд:
273
  - Кількість рядків
274
  - Список колонок
README.md CHANGED
@@ -192,7 +192,7 @@ python main.py
192
  - Оберіть провайдер AI та модель
193
  - Налаштуйте паузу між запитами (рекомендовано 1-2 сек)
194
  - Завантажте CSV файл з колонкою `text`
195
- - Натисніть "📂 Завантажити CSV файл" для перегляду
196
  - Запустіть тестування кнопкою "▶️ Запустити пакетне тестування"
197
  - Завантажте результати після завершення
198
 
 
192
  - Оберіть провайдер AI та модель
193
  - Налаштуйте паузу між запитами (рекомендовано 1-2 сек)
194
  - Завантажте CSV файл з колонкою `text`
195
+ - Натисніть "📂 Завантажити CSV/XLSX файл" для перегляду
196
  - Запустіть тестування кнопкою "▶️ Запустити пакетне тестування"
197
  - Завантажте результати після завершення
198
 
interface.py CHANGED
@@ -333,28 +333,47 @@ async def process_raw_text_search(text, url, file, method, state_lp_json):
333
 
334
 
335
  # Batch testing functions
336
- async def load_csv_file(file) -> Tuple[str, Optional[pd.DataFrame]]:
337
- """Load CSV file and validate it has a 'text' column."""
338
  try:
339
  if file is None:
340
  return "Помилка: Файл не вибрано", None
341
 
342
- # Try to read CSV with different encodings
343
- try:
344
- df = pd.read_csv(file.name, encoding='utf-8')
345
- except UnicodeDecodeError:
346
  try:
347
- df = pd.read_csv(file.name, encoding='cp1251')
 
348
  except Exception as e:
349
- return f"Помилка читання CSV: {str(e)}", None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
350
 
351
  # Validate 'text' column exists
352
  if 'text' not in df.columns:
353
- return f"Помилка: CSV файл повинен містити колонку 'text'. Знайдені колонки: {', '.join(df.columns)}", None
354
 
355
  # Show preview
356
  rows_count = len(df)
357
- preview_msg = f"✅ Файл завантажено успішно!\n\n**Кількість рядків:** {rows_count}\n\n**Колонки:** {', '.join(df.columns)}\n\n**Перші 3 рядки (текст):**\n"
358
  for idx, row in df.head(3).iterrows():
359
  text_preview = str(row['text'])[:100] + "..." if len(str(row['text'])) > 100 else str(row['text'])
360
  preview_msg += f"\n{idx + 1}. {text_preview}\n"
@@ -849,18 +868,17 @@ def create_gradio_interface() -> gr.Blocks:
849
 
850
  # Вкладка Пакетне тестування (Batch Testing)
851
  with gr.Tab("📊 Пакетне тестування", id=4):
852
- gr.Markdown("### Пакетна генерація правових позицій з CSV файлу", elem_classes=["tab-header"])
853
 
854
  gr.Markdown("""
855
  **Інструкція:**
856
  1. Виберіть провайдера AI та модель для генерації
857
- 2. Завантажте CSV файл, що містить колонку `text` з текстами судових рішень
858
  3. Запустіть пакетне тестування
859
- 4. Завантажте результати у форматі CSV
860
 
861
- **Формат CSV файлу:**
862
- - Обов'язково повинна бути колонка `text` з текстами судових рішень
863
- - Результати будуть збережені в новій колонці з назвою моделі
864
  """)
865
 
866
  with gr.Row():
@@ -887,8 +905,8 @@ def create_gradio_interface() -> gr.Blocks:
887
  )
888
 
889
  csv_file_input = gr.File(
890
- label="📁 Завантажте CSV файл з тестовими даними",
891
- file_types=[".csv"],
892
  type="filepath"
893
  )
894
 
@@ -901,7 +919,7 @@ def create_gradio_interface() -> gr.Blocks:
901
  batch_df_state = gr.State()
902
 
903
  load_csv_button = gr.Button(
904
- "📂 Завантажити CSV файл",
905
  variant="secondary",
906
  scale=1
907
  )
@@ -1097,7 +1115,7 @@ def create_gradio_interface() -> gr.Blocks:
1097
 
1098
  # Batch testing tab event handlers
1099
  load_csv_button.click(
1100
- fn=load_csv_file,
1101
  inputs=[csv_file_input],
1102
  outputs=[csv_preview_output, batch_df_state]
1103
  ).then(
 
333
 
334
 
335
  # Batch testing functions
336
+ async def load_data_file(file) -> Tuple[str, Optional[pd.DataFrame]]:
337
+ """Load CSV or Excel file and validate it has a 'text' column."""
338
  try:
339
  if file is None:
340
  return "Помилка: Файл не вибрано", None
341
 
342
+ file_path = Path(file.name)
343
+ file_ext = file_path.suffix.lower()
344
+
345
+ if file_ext in ['.xlsx', '.xls']:
346
  try:
347
+ # Read Excel
348
+ df = pd.read_excel(file.name)
349
  except Exception as e:
350
+ return f"Помилка читання Excel: {str(e)}", None
351
+ else:
352
+ # Try to read CSV with different encodings and automatic separator detection
353
+ encodings = ['utf-8-sig', 'utf-8', 'cp1251', 'latin1']
354
+ df = None
355
+ last_error = ""
356
+
357
+ for enc in encodings:
358
+ try:
359
+ # Use sep=None, engine='python' for automatic separator detection
360
+ # Use on_bad_lines='warn' to skip problematic lines if they occur
361
+ df = pd.read_csv(file.name, sep=None, engine='python', encoding=enc, on_bad_lines='warn')
362
+ break
363
+ except Exception as e:
364
+ last_error = str(e)
365
+ continue
366
+
367
+ if df is None:
368
+ return f"Помилка читання CSV: {last_error}", None
369
 
370
  # Validate 'text' column exists
371
  if 'text' not in df.columns:
372
+ return f"Помилка: Файл повинен містити колонку 'text'. Знайдені колонки: {', '.join(df.columns)}", None
373
 
374
  # Show preview
375
  rows_count = len(df)
376
+ preview_msg = f"✅ Файл {file_path.name} завантажено успішно!\n\n**Кількість рядків:** {rows_count}\n\n**Колонки:** {', '.join(df.columns)}\n\n**Перші 3 рядки (текст):**\n"
377
  for idx, row in df.head(3).iterrows():
378
  text_preview = str(row['text'])[:100] + "..." if len(str(row['text'])) > 100 else str(row['text'])
379
  preview_msg += f"\n{idx + 1}. {text_preview}\n"
 
868
 
869
  # Вкладка Пакетне тестування (Batch Testing)
870
  with gr.Tab("📊 Пакетне тестування", id=4):
871
+ gr.Markdown("### Пакетна генерація правових позицій з CSV/Excel файлу", elem_classes=["tab-header"])
872
 
873
  gr.Markdown("""
874
  **Інструкція:**
875
  1. Виберіть провайдера AI та модель для генерації
876
+ 2. Завантажте CSV або Excel (.xlsx, .xls) файл, що містить колонку `text` з текстами судових рішень
877
  3. Запустіть пакетне тестування
878
+ 4. Завантажте результати у форматі CSV (результати завжди зберігаються як CSV для сумісності)
879
 
880
+ **Вимоги до файлу:**
881
+ - Обов'язково повинна бути колонка `text` з текстами рішень
 
882
  """)
883
 
884
  with gr.Row():
 
905
  )
906
 
907
  csv_file_input = gr.File(
908
+ label="📁 Завантажте CSV або Excel файл з тестовими даними",
909
+ file_types=[".csv", ".xlsx", ".xls"],
910
  type="filepath"
911
  )
912
 
 
919
  batch_df_state = gr.State()
920
 
921
  load_csv_button = gr.Button(
922
+ "📂 Завантажити CSV/XLSX файл",
923
  variant="secondary",
924
  scale=1
925
  )
 
1115
 
1116
  # Batch testing tab event handlers
1117
  load_csv_button.click(
1118
+ fn=load_data_file,
1119
  inputs=[csv_file_input],
1120
  outputs=[csv_preview_output, batch_df_state]
1121
  ).then(
prompts.py CHANGED
@@ -6,9 +6,9 @@ SYSTEM_PROMPT = """<role>
6
  на формулюванні правових позицій на основі судових рішень для бази правових
7
  позицій Верховного Суду (lpd.court.gov.ua).
8
 
9
- Правова позиція — це НЕ переказ рішення. Це абстрактне правове правило у 2-4
10
- реченнях, яке може бути застосоване до аналогічних справ. Кожне речення
11
- формулює окрему правову тезу прямим декларативним стилем.
12
  </role>"""
13
 
14
  # Main prompt template
@@ -32,14 +32,17 @@ LEGAL_POSITION_PROMPT = """
32
  - Резолютивну частину (використовуй лише для визначення типу судочинства)
33
 
34
  Подумки визнач: (1) яке правове питання вирішував Верховний Суд,
35
- (2) який абстрактний правовий принцип він сформулював,
36
  (3) як це правило може бути застосоване до аналогічних справ.
37
  </strategy>
38
 
39
  <rules_do>
40
  <rule id="source_focus">
41
  Основа правової позиції — висновки Верховного Суду з мотивувальної частини рішення.
42
- Формулюй правило на базі того, що ВС вважає правильним застосуванням норм права.
 
 
 
43
  </rule>
44
 
45
  <rule id="declarative_style">
@@ -51,17 +54,18 @@ LEGAL_POSITION_PROMPT = """
51
  </rule>
52
 
53
  <rule id="abstraction">
54
- Формулюй правову позицію як ПРАВИЛО для аналогічних справ.
55
  Не згадуй конкретних осіб, назви підприємств, дати чи номери справ.
56
  Використовуй узагальнені терміни: "особа", "юридична особа", "директор",
57
- "позивач", "відповідач", "суб'єкт владних повноважень", "суд".
58
  </rule>
59
 
60
  <rule id="conciseness">
61
- Текст правової позиції (поле "text") — це 2-4 речення.
62
- Кожне речення формулює ОКРЕМУ правову тезу.
63
- Не об'єднуй кілька ідей в одне речення.
64
  Кожне слово повинно нести юридичний зміст.
 
 
65
  </rule>
66
 
67
  <rule id="language">
@@ -70,7 +74,7 @@ LEGAL_POSITION_PROMPT = """
70
  </rule>
71
 
72
  <rule id="proceeding_type">
73
- Тип судочинства — строго один із чотирьох варіантів:
74
  - "Адміністративне судочинство"
75
  - "Кримінальне судочинство"
76
  - "Цивільне судочинство"
@@ -78,8 +82,8 @@ LEGAL_POSITION_PROMPT = """
78
  </rule>
79
 
80
  <rule id="category">
81
- Категорія повинна бути конкретною і по можливості містити посилання на відповідні
82
- статті кодексів. Категорія описує правову тематику, а не просто тип судочинства.
83
  </rule>
84
  </rules_do>
85
 
@@ -91,7 +95,7 @@ LEGAL_POSITION_PROMPT = """
91
 
92
  <rule id="no_factual_retelling">
93
  НЕ переказуй фактичні обставини конкретної справи. Правова позиція — це
94
- абстрактне правило, а не опис того, що сталося.
95
  </rule>
96
 
97
  <rule id="no_verbose_patterns">
@@ -101,8 +105,10 @@ LEGAL_POSITION_PROMPT = """
101
  </rule>
102
 
103
  <rule id="no_law_text_copying">
104
- НЕ дублюй текст статей закону дослівно. Посилайся на статті, але формулюй
105
- правило своїми словами як висновок ВС.
 
 
106
  </rule>
107
  </rules_dont>
108
 
 
6
  на формулюванні правових позицій на основі судових рішень для бази правових
7
  позицій Верховного Суду (lpd.court.gov.ua).
8
 
9
+ Правова позиція — це НЕ переказ рішення. Це абстрактне правове правило у 1-2
10
+ реченнях, яке може бути застосоване до аналогічних справ. Правова позиція
11
+ формулює правову тезу прямим декларативним стилем.
12
  </role>"""
13
 
14
  # Main prompt template
 
32
  - Резолютивну частину (використовуй лише для визначення типу судочинства)
33
 
34
  Подумки визнач: (1) яке правове питання вирішував Верховний Суд,
35
+ (2) який правовий принцип він сформулював,
36
  (3) як це правило може бути застосоване до аналогічних справ.
37
  </strategy>
38
 
39
  <rules_do>
40
  <rule id="source_focus">
41
  Основа правової позиції — висновки Верховного Суду з мотивувальної частини рішення.
42
+ Формулюй правило на базі того, що Верховний Суд вважає правильним застосуванням норм права.
43
+ Виходь з того, що одна правова позиція - одне правило. Текст має бути очищено від зайвої процесуальної
44
+ логіки. Правова позиція не повинна містити більше однієї юридичної ідеї.
45
+ Якщо текст містить декілька правових висновків — залиш лише основний.
46
  </rule>
47
 
48
  <rule id="declarative_style">
 
54
  </rule>
55
 
56
  <rule id="abstraction">
57
+ Формулюй правову позицію як готову норму для застосування в інших аналогічних справах.
58
  Не згадуй конкретних осіб, назви підприємств, дати чи номери справ.
59
  Використовуй узагальнені терміни: "особа", "юридична особа", "директор",
60
+ "позивач", "відповідач", "суб'єкт владних повноважень", "суд", "апеляційний суд", "касаційний суд".
61
  </rule>
62
 
63
  <rule id="conciseness">
64
+ Текст правової позиції (поле "text") — це 1-2 речення.
65
+ Не об'єднуй кілька юридичних ідей в одну правову позицію.
 
66
  Кожне слово повинно нести юридичний зміст.
67
+ Правова позиція не повинна бути занадто пояснювальною (не має бути зайвих
68
+ деталей, лише юридичне правило).
69
  </rule>
70
 
71
  <rule id="language">
 
74
  </rule>
75
 
76
  <rule id="proceeding_type">
77
+ Тип судочинства — виключно один із чотирьох варіантів:
78
  - "Адміністративне судочинство"
79
  - "Кримінальне судочинство"
80
  - "Цивільне судочинство"
 
82
  </rule>
83
 
84
  <rule id="category">
85
+ Категорія повинна бути конкретною і по можливості містити посилання на відповідну
86
+ статтю кодексу. Категорія описує правову тематику, а не просто тип судочинства.
87
  </rule>
88
  </rules_do>
89
 
 
95
 
96
  <rule id="no_factual_retelling">
97
  НЕ переказуй фактичні обставини конкретної справи. Правова позиція — це
98
+ правило, а не опис того, що сталося.
99
  </rule>
100
 
101
  <rule id="no_verbose_patterns">
 
105
  </rule>
106
 
107
  <rule id="no_law_text_copying">
108
+ НЕ дублюй текст статей закону дослівно. Посилайся на статтю кодексу, але формулюй
109
+ правило своїми словами як висновок Верховного Суду. Не посилайся на одну й ту саму
110
+ статтю декілька разів в одній правовій позиції. При посиланні на інші нормативні документи
111
+ не вказуй їх номер та дату, але вказуй огран, який його видав.
112
  </rule>
113
  </rules_dont>
114
 
requirements.txt CHANGED
@@ -17,3 +17,4 @@ pyyaml
17
  pydantic>=2.0.0
18
  pydantic-settings
19
  huggingface-hub>=0.23.0
 
 
17
  pydantic>=2.0.0
18
  pydantic-settings
19
  huggingface-hub>=0.23.0
20
+ openpyxl
utils.py CHANGED
@@ -20,9 +20,26 @@ def clean_text(text: str) -> str:
20
  }
21
 
22
  try:
 
23
  text = unicodedata.normalize('NFKD', text)
 
24
  for old, new in replacements.items():
25
  text = text.replace(old, new)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  text = ' '.join(text.split())
27
  text = ''.join(char for char in text
28
  if not unicodedata.category(char).startswith('C'))
 
20
  }
21
 
22
  try:
23
+ # Normalize to NFKD and handle character replacements
24
  text = unicodedata.normalize('NFKD', text)
25
+ # Handle character replacements
26
  for old, new in replacements.items():
27
  text = text.replace(old, new)
28
+
29
+ # Remove HTML tags and entities
30
+ # Specifically targeting </p> <p> and other remnants
31
+ text = re.sub(r'</p>\s*<p>', ' ', text, flags=re.IGNORECASE)
32
+ text = re.sub(r'<[^>]+>', ' ', text)
33
+
34
+ # Handle common HTML entities
35
+ entities = {
36
+ '&nbsp;': ' ', '&quot;': '"', '&amp;': '&',
37
+ '&lt;': '<', '&gt;': '>', '&apos;': "'"
38
+ }
39
+ for ent, rep in entities.items():
40
+ text = text.replace(ent, rep)
41
+
42
+ # Remove control characters and normalize whitespace
43
  text = ' '.join(text.split())
44
  text = ''.join(char for char in text
45
  if not unicodedata.category(char).startswith('C'))