Spaces:
Running
Running
feat: enhance text cleaning utility to remove HTML tags and entities, update documentation, and requirements.
Browse files- .gitignore +3 -0
- BATCH_TESTING_README.md +1 -1
- HELP.md +1 -1
- README.md +1 -1
- interface.py +38 -20
- prompts.py +22 -16
- requirements.txt +1 -0
- 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
|
| 337 |
-
"""Load CSV file and validate it has a 'text' column."""
|
| 338 |
try:
|
| 339 |
if file is None:
|
| 340 |
return "Помилка: Файл не вибрано", None
|
| 341 |
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
try:
|
| 347 |
-
|
|
|
|
| 348 |
except Exception as e:
|
| 349 |
-
return f"Помилка читання
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 350 |
|
| 351 |
# Validate 'text' column exists
|
| 352 |
if 'text' not in df.columns:
|
| 353 |
-
return f"Помилка:
|
| 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 |
-
**
|
| 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=
|
| 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 |
-
Правова позиція — це НЕ переказ рішення. Це абстрактне правове правило у
|
| 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") — це
|
| 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 |
+
' ': ' ', '"': '"', '&': '&',
|
| 37 |
+
'<': '<', '>': '>', ''': "'"
|
| 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'))
|