init
Browse files- dummy.py +134 -0
- input_jsonl/test.jsonl +10 -0
- input_parquet/test.parquet +3 -0
- main_jsonl.py +155 -0
- main_parquet.py +167 -0
- models/model.joblib +3 -0
- models/scaler.pkl +3 -0
- output/test.jsonl +10 -0
- output/test.parquet +3 -0
- requirements.txt +62 -0
- text_analyzer/__pycache__/analyzer.cpython-311.pyc +0 -0
- text_analyzer/__pycache__/analyzer.cpython-312.pyc +0 -0
- text_analyzer/__pycache__/constants.cpython-311.pyc +0 -0
- text_analyzer/__pycache__/constants.cpython-312.pyc +0 -0
- text_analyzer/__pycache__/utils.cpython-311.pyc +0 -0
- text_analyzer/__pycache__/utils.cpython-312.pyc +0 -0
- text_analyzer/analyzer.py +86 -0
- text_analyzer/constants.py +284 -0
- text_analyzer/features/__pycache__/base_features.cpython-311.pyc +0 -0
- text_analyzer/features/__pycache__/base_features.cpython-312.pyc +0 -0
- text_analyzer/features/__pycache__/linguistic_features.cpython-311.pyc +0 -0
- text_analyzer/features/__pycache__/linguistic_features.cpython-312.pyc +0 -0
- text_analyzer/features/__pycache__/regex_features.cpython-311.pyc +0 -0
- text_analyzer/features/__pycache__/regex_features.cpython-312.pyc +0 -0
- text_analyzer/features/__pycache__/spacy_features.cpython-311.pyc +0 -0
- text_analyzer/features/__pycache__/spacy_features.cpython-312.pyc +0 -0
- text_analyzer/features/__pycache__/structural_features.cpython-311.pyc +0 -0
- text_analyzer/features/__pycache__/structural_features.cpython-312.pyc +0 -0
- text_analyzer/features/base_features.py +243 -0
- text_analyzer/features/linguistic_features.py +122 -0
- text_analyzer/features/regex_features.py +51 -0
- text_analyzer/features/spacy_features.py +196 -0
- text_analyzer/features/structural_features.py +149 -0
- text_analyzer/utils.py +12 -0
dummy.py
ADDED
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Interaktywny skrypt do testowania klasyfikatora jakości tekstu.
|
3 |
+
|
4 |
+
Ten moduł dostarcza prosty interfejs wiersza poleceń (CLI) do analizy
|
5 |
+
pojedynczych tekstów. Po uruchomieniu, skrypt wczytuje te same modele
|
6 |
+
i komponenty, które są używane w procesie przetwarzania wsadowego
|
7 |
+
(TextAnalyzer, Scaler, Classifier).
|
8 |
+
|
9 |
+
Jest to narzędzie przeznaczone do szybkich testów, debugowania i demonstracji
|
10 |
+
działania modelu, zapewniając spójność wyników z przetwarzaniem masowym.
|
11 |
+
|
12 |
+
Użycie:
|
13 |
+
python interactive_classifier.py
|
14 |
+
"""
|
15 |
+
|
16 |
+
# --- Importy bibliotek ---
|
17 |
+
|
18 |
+
# Biblioteki standardowe
|
19 |
+
import os
|
20 |
+
import pickle
|
21 |
+
import joblib
|
22 |
+
import pandas as pd
|
23 |
+
from text_analyzer.analyzer import TextAnalyzer
|
24 |
+
from text_analyzer import constants
|
25 |
+
|
26 |
+
# --- Stałe globalne ---
|
27 |
+
|
28 |
+
MODELS_DIR = 'models'
|
29 |
+
|
30 |
+
# --- Ładowanie modeli ---
|
31 |
+
|
32 |
+
# Modele i analizator są ładowane globalnie tylko raz przy starcie skryptu.
|
33 |
+
# Dzięki temu unika się wielokrotnego, kosztownego odczytu plików z dysku
|
34 |
+
# w pętli interaktywnej, co zapewnia natychmiastową odpowiedź na zapytanie użytkownika.
|
35 |
+
print("Ładowanie modeli i analizatora...")
|
36 |
+
|
37 |
+
with open('models/scaler.pkl', 'rb') as f:
|
38 |
+
scaler = pickle.load(f)
|
39 |
+
classifier = joblib.load("models/model.joblib")
|
40 |
+
text_analyzer = TextAnalyzer()
|
41 |
+
|
42 |
+
|
43 |
+
# --- Definicje funkcji ---
|
44 |
+
|
45 |
+
def classify_single_text(text_to_classify: str) -> tuple[str | None, float | None]:
|
46 |
+
"""
|
47 |
+
Analizuje pojedynczy tekst i dokonuje predykcji jego jakości,
|
48 |
+
używając tego samego potoku co w przetwarzaniu wsadowym.
|
49 |
+
|
50 |
+
Potok inferencyjny:
|
51 |
+
1. Ekstrakcja cech lingwistycznych za pomocą TextAnalyzer.
|
52 |
+
2. Uporządkowanie cech zgodnie z `constants.COLUMN_ORDER`.
|
53 |
+
3. Skalowanie cech za pomocą wczytanego skalera.
|
54 |
+
4. Predykcja prawdopodobieństw klas za pomocą modelu.
|
55 |
+
5. Wybór najbardziej prawdopodobnej klasy i formatowanie wyniku.
|
56 |
+
|
57 |
+
Args:
|
58 |
+
text_to_classify (str): Ciąg znaków do analizy.
|
59 |
+
|
60 |
+
Returns:
|
61 |
+
tuple[str | None, float | None]: zawiera:
|
62 |
+
- Przewidywaną kategorię ('LOW', 'MEDIUM', 'HIGH') lub None w przypadku błędu.
|
63 |
+
- Pewność predykcji (w procentach, 0-100) lub None w przypadku błędu.
|
64 |
+
"""
|
65 |
+
# Krok walidacji wejścia
|
66 |
+
if not isinstance(text_to_classify, str) or not text_to_classify.strip():
|
67 |
+
print("Błąd: Wprowadzony tekst jest pusty lub nie jest typu string.")
|
68 |
+
return None, None
|
69 |
+
|
70 |
+
try:
|
71 |
+
# Krok 1: Ekstrakcja cech. Używamy `analyze_batch` z listą jednoelementową,
|
72 |
+
# aby zapewnić identyczne przetwarzanie jak w skrypcie masowym.
|
73 |
+
# `next()` pobiera pierwszy (i jedyny) wynik z generatora.
|
74 |
+
features_dict = next(text_analyzer.analyze_batch([text_to_classify]))
|
75 |
+
|
76 |
+
# Krok 2: Uporządkowanie cech. To kluczowe dla spójności z modelem.
|
77 |
+
ordered_features = [features_dict.get(fname, 0.0) for fname in constants.COLUMN_ORDER]
|
78 |
+
|
79 |
+
# Krok 3: Przygotowanie danych do predykcji (DataFrame z jedną próbką).
|
80 |
+
features_df = pd.DataFrame([ordered_features], columns=constants.COLUMN_ORDER)
|
81 |
+
input_features_scaled = scaler.transform(features_df)
|
82 |
+
|
83 |
+
# Krok 4: Wykonanie predykcji.
|
84 |
+
# Używamy `predict_proba` aby uzyskać pewność (confidence score).
|
85 |
+
y_pred_proba = classifier.predict_proba(input_features_scaled)
|
86 |
+
|
87 |
+
# Krok 5: Przetworzenie wyników predykcji.
|
88 |
+
# `y_pred_proba` ma kształt (1, 3), więc bierzemy pierwszy element `[0]`.
|
89 |
+
hardcoded_labels = ["LOW", "MEDIUM", "HIGH"]
|
90 |
+
category_probabilities = {
|
91 |
+
label: prob
|
92 |
+
for label, prob in zip(hardcoded_labels, y_pred_proba[0])
|
93 |
+
}
|
94 |
+
|
95 |
+
most_probable_category = max(category_probabilities, key=category_probabilities.get)
|
96 |
+
confidence = round(category_probabilities[most_probable_category] * 100, 2)
|
97 |
+
|
98 |
+
return most_probable_category, confidence
|
99 |
+
|
100 |
+
except Exception as e:
|
101 |
+
print(f"\nWystąpił nieoczekiwany błąd podczas przetwarzania tekstu.")
|
102 |
+
print(f"Szczegóły błędu: {e}")
|
103 |
+
return None, None
|
104 |
+
|
105 |
+
|
106 |
+
# --- Główny blok wykonawczy ---
|
107 |
+
|
108 |
+
if __name__ == '__main__':
|
109 |
+
# Wyświetlenie instrukcji dla użytkownika.
|
110 |
+
print("\n--- Interaktywny Klasyfikator Jakości Tekstu ---")
|
111 |
+
print("Wpisz tekst i naciśnij Enter, aby uzyskać klasyfikację.")
|
112 |
+
print("Wpisz 'quit' lub 'exit', aby zakończyć.")
|
113 |
+
|
114 |
+
# Główna pętla interaktywna (REPL: Read-Evaluate-Print Loop).
|
115 |
+
while True:
|
116 |
+
try:
|
117 |
+
# Read: Pobierz dane wejściowe od użytkownika.
|
118 |
+
user_input = input("\n> ")
|
119 |
+
|
120 |
+
# Sprawdź warunek wyjścia z pętli.
|
121 |
+
if user_input.lower() in ['quit', 'exit']:
|
122 |
+
print("Zamykanie programu...")
|
123 |
+
break
|
124 |
+
|
125 |
+
# Evaluate: Przetwórz dane wejściowe.
|
126 |
+
category, confidence = classify_single_text(user_input)
|
127 |
+
|
128 |
+
# Print: Wyświetl wynik, jeśli przetwarzanie zakończyło się sukcesem.
|
129 |
+
if category is not None:
|
130 |
+
print(f" └── Predykcja: {category} (Pewność: {confidence}%)")
|
131 |
+
|
132 |
+
except KeyboardInterrupt: # Obsługa Ctrl+C
|
133 |
+
print("\nPrzerwano przez użytkownika. Zamykanie programu...")
|
134 |
+
break
|
input_jsonl/test.jsonl
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{"text": "Pierwszy kamienny kościół w stylu romańskim powstał w tym miejscu już w XII wieku. Część romańskiej świątyni następnie rozebrano i zastąpiono budowlą gotycką. Obecnie najstarszą częścią budowli jest wieża pochodząca z pierwszej połowy XIII wieku. Prezbiterium i zakrystia pochodzą z końca XIII wieku, natomiast nawa została zbudowana na początku XIV wieku. Podejrzewa się, że planowano również rozebrać stosunkowo małą wieżę i zastąpić ją bardziej proporcjonalną, gotycką. Świątynia zachowała bardzo dużo ze swojego średniowiecznego charakteru. Renowację przeszła w 1937 roku"}
|
2 |
+
{"text": "FJM.B.ZP \n cykl kształcenia 2019-2024\nKARTA PRZEDMIOTU (SYLABUS) \nI. INFORMACJE OGÓLNE \n1.Nazwa przedmiotu\nZdrowie publiczne\n2. Kod przedmiotu\nFJM.B.ZP\n3. Karta przedmiotu ważna od roku akademickiego \n4.Status przedmiotuobowiązkowy\n5. Wydział\nWydział Nauk o Zdrowiu w Katowicach\n6. Kierunek\nFizjoterapia\n7. Profil kształcenia\nOgólnoakademicki\n8. Poziom kształcenia (studiów)\nJednolite magisterskie\n9. Forma prowadzenia studiów\nStacjonarne\n10. Semestr\nII\n11. Jednostka realizująca \nZakład Promocji Zdrowia i Pielęgniarstwa Środowiskowego\n12. Adres, telefon kontaktowy\nKatowice, <address>, tel: 32 20-88-300\n13. Strona internetowa\n14. Kierownik jednostkidr hab. n. med. Tomasz Irzyniec\n15. Osoba odpowiedzialna za prowadzenie przedmiotudr hab. n. med. Tomasz Irzyniec\n16. Nauczyciele akademiccy realizujący przedmiot\nDR N. MED. JADWIGA KAŹMIERCZAK,ZOFIA NOWAK-KAPUSTA, DR N. MED. KATARZYNA LESZCZYŃSKA, DR N. FARM. IZABELA MACIEJEWSKA-PASZEK, MGR IZABELA PAŁKA,MGR MAGDALENA SZOSTAK-TRYBUŚ, DR MAGDALENA SZOTOWSKA\n17. Przynależność do grupy przedmiotów\nB Nauki ogólne\n18. Język prowadzenia zajęć\nPolski\n19. Wymagania wstępne i dodatkowe w zakresie wiedzy, umiejętności i innych kompetencjiogólna wiedza z nauk społecznych, epidemiologicznych, biologii, higieny\n20. Cele przedmiotu \nSymbol\nCel przedmiotu \nC1\nDostarczenie wiedzy o założeniach i zadaniach Zdrowia Publicznego. \nC2\nWyrobienie umiejętności wskazywania na kulturowe, społeczne i ekonomiczne uwarunkowania zdrowia publicznego oraz zagrożenia zdrowotne współczesnego człowieka. \nC3\nWyposażenie absolwenta w umiejętności związane z profilaktyką chorób oraz promocją zdrowia w zakresie dysfunkcji i niepełnosprawności. Badanie przesiewowe w profilaktyce dysfunkcji i niepełnosprawności.\n C4\nDostarczenie wiedzy o założeniach , zadaniach i organizacji narodowego systemu opieki zdrowotnej w Polsce i na świecie, a także działaniach NFZ. \n C5\n Dostarczenie wiedzy o jakości w opiece zdrowotnej.\n II. OPIS EFEKTÓW UCZENIA SIĘ DLA PRZEDMIOTU I ODNIESIENIE DO CELÓW PRZEDMIOTU I EUs ZE STANDARDU\nNumer efektu uczenia się dla przedmiotu (symbol)\nStudent, który zaliczył przedmiotznarozumiepotrafijest gotów do:\nOdniesienie do celów przedmiotu\nOdniesienie do efektów uczenia się ze standardu\nEUs_W1\nczynniki decydujące o zdrowiu oraz o zagrożeniu zdrowia;\nC1\nB.W11.\nEUs_W2\nzasady edukacji zdrowotnej i promocji zdrowia oraz elementy polityki społecznej dotyczącej ochrony zdrowia;\nC2\nB.W12.\nEUs_U1\norganizować działania ukierunkowane na edukację zdrowotną, promocję zdrowia i profilaktykę niepełnosprawności;\nC1, C4\nB.U4.\nEUs_U2\nprzeprowadzić badanie przesiewowe w profilaktyce dysfunkcji i niepełnosprawności.\nC1, C3\nB.U5.\nIII. MACIERZ EFEKTÓW KSZTAŁCENIA DLA PRZEDMIOTU W ODNIESIENIU DO FORM ZAJĘĆ, STOSOWANYCH METOD DYDAKTYCZNYCH ORAZ SPOSOBÓW WERYFIKACJI\nNumer efektu uczenia się DLA PRZEDMIOTU (symbol)\nForma zajęć dydaktycznych\nMetody dydaktyczne\nSposób weryfikacji efektów kształcenia (forma zaliczeń)\nWykład\nZajęcia seminaryjne\nĆwiczenia\nZajęcia praktyczne\nPraktyka zawodowa\nBez nauczyciela\nEUs_W1\n2 \nWykład, prezentacja multimedialna\nZaliczenie na ocenę, test pytań zamkniętych, jednokrotnego wyboru\nEUs_W2\nWykład, prezentacja multimedialna\nZaliczenie na ocenę, test pytań zamkniętych, jednokrotnego wyboru\nEUs_U1\nPrezentacja multimedialnadyskusja\nZaliczenie na ocenę, test pytań zamkniętych, jednokrotnego wyboru\nEUs_U2\nprezentacja multimedialnadyskusja\nZaliczenie na ocenę, test pytań zamkniętych, jednokrotnego wyboru\nIV. TREŚCI PRZEDMIOTU I ODNIESIENIE DO EFEKTÓW UCZENIA SIĘ DLA PRZEDMIOTU\nSYMBOL\nWYKŁADY\nLICZBA GODZIN\nOdniesienie do efektów uczenia się dla przedmiotu\nWk1\n Kulturowe, społeczne i ekonomiczne uwarunkowania zdrowia publicznego. Charakterystyka istoty profilaktyki i prewencji chorób. Podstawy organizacji Narodowego Systemu Zdrowia w Polsce. Zasady funkcjonowania rynku usług medycznych w Polsce oraz wybranych krajach Unii Europejskiej\nEUs_W1\nEUs_W2\nWk2\nStruktura i funkcje jednostek opieki zdrowotnej. Definiowanie pojęć jakości w opiece zdrowotnej, kryteria opieki zdrowotnej. Metody i techniki oceny stanu zdrowia populacji\nEUs_W1\nEUs_W2\nSUMA GODZIN\nSYMBOL\nSEMINARIA\nLICZBA GODZIN\nOdniesienie do efek-tów uczenia się dla przedmiotu\nS1\nPrzedstawienia modeli poznawczych zachowań zdrowotnych\nEUs_U1\nEUs_U2\nS2\nWyjaśnienia podstawowych pojęć medycyny środowiskowej\nWyjaśnienia znaczenie stresu dla zdrowia. \nŹródła zanieczyszczenia powietrza, gleby i wód\nEUs_U1\nS3\nWyjaśnienia znaczenie stresu dla zdrowia. \nŹródła zanieczyszczenia powietrza, gleby i wód\nEUs_U2\nS4\nMiędzynarodowe programy ochrony ludności przed niekorzystnymi wpływami środowiska. Organizacja działań ukierunkowane na edukację zdrowotną, promocję zdrowia i profilaktykę niepełnosprawności.\nEUs_U1\nS5\nCele strategiczne i cele szczegółowe Narodowego Programu Zdrowia. Badania przesiewowe w profilaktyce dysfunkcji i niepełnosprawności.\nEUs_U1\nEUs_U2\nSUMA GODZIN\nSYMBOL\nĆWICZENIA\nLICZBA GODZIN\nOdniesienie do efek-tów uczenia się dla przedmiotu\nĆw1\nĆw2\nĆw3\nSUMA GODZIN\nSYMBOL\nSYMULACJA MEDYCZNA\nLICZBA GODZIN\nOdniesienie do efektów uczenia się dla przedmiotu\nSM1\nSM2\nSUMA GODZIN\nSYMBOL\nZAJĘCIA PRAKTYCZNE\nLICZBA GODZIN\nOdniesienie do efektów uczenia się dla przedmiotu\nZ1\nZ2\nSUMA GODZIN\nSYMBOL\nPARKTYKA ZAWODOWA\nLICZBA GODZIN\nOdniesienie do efektów uczenia się dla przedmiotu\nP1\nP2\nSUMA GODZIN\nV. METODY WALIDACJI KOŃCOWYCH EFEKTÓW UCZENIA SIĘ\nZaliczenia:\nZaliczenie na ocenę, test pytań zamkniętych, jednokrotnego wyboru\nEgzamin:\n Nie dotyczy\nPraktyka zawodowa:\n Nie dotyczy\nVI. ZALECANA LITERATURA \nPODSTAWOWA\n1. Jethon Z.: Medycyna zapobiegawcza i środowiskowa. PZWL, Warszawa 2000\n2. Kozierkiewicz A.: Zdrowie 21. Zdrowie dla wszystkich w XXI w. Kraków, Uniwersyteckie Wydanie Medyczne, Vesalius 2001\n3. Kulik B., Latalski M.: Zdrowie Publiczne. Czelej sp. z oo, Lublin 2002\nWojt 4. Wojtczak A. Zdrowie Publiczne , PZWL, Warszawa , 2008\n5. Wojtczak A: Zdrowie publiczne wyzwaniem dla systemów zdrowia XXI wieku. PZWL, Warszawa, 2009.\nUZUPEŁNIAJĄCA\n1. Leowski J: Polityka zdrowotna a zdrowie publiczne. CEDEWU. Warszawa, 2008. \nVII. INNE PRZYDATNE INFORMACJE O PRZEDMIOCIE\nMateriały do zajęć\nX\nMiejsca odbywania się zajęć\nX \nMiejsca i godziny konsultacji\n X \nVIII. NAKŁAD PRACY STUDENTA (BILANS PUNKTÓW ECTS)\nRodzaj zajęć\nLiczba godzin\nPunkty ECTS\nWykłady\nSeminaria \nSymulacja medyczna\nĆwiczenia\nBezkontaktowe\nZajęcia praktyczne\nPraktyki zawodowe\nSUMA\nIX. KRYTERIA OCENIANIA\nEfekt uczenia się dla przedmiotu\nNa ocenę niedostateczną (2,0)\nNa ocenę dostateczną (3,0)\nNa ocenę dość dobrą (3,5)\nNa ocenę dobrą (4,0)\nNa ocenę ponad dobrą (4,5)\nNa ocenę bardzo dobrą (5,0)\nEUs_W1\nNie opanował materiału o czynnikach decydujących o zdrowiu oraz o zagrożeniu zdrowia\nOpanował materiał w minimalnym zakresię o czynnikach decydujących o zdrowiu oraz o zagrożeniu zdrowia\nOpanował materiał w niewielkim zakresie o czynnikach decydujących o zdrowiu oraz o zagrożeniu zdrowia\nOpanował materiał w podstawowym zakresie o czynnikach decydujących o zdrowiu oraz o zagrożeniu zdrowia\nOpanował materiał w niemal całkowitym zakresie o czynnikach decydujących o zdrowiu oraz o zagrożeniu zdrowia\nOpanował materiał w całkowitym zakresie o czynnikach decydujących o zdrowiu oraz o zagrożeniu zdrowia\nEUs_W2\nNie opanował materiału nt. zasad edukacji zdrowotnej i promocji zdrowia\nOpanował materiał w minimalnym zakresie nt. zasad edukacji zdrowotnej i promocji zdrowia\nOpanował materiał w niewielkim zakresie nt. zasad edukacji zdrowotnej i promocji zdrowia\nOpanował materiał w podstawowym zakresie nt. zasad edukacji zdrowotnej i promocji zdrowia\nOpanował materiał w niemal całkowitym zakresie nt. zasad edukacji zdrowotnej i promocji zdrowia\nOpanował materiał w całkowitym zakresie nt. zasad edukacji zdrowotnej i promocji zdrowia\nEUs_U1\nNie opanował materiału o organizowaniu działań ukierunkowanych na edukację zdrowotną, promocję zdrowia i profilaktykę niepełnosprawności\nOpanował materiał w minimalnym zakresie o organizowaniu działań ukierunkowanych na edukację zdrowotną, promocję zdrowia i profilaktykę niepełnosprawności\nOpanował materiał w niewielkim zakresie o organizowaniu działań ukierunkowanych na edukację zdrowotną, promocję zdrowia i profilaktykę niepełnosprawności\nOpanował materiał w podstawowym zakresie o organizowaniu działań ukierunkowanych na edukację zdrowotną, promocję zdrowia i profilaktykę niepełnosprawności\nOpanował materiał w niemal całkowitym zakresie o organizowaniu działań ukierunkowanych na edukację zdrowotną, promocję zdrowia i profilaktykę niepełnosprawności\nOpanował materiał w całkowitym zakresie o organizowaniu działań ukierunkowanych na edukację zdrowotną, promocję zdrowia i profilaktykę niepełnosprawności\nEUs_U2\nNie opanował materiału nt. przeprowadzenia badań przesiewowych w profilaktyce dysfunkcji i niepełnosprawności\nOpanował materiał w minimalnym zakresie nt. przeprowadzenia badań przesiewowych w profilaktyce dysfunkcji i niepełnosprawności\nOpanował materiał w niewielkim zakresie nt. przeprowadzenia badań przesiewowych w profilaktyce dysfunkcji i niepełnosprawności\nOpanował materiał w podstawowym zakresie nt. przeprowadzenia badań przesiewowych w profilaktyce dysfunkcji i niepełnosprawności\nOpanował materiał w niemal całkowitym zakresie nt. przeprowadzenia badań przesiewowych w profilaktyce dysfunkcji i niepełnosprawności\nOpanował materiał w całkowitym zakresie nt. przeprowadzenia badań przesiewowych w profilaktyce dysfunkcji i niepełnosprawności\n Zatwierdzono:\n ...\n (data i podpis Kierownika Katedry lub Zakładu"}
|
3 |
+
{"text": "Sztuka romańska (styl romański, romanizm, romańszczyzna) – styl w sztukach plastycznych XI–XIII wieku, ukształtowany w Europie zachodniej (na zachód od Renu), na terenach zajmowanych obecnie przez dzisiejsze północne Włochy, Francję i zachodnie Niemcy. Wkrótce zasięgiem nowego stylu objęte zostały kolejne tereny Europy i wraz z prowadzonymi wyprawami krzyżowymi przeniknął na Bliski Wschód. Czas trwania sztuki romańskiej był niejednolity; występowały także różnice w stylu między poszczególnymi regionami. Sztuka ta wyrosła na bazie antyku oraz doświadczeń sztuki karolińskiej i ottońskiej, także bizantyńskiej. Romanizm związany był z działalnością Kościoła katolickiego i stąd obecny był w głównej mierze w sztuce sakralnej."}
|
4 |
+
{"text": "Przypisy\n Jerzy Z. Łoziński: Pomniki sztuki w Polsce. T. 1: Małopolska. Warszawa: Wydawnictwo „Arkady”, 1985, s. 401–402. ISBN 83-213-3005-3.\nLinki zewnętrzne\nTransRomanica – The Romanesque Routes of European Heritage\nKlasztor Cystersów w Sulejowie – Podklasztorzu\nEwangeliarz Anastazji na stronie Polona.pl\nSakramentarz tyniecki na stronie Polona.pl\nKontrola autorytatywna (ruch artystyczny):\nLCCN: sh85007873GND: 4050482-7NDL: 00569670BnF: 11930977jBNCF: 4264NKC: ph125218BNE: XX525353J9U: 987007295497105171\nEncyklopedie internetowe:\nBritannica: art/Romanesque-art\nKategoria: Sztuka romańska"}
|
5 |
+
{"text": "Na temat historii sakramentarza w wiekach XII–XIX jest niewiele informacji. Prawdopodobnie przynajmniej do XIII w. był używany jako księga liturgiczna, później traktowano go jako cenny zabytek. Wiadomo, że w 1656, w czasie wojen szwedzkich, księga została zrabowana z Tyńca przez Szwedów, a następnie odkupiona w Krakowie, skąd powróciła do klasztoru. Informuje o tym notka na pierwszej karcie: Anno domini 1656 die 11 Augusti tę xięgę w klasztorze tynieckim wyrabowano gdy obóz Szwedzi rozproszyli Polaków tamże pod tymże klasztorem y tę xiążkę odkupiono w Krakowie. Obecna oprawa rękopisu pochodzi z XVII wieku[4]. W opracowaniach przyjmowano, że księga mogła mieć wcześniej drogocenną oprawę z kości słoniowej, która została odłączona po zrabowaniu przez Szwedów[6]. Możliwe jednak, że zniszczona oprawa została wymieniona jeszcze przed zrabowaniem rękopisu."}
|
6 |
+
{"text": "Przednia okładka\nPrzednia okładka\n \nMiniatura Maiestas Domini\nMiniatura Maiestas Domini\n \nMiniatura Ukrzyżowanie\nMiniatura Ukrzyżowanie\n \nInicjał całostronnicowy V\nInicjał całostronnicowy V\n \nInicjał całostronnicowy D\nInicjał całostronnicowy D\n \nOzdobna strona spisana na purpurze\nOzdobna strona spisana na purpurze\n \nOzdobna strona spisana na purpurze\nOzdobna strona spisana na purpurze\n \nPierwsza strona tekstu\nPierwsza strona tekstu\n \nStrona z inicjałem C\nStrona z inicjałem C\n \nStrona z dopisanymi glosami i neumami\nStrona z dopisanymi glosami i neumami"}
|
7 |
+
{"text": "Uchwała Nr 19\nZarządu Związku Rzemiosła Polskiego\nz dnia 5 grudnia 2017 r.\nznak: NO-I-130/Standardy/17\nw sprawie: ustalenia standardów wymagań egzaminacyjnych na egzamin czeladniczy przeprowadzany\nprzez komisje egzaminacyjne izb rzemieślniczych.\nNa podstawie art.3, ust.3a ustawy o rzemiośle z dnia 22 marca 1989 r. (Dz. U. 2016, poz. 1285) oraz §2\nust.2, pkt.7 Statutu Związku Rzemiosła Polskiego - Zarząd Związku Rzemiosła Polskiego uchwala co\nnastępuje:\n§1\n1. Ustala się standard wymagań egzaminacyjnych w zawodzie odpowiadającym danemu rodzajowi\nrzemiosła, występującym w klasyfikacji zawodów i specjalności rynku pracy oraz klasyfikacji zawodów\nszkolnictwa zawodowego uwzględniając wymagania określone w podstawie programowej\nkształcenia w zawodach, dla n/w zawodu:\nNr\nstandardu\nKod z wykazu zawodów, wg\nklasyfikacji zawodów i\nspecjalności dla potrzeb\nrynku pracy\nNazwa zawodu\n6/cz 721104 MODELARZ ODLEWNICZY\n§2\nStandard, o którym mowa w § 1, uwzględniony jest w ramach przygotowywania zadań egzaminacyjnych\ndo etapu praktycznego i do części pisemnej oraz części ustnej etapu teoretycznego egzaminu\nczeladniczego, o którym mowa w § 12 rozporządzenia Ministra Edukacji Narodowej z dnia 10 stycznia\n2017 r. w sprawie egzaminu czeladniczego, egzaminu mistrzowskiego oraz egzaminu sprawdzającego\nprzeprowadzanych przez komisje egzaminacyjne izb rzemieślniczych (Dz.U. z 2017 r., poz. 89)\n§3\nUchwała wchodzi w życie z dniem podjęcia.\n/-/ /-/\n ZASTĘPCA PREZESA PREZES Zarządu ZRP\n "}
|
8 |
+
{"text": "Alternatywy 4 to jeden z najważniejszych i najbardziej kultowych polskich seriali komediowych, który stał się nie tylko rozrywką, ale i wyjątkowym zwierciadłem epoki schyłkowego PRL-u. Wyreżyserowany przez Stanisława Bareję według scenariusza Janusza Płońskiego i Macieja Rybińskiego (wzbogaconego o autorskie pomysły Barei), serial powstał na początku lat 80., a jego emisja była przez kilka lat blokowana przez cenzurę ze względu na ostrą, satyryczną krytykę rzeczywistości socjalistycznej.\n\nFabuła i bohaterowie\nAkcja serialu rozgrywa się w nowo wybudowanym bloku na warszawskim Ursynowie przy fikcyjnej ulicy Alternatywy 4, gdzie wprowadzają się lokatorzy pochodzący z różnych grup społecznych: robotnik, lekarz, nauczycielka, karierowicz, emerytka, naukowiec, artystka, przedstawiciel prywatnej inicjatywy, a nawet stypendysta z Harvardu. Ich codzienne życie staje się areną walki z absurdami PRL-owskiej biurokracji, niedoróbkami budowlanymi, reglamentacją towarów i wszechobecną kontrolą. Centralną postacią jest gospodarz domu Stanisław Anioł (Roman Wilhelmi) - drobny karierowicz, marzący o władzy i porządku, który swoimi działaniami doprowadza mieszkańców do granic wytrzymałości.\n\nW obsadzie znaleźli się wybitni polscy aktorzy: Bożena Dykiel (Mieczysława Anioł), Witold Pyrkosz (Józef Balcerek), Zofia Czerwińska (Zofia Balcerkowa), Jerzy Turek (Tadeusz Kubiak), Wojciech Pokora (docent Furman), Stanisława Celińska (Bożena Lewicka), Mieczysław Voit (profesor Rozwadowski), Jerzy Bończak (Krzysztof Manc), Jerzy Kryszak (lekarz Kołek), Kazimierz Kaczor (Zygmunt Kotek), Janusz Gajos (Jan Winnicki) i wielu innych.\n\nTematyka i przesłanie\nSerial jest mistrzowską satyrą na realia późnego PRL-u. Bareja i jego współscenarzyści stworzyli spójny ekranowy mikrokosmos, będący parafrazą państwa policyjnego z całą machiną nacisku i obywatelami, których jedynym celem wydaje się przechytrzenie wszechwładnego gospodarza. Każdy odcinek to ironiczny komentarz do absurdów codzienności: walki o towar, awarii budowlanych, biurokracji, kolejek, czy prób narzucania ideologii. Serial nie stroni od aluzji do wydarzeń historycznych i politycznych - pojawiają się odniesienia do emigracji po Marcu 1968, strajków robotniczych, a nawet zbrodni katyńskiej, przemycane w dialogach i retrospekcjach bohaterów.\n\nWażnym motywem jest również siła wspólnoty - mieszkańcy, mimo różnic, uczą się współpracować i przeciwstawiać się opresji, co w finale prowadzi do spektakularnej „zemsty” na Aniele podczas wizyty zagranicznej delegacji, gdy blok zostaje celowo przemieniony w ruderę, a Anioł kompromituje się przed władzami.\n\nProdukcja i recepcja\nProdukcja serialu rozpoczęła się w 1981 roku, jednak ze względu na wprowadzenie stanu wojennego i bojkot telewizji przez aktorów, realizacja była kilkakrotnie przerywana i wznowiona dopiero w 1982 roku. Po ukończeniu w 1983 roku emisja została zablokowana przez cenzurę na ponad trzy lata. Serial krążył w nielegalnym obiegu na kasetach wideo, a do oficjalnej telewizji trafił dopiero w 1986 roku, już po usunięciu niektórych najbardziej krytycznych scen. Dopiero w 2014 roku Telewizja Polska opublikowała zrekonstruowaną wersję, przywracającą ocenzurowane fragmenty.\n\nPoczątkowo przyjęcie przez krytyków było chłodne - zarzucano brak spójności i chaotyczny montaż, jednak widzowie szybko docenili serial za inteligentny humor, celne obserwacje i galerię barwnych postaci. „Alternatywy 4” zyskały status kultowego dzieła, a cytaty, sceny i postacie na stałe weszły do polskiej popkultury.\n\nDziedzictwo i znaczenie\nDziś „Alternatywy 4” są nie tylko rozrywką, ale także cenną lekcją historii i socjologicznym portretem schyłkowego PRL-u. Serial regularnie powraca na antenę, doczekał się cyfrowej rekonstrukcji, a jego bohaterowie i motywy są przedmiotem analiz, wspomnień i licznych odniesień w kulturze współczesnej. Stanowi wzorzec polskiej satyry, będąc jednocześnie świadectwem epoki, w której powstał - epoki absurdu, sprytu i walki o godność w codziennym życiu.\n"}
|
9 |
+
{"text": "Akslop, może to jakieś duńskie miasto\njestem tu przejazdem, co prawda na\nnieco dłużej, bo ministrowie rolnictwa\nusiedli na bańkach z mlekiem i zatarasowali\nwszystkie szosy, zdążono mnie trochę rozwałkować\nlokalnymi osobliwościami, jak Diwron\nczy Cziweżór. kochałem tutejsze dziewczyny,\npolicja parę razy pogoniła mnie po\nchodnikach, mieszkańcy są bardzo serdeczni,\nnamawiają, żebym został na dłużej, obiecuję\nwam, gdziekolwiek się znajdę, zawsze pamiętać będę\nAkslop."}
|
10 |
+
{"text": "Bielik - orzeł, czy nie orzeł?\nBielik, birkut, orzeł bielik, orzeł białogłów, orzeł białogon. Z pewnością znalazłoby się jeszcze kilka nazw określających tego potężnego drapieżnika, którego sylwetka prawdopodobnie od wieków zdobi godło naszego państwa. Prawdopodobnie, bo nierozwiązany spór o to trwa od wielu lat. Przedmiotem sporu są oczywiście dwa największe ptaki drapieżne występujące w naszym kraju - tytułowy bielik oraz minimalnie mniejszy orzeł przedni.\n\nGdyby wziąć pod uwagę fakt, że ten pierwszy z wiekiem stają się coraz jaśniejszy, niemal siwy z czysto białym ogonem, to jest mu już całkiem blisko do orła z naszego godła. Kolejnym argumentem przemawiającym za bielikiem są nieopierzone skoki widoczne na symbolu narodowym. Orły z rodzaju Aquila, do których należy orzeł przedni, mają skoki pokryte piórami do samej nasady palców.\n\nWedług naukowców bielik jednak nie jest orłem, ale należy do rodzaju Haliaeetus, czyli orłanów. Genetycznie więc bliżej mu do kani rudej, niż do rodzaju Aquila (orłów właściwych), którego przedstawicielem jest wspomniany już orzeł przedni. Nie zmienia to faktu, że w wielu językach, wliczając w to również nazwę naukową, bielik jest nazywany orłem. Przykładami mogą być niemiecki Seeadler (orzeł morski), angielski White-tailed eagle (orzeł z białym ogonem), czy wreszcie Haliaeetus albicilla, gdzie pierwszy człon jest połączeniem dwóch greckich słów hals – morze i aetos – orzeł. Określenie albi-cilla oznacza biały ogon.\n\nSporo zamieszania w nazewnictwo wprowadziło również dodanie bielikowi przedrostka orzeł w latach 70., kiedy gatunkowi zagrażało wyginięcie. Bezlitośnie był wówczas tępiony jako szkodnik, rzekomo siejący spustoszenie w hodowlach ryb. Nazwanie go orłem miało podnieść jego prestiż i wzbudzić większy szacunek wśród potencjalnych prześladowców. Faktycznie, bielik żywi się rybami. Zwłaszcza tymi łatwo dostępnymi - słabszymi, chorymi, martwymi, czy przebywającymi tuż pod powierzchnią wody. Ptaki te nauczyły się również doskonale korzystać z resztek wyrzucanych za burtę przez statki rybackie. Często można obserwować bieliki podążające za kutrami i cierpliwie wyczekujące na wyrzuconą z jego pokładu rybę, którą błyskawicznie podejmują z powierzchni wody.\n\nCiekawostką jest, że uprawiają również kleptopasożytnictwo, czyli podbierają upolowaną zdobycz wydrom, kormoranom, mewom, czy rybołowom. Ryby nie są jednak jedynym składnikiem diety bielików. Równie chętnie polują one na ptactwo wodno-błotne, zwłaszcza łyski i krzyżówki. W tym przypadku wyspecjalizowały się w polowaniu w tandemie, polegającym na tym, że jeden z osobników przelatując nad szuwarami, stara się wypłoszyć z nich kaczki lub łyski, a w tym samym czasie drugi przypuszcza atak na ofiarę, która nieostrożnie pojawiła się na otwartej wodzie.\n\nBardzo ważnym składnikiem diety bielików jest również padlina, która w okresie zimowym, zwłaszcza u osobników młodych stanowi znaczący procent pobieranego pokarmu. Drapieżniki te słysząc stado kruków, które już wcześniej namierzyły resztki, po ich głosie szybko lokalizują stołówkę. Jeśli jest ona atrakcyjna, może się na niej zgromadzić nawet kilkanaście bielików.\n\nPo kryzysie z lat 70. sytuacja gatunku w naszym kraju zdecydowanie się poprawiła i aktualnie bielik nie jest ptakiem, którego spotkanie graniczy z cudem. Pamiętać jednak należy, że nadal podlega statusowi ochrony ścisłej, a wokół jego gniazd wyznacza się strefy ochronne. Umieszczony jest również w Polskiej Czerwonej Księdze Zwierząt i Załączniku Dyrektywy Ptasiej."}
|
input_parquet/test.parquet
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:afcf64008a9d28a6f5dc04c5094b79bfdaf398d16ae7499261c6d93812b86f49
|
3 |
+
size 21796
|
main_jsonl.py
ADDED
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: utf-8 -*-
|
2 |
+
"""
|
3 |
+
Skrypt do masowego przetwarzania plików JSONL w celu klasyfikacji jakości tekstu.
|
4 |
+
|
5 |
+
Ten moduł jest przeznaczony do wydajnej analizy dużych zbiorów danych.
|
6 |
+
Skanuje folder wejściowy w poszukiwaniu plików .jsonl, przetwarza każdy z nich
|
7 |
+
równolegle z użyciem wielu procesów (`multiprocessing`), a następnie zapisuje
|
8 |
+
wyniki do nowego pliku w folderze wyjściowym, zachowując oryginalną strukturę
|
9 |
+
danych i dodając wyniki klasyfikacji.
|
10 |
+
"""
|
11 |
+
|
12 |
+
# --- Importy bibliotek ---
|
13 |
+
import os
|
14 |
+
import glob
|
15 |
+
import time
|
16 |
+
import pickle
|
17 |
+
import joblib
|
18 |
+
import pandas as pd
|
19 |
+
import json
|
20 |
+
import numpy as np
|
21 |
+
from tqdm import tqdm
|
22 |
+
from typing import List
|
23 |
+
|
24 |
+
from text_analyzer.analyzer import TextAnalyzer
|
25 |
+
from text_analyzer import constants # Potrzebujemy COLUMN_ORDER
|
26 |
+
|
27 |
+
# --- Ładowanie modeli i konfiguracja ---
|
28 |
+
|
29 |
+
# Modele są ładowane na poziomie globalnym. W systemach uniksowych (Linux/macOS)
|
30 |
+
# dzięki mechanizmowi 'fork', procesy-dzieci dziedziczą te obiekty bez ponownego
|
31 |
+
# odczytu z dysku. Na Windowsie ('spawn'), każdy proces-dziecko musi zaimportować
|
32 |
+
# skrypt i załadować modele od nowa.
|
33 |
+
|
34 |
+
with open('models/scaler.pkl', 'rb') as f:
|
35 |
+
scaler = pickle.load(f)
|
36 |
+
classifier = joblib.load("models/model.joblib")
|
37 |
+
text_analyzer = TextAnalyzer()
|
38 |
+
|
39 |
+
# Liczba procesów roboczych do wykorzystania podczas analizy tekstów wewnątrz jednego pliku.
|
40 |
+
NUM_PROCESSES = 10
|
41 |
+
|
42 |
+
class NumpyJSONEncoder(json.JSONEncoder):
|
43 |
+
"""
|
44 |
+
Specjalny enkoder JSON do obsługi typów danych z NumPy,
|
45 |
+
które nie są domyślnie serializowalne.
|
46 |
+
"""
|
47 |
+
def default(self, obj):
|
48 |
+
if isinstance(obj, np.integer):
|
49 |
+
return int(obj)
|
50 |
+
if isinstance(obj, np.floating):
|
51 |
+
return float(obj)
|
52 |
+
if isinstance(obj, np.ndarray):
|
53 |
+
return obj.tolist()
|
54 |
+
return super(NumpyJSONEncoder, self).default(obj)
|
55 |
+
|
56 |
+
# --- Definicje funkcji ---
|
57 |
+
|
58 |
+
def predict_batch(texts: List[str], analyzer: TextAnalyzer, scaler_model, classifier_model) -> List[tuple[str | None, float | None]]:
|
59 |
+
"""
|
60 |
+
Przetwarza całą listę tekstów wsadowo i zwraca listę predykcji.
|
61 |
+
"""
|
62 |
+
all_features = []
|
63 |
+
|
64 |
+
# Krok 1: Ekstrakcja cech dla wszystkich tekstów za jednym zamachem
|
65 |
+
# Używamy tqdm, aby śledzić postęp analizy wsadowej
|
66 |
+
feature_generator = analyzer.analyze_batch(texts, batch_size=NUM_PROCESSES) # Dostosuj batch_size
|
67 |
+
for features_dict in tqdm(feature_generator, total=len(texts), desc="Analiza cech"):
|
68 |
+
ordered_features = [features_dict.get(fname, 0.0) for fname in constants.COLUMN_ORDER]
|
69 |
+
all_features.append(ordered_features)
|
70 |
+
|
71 |
+
if not all_features:
|
72 |
+
return []
|
73 |
+
|
74 |
+
# Krok 2: Przygotowanie i skalowanie wszystkich wektorów naraz
|
75 |
+
features_df = pd.DataFrame(all_features, columns=constants.COLUMN_ORDER)
|
76 |
+
features_scaled = scaler_model.transform(features_df)
|
77 |
+
|
78 |
+
# Krok 3: Predykcja dla całej paczki
|
79 |
+
pred_probas = classifier_model.predict_proba(features_scaled)
|
80 |
+
|
81 |
+
# Krok 4: Przetworzenie wyników
|
82 |
+
results = []
|
83 |
+
hardcoded_labels = ["LOW", "MEDIUM", "HIGH"]
|
84 |
+
for single_pred_proba in pred_probas:
|
85 |
+
category_prob = {
|
86 |
+
label: prob
|
87 |
+
for label, prob in zip(hardcoded_labels, single_pred_proba)
|
88 |
+
}
|
89 |
+
# Sortujemy, aby znaleźć kategorię z najwyższym prawdopodobieństwem
|
90 |
+
sorted_category_prob = sorted(category_prob.items(), key=lambda item: item[1], reverse=True)
|
91 |
+
|
92 |
+
# Pobieramy nazwę i wartość
|
93 |
+
most_probable_category, confidence = sorted_category_prob[0]
|
94 |
+
|
95 |
+
results.append((most_probable_category, round(float(confidence) * 100, 2)))
|
96 |
+
|
97 |
+
return results
|
98 |
+
|
99 |
+
def process_jsonl_file(input_file: str, output_file: str):
|
100 |
+
"""Orkiestruje proces przetwarzania pojedynczego pliku .jsonl wsadowo."""
|
101 |
+
original_data = []
|
102 |
+
texts_to_process = []
|
103 |
+
try:
|
104 |
+
with open(input_file, 'r', encoding='utf-8') as f:
|
105 |
+
for line in f:
|
106 |
+
json_object = json.loads(line)
|
107 |
+
original_data.append(json_object)
|
108 |
+
texts_to_process.append(json_object.get('text', ''))
|
109 |
+
except Exception as e:
|
110 |
+
print(f"Nie udało się wczytać pliku {input_file}. Błąd: {e}")
|
111 |
+
return
|
112 |
+
|
113 |
+
print(f"Wczytano {len(texts_to_process)} wierszy. Rozpoczynam przetwarzanie wsadowe...")
|
114 |
+
|
115 |
+
# Wywołujemy funkcję wsadową
|
116 |
+
results = predict_batch(texts_to_process, text_analyzer, scaler, classifier)
|
117 |
+
|
118 |
+
# Zapisywanie wyników
|
119 |
+
try:
|
120 |
+
with open(output_file, 'w', encoding='utf-8') as f:
|
121 |
+
for i, (category, confidence) in enumerate(results):
|
122 |
+
output_object = original_data[i]
|
123 |
+
output_object['quality_ai'] = category
|
124 |
+
output_object['confidence'] = confidence
|
125 |
+
json_line = json.dumps(output_object, ensure_ascii=False, cls=NumpyJSONEncoder)
|
126 |
+
f.write(json_line + '\n')
|
127 |
+
except Exception as e:
|
128 |
+
print(f"Nie udało się zapisać pliku {output_file}. Błąd: {e}")
|
129 |
+
|
130 |
+
# --- Główny blok wykonawczy ---
|
131 |
+
|
132 |
+
if __name__ == '__main__':
|
133 |
+
print("Inicjalizacja skryptu przetwarzania wsadowego...")
|
134 |
+
|
135 |
+
INPUT_FOLDER = 'input_jsonl'
|
136 |
+
OUTPUT_FOLDER = 'output'
|
137 |
+
os.makedirs(OUTPUT_FOLDER, exist_ok=True)
|
138 |
+
|
139 |
+
# Skanowanie plików
|
140 |
+
jsonl_files = glob.glob(os.path.join(INPUT_FOLDER, '*.jsonl'))
|
141 |
+
|
142 |
+
for file_path in jsonl_files:
|
143 |
+
start_time = time.time()
|
144 |
+
output_file = os.path.join(OUTPUT_FOLDER, os.path.basename(file_path))
|
145 |
+
|
146 |
+
if os.path.exists(output_file):
|
147 |
+
print(f"POMIJAM - plik już istnieje: {output_file}")
|
148 |
+
continue
|
149 |
+
|
150 |
+
print(f"\n--- Przetwarzanie pliku: {file_path} ---")
|
151 |
+
process_jsonl_file(file_path, output_file)
|
152 |
+
end_time = time.time()
|
153 |
+
print(f"Processing time: {end_time - start_time:.4f} seconds")
|
154 |
+
|
155 |
+
print("\nWszystkie pliki zostały przetworzone!")
|
main_parquet.py
ADDED
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: utf-8 -*-
|
2 |
+
"""
|
3 |
+
Skrypt do masowego przetwarzania plików parquet w celu klasyfikacji jakości tekstu.
|
4 |
+
|
5 |
+
Ten moduł jest przeznaczony do wydajnej analizy dużych zbiorów danych.
|
6 |
+
Skanuje folder wejściowy w poszukiwaniu plików .parquet, przetwarza każdy z nich
|
7 |
+
równolegle z użyciem wielu procesów (`multiprocessing`), a następnie zapisuje
|
8 |
+
wyniki do nowego pliku w folderze wyjściowym, zachowując oryginalną strukturę
|
9 |
+
danych i dodając wyniki klasyfikacji.
|
10 |
+
"""
|
11 |
+
|
12 |
+
# --- Importy bibliotek ---
|
13 |
+
import os
|
14 |
+
import glob
|
15 |
+
import time
|
16 |
+
import pickle
|
17 |
+
import joblib
|
18 |
+
import pandas as pd
|
19 |
+
import json
|
20 |
+
import numpy as np
|
21 |
+
from tqdm import tqdm
|
22 |
+
from typing import List
|
23 |
+
|
24 |
+
from text_analyzer.analyzer import TextAnalyzer
|
25 |
+
from text_analyzer import constants # Potrzebujemy COLUMN_ORDER
|
26 |
+
|
27 |
+
# --- Ładowanie modeli i konfiguracja ---
|
28 |
+
|
29 |
+
# Modele są ładowane na poziomie globalnym. W systemach uniksowych (Linux/macOS)
|
30 |
+
# dzięki mechanizmowi 'fork', procesy-dzieci dziedziczą te obiekty bez ponownego
|
31 |
+
# odczytu z dysku. Na Windowsie ('spawn'), każdy proces-dziecko musi zaimportować
|
32 |
+
# skrypt i załadować modele od nowa.
|
33 |
+
|
34 |
+
with open('models/scaler.pkl', 'rb') as f:
|
35 |
+
scaler = pickle.load(f)
|
36 |
+
classifier = joblib.load("models/model.joblib")
|
37 |
+
text_analyzer = TextAnalyzer()
|
38 |
+
|
39 |
+
# Liczba procesów roboczych do wykorzystania podczas analizy tekstów wewnątrz jednego pliku.
|
40 |
+
NUM_PROCESSES = 10
|
41 |
+
|
42 |
+
class NumpyJSONEncoder(json.JSONEncoder):
|
43 |
+
"""
|
44 |
+
Specjalny enkoder JSON do obsługi typów danych z NumPy,
|
45 |
+
które nie są domyślnie serializowalne.
|
46 |
+
"""
|
47 |
+
def default(self, obj):
|
48 |
+
if isinstance(obj, np.integer):
|
49 |
+
return int(obj)
|
50 |
+
if isinstance(obj, np.floating):
|
51 |
+
return float(obj)
|
52 |
+
if isinstance(obj, np.ndarray):
|
53 |
+
return obj.tolist()
|
54 |
+
return super(NumpyJSONEncoder, self).default(obj)
|
55 |
+
|
56 |
+
# --- Definicje funkcji ---
|
57 |
+
|
58 |
+
def predict_batch(texts: List[str], analyzer: TextAnalyzer, scaler_model, classifier_model) -> List[tuple[str | None, float | None]]:
|
59 |
+
"""
|
60 |
+
Przetwarza całą listę tekstów wsadowo i zwraca listę predykcji.
|
61 |
+
"""
|
62 |
+
all_features = []
|
63 |
+
|
64 |
+
# Krok 1: Ekstrakcja cech dla wszystkich tekstów za jednym zamachem
|
65 |
+
# Używamy tqdm, aby śledzić postęp analizy wsadowej
|
66 |
+
feature_generator = analyzer.analyze_batch(texts, batch_size=NUM_PROCESSES) # Dostosuj batch_size
|
67 |
+
for features_dict in tqdm(feature_generator, total=len(texts), desc="Analiza cech"):
|
68 |
+
ordered_features = [features_dict.get(fname, 0.0) for fname in constants.COLUMN_ORDER]
|
69 |
+
all_features.append(ordered_features)
|
70 |
+
|
71 |
+
if not all_features:
|
72 |
+
return []
|
73 |
+
|
74 |
+
# Krok 2: Przygotowanie i skalowanie wszystkich wektorów naraz
|
75 |
+
features_df = pd.DataFrame(all_features, columns=constants.COLUMN_ORDER)
|
76 |
+
features_scaled = scaler_model.transform(features_df)
|
77 |
+
|
78 |
+
# Krok 3: Predykcja dla całej paczki
|
79 |
+
pred_probas = classifier_model.predict_proba(features_scaled)
|
80 |
+
|
81 |
+
# Krok 4: Przetworzenie wyników
|
82 |
+
results = []
|
83 |
+
hardcoded_labels = ["LOW", "MEDIUM", "HIGH"]
|
84 |
+
for single_pred_proba in pred_probas:
|
85 |
+
category_prob = {
|
86 |
+
label: prob
|
87 |
+
for label, prob in zip(hardcoded_labels, single_pred_proba)
|
88 |
+
}
|
89 |
+
# Sortujemy, aby znaleźć kategorię z najwyższym prawdopodobieństwem
|
90 |
+
sorted_category_prob = sorted(category_prob.items(), key=lambda item: item[1], reverse=True)
|
91 |
+
|
92 |
+
# Pobieramy nazwę i wartość
|
93 |
+
most_probable_category, confidence = sorted_category_prob[0]
|
94 |
+
|
95 |
+
results.append((most_probable_category, round(float(confidence) * 100, 2)))
|
96 |
+
|
97 |
+
return results
|
98 |
+
|
99 |
+
def process_parquet_file(input_file: str, output_file: str):
|
100 |
+
"""
|
101 |
+
Orkiestruje proces przetwarzania pojedynczego pliku .parquet wsadowo.
|
102 |
+
Wczytuje plik, przetwarza kolumnę 'text', a następnie dopisuje
|
103 |
+
wynikowe kolumny 'quality_ai' i 'confidence' do nowego pliku Parquet.
|
104 |
+
"""
|
105 |
+
try:
|
106 |
+
# Krok 1: Wczytaj cały plik Parquet do ramki danych pandas
|
107 |
+
df = pd.read_parquet(input_file)
|
108 |
+
except Exception as e:
|
109 |
+
print(f"Nie udało się wczytać pliku {input_file}. Błąd: {e}")
|
110 |
+
return
|
111 |
+
|
112 |
+
# Sprawdzenie, czy kolumna 'text' istnieje
|
113 |
+
if 'text' not in df.columns:
|
114 |
+
print(f"Błąd: W pliku {input_file} brakuje wymaganej kolumny 'text'.")
|
115 |
+
return
|
116 |
+
|
117 |
+
# Krok 2: Przygotuj dane do przetwarzania wsadowego
|
118 |
+
texts_to_process = df['text'].tolist()
|
119 |
+
print(f"Wczytano {len(texts_to_process)} wierszy. Rozpoczynam przetwarzanie wsadowe...")
|
120 |
+
|
121 |
+
# Krok 3: Wywołaj funkcję wsadową (ta część pozostaje bez zmian)
|
122 |
+
# Zakładamy, że predict_batch zwraca listę krotek: [(kategoria, pewność), ...]
|
123 |
+
results = predict_batch(texts_to_process, text_analyzer, scaler, classifier)
|
124 |
+
|
125 |
+
# Krok 4: Dodaj wyniki jako nowe kolumny do ramki danych
|
126 |
+
# "Rozpakowujemy" listę krotek na dwie oddzielne listy
|
127 |
+
categories = [res[0] for res in results]
|
128 |
+
confidences = [res[1] for res in results]
|
129 |
+
|
130 |
+
df['quality_ai'] = categories
|
131 |
+
df['confidence'] = confidences
|
132 |
+
|
133 |
+
# Krok 5: Zapisz zmodyfikowaną ramkę danych do nowego pliku Parquet
|
134 |
+
try:
|
135 |
+
# index=False zapobiega zapisaniu indeksu pandas jako kolumny w pliku
|
136 |
+
df.to_parquet(output_file, index=False)
|
137 |
+
print(df.head(10))
|
138 |
+
print(f"Pomyślnie zapisano przetworzone dane do pliku {output_file}")
|
139 |
+
except Exception as e:
|
140 |
+
print(f"Nie udało się zapisać pliku {output_file}. Błąd: {e}")
|
141 |
+
|
142 |
+
# --- Główny blok wykonawczy ---
|
143 |
+
|
144 |
+
if __name__ == '__main__':
|
145 |
+
print("Inicjalizacja skryptu przetwarzania wsadowego...")
|
146 |
+
|
147 |
+
INPUT_FOLDER = 'input_parquet'
|
148 |
+
OUTPUT_FOLDER = 'output'
|
149 |
+
os.makedirs(OUTPUT_FOLDER, exist_ok=True)
|
150 |
+
|
151 |
+
# Skanowanie plików
|
152 |
+
parquet_files = glob.glob(os.path.join(INPUT_FOLDER, '*.parquet'))
|
153 |
+
|
154 |
+
for file_path in parquet_files:
|
155 |
+
start_time = time.time()
|
156 |
+
output_file = os.path.join(OUTPUT_FOLDER, os.path.basename(file_path))
|
157 |
+
|
158 |
+
if os.path.exists(output_file):
|
159 |
+
print(f"POMIJAM - plik już istnieje: {output_file}")
|
160 |
+
continue
|
161 |
+
|
162 |
+
print(f"\n--- Przetwarzanie pliku: {file_path} ---")
|
163 |
+
process_parquet_file(file_path, output_file)
|
164 |
+
end_time = time.time()
|
165 |
+
print(f"Processing time: {end_time - start_time:.4f} seconds")
|
166 |
+
|
167 |
+
print("\nWszystkie pliki zostały przetworzone!")
|
models/model.joblib
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:273db6e678f5c8cfd0fa4fde15c49996c9b4d49daeea4e7d6aaaac50e7c78b98
|
3 |
+
size 1085977
|
models/scaler.pkl
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:fab48617bcd5953b85f6202bd1e65baec5f5664bd2e1bf3ad926e08d1b9f6945
|
3 |
+
size 9468
|
output/test.jsonl
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{"text": "Pierwszy kamienny kościół w stylu romańskim powstał w tym miejscu już w XII wieku. Część romańskiej świątyni następnie rozebrano i zastąpiono budowlą gotycką. Obecnie najstarszą częścią budowli jest wieża pochodząca z pierwszej połowy XIII wieku. Prezbiterium i zakrystia pochodzą z końca XIII wieku, natomiast nawa została zbudowana na początku XIV wieku. Podejrzewa się, że planowano również rozebrać stosunkowo małą wieżę i zastąpić ją bardziej proporcjonalną, gotycką. Świątynia zachowała bardzo dużo ze swojego średniowiecznego charakteru. Renowację przeszła w 1937 roku", "quality_ai": "HIGH", "confidence": 99.97}
|
2 |
+
{"text": "FJM.B.ZP \n cykl kształcenia 2019-2024\nKARTA PRZEDMIOTU (SYLABUS) \nI. INFORMACJE OGÓLNE \n1.Nazwa przedmiotu\nZdrowie publiczne\n2. Kod przedmiotu\nFJM.B.ZP\n3. Karta przedmiotu ważna od roku akademickiego \n4.Status przedmiotuobowiązkowy\n5. Wydział\nWydział Nauk o Zdrowiu w Katowicach\n6. Kierunek\nFizjoterapia\n7. Profil kształcenia\nOgólnoakademicki\n8. Poziom kształcenia (studiów)\nJednolite magisterskie\n9. Forma prowadzenia studiów\nStacjonarne\n10. Semestr\nII\n11. Jednostka realizująca \nZakład Promocji Zdrowia i Pielęgniarstwa Środowiskowego\n12. Adres, telefon kontaktowy\nKatowice, <address>, tel: 32 20-88-300\n13. Strona internetowa\n14. Kierownik jednostkidr hab. n. med. Tomasz Irzyniec\n15. Osoba odpowiedzialna za prowadzenie przedmiotudr hab. n. med. Tomasz Irzyniec\n16. Nauczyciele akademiccy realizujący przedmiot\nDR N. MED. JADWIGA KAŹMIERCZAK,ZOFIA NOWAK-KAPUSTA, DR N. MED. KATARZYNA LESZCZYŃSKA, DR N. FARM. IZABELA MACIEJEWSKA-PASZEK, MGR IZABELA PAŁKA,MGR MAGDALENA SZOSTAK-TRYBUŚ, DR MAGDALENA SZOTOWSKA\n17. Przynależność do grupy przedmiotów\nB Nauki ogólne\n18. Język prowadzenia zajęć\nPolski\n19. Wymagania wstępne i dodatkowe w zakresie wiedzy, umiejętności i innych kompetencjiogólna wiedza z nauk społecznych, epidemiologicznych, biologii, higieny\n20. Cele przedmiotu \nSymbol\nCel przedmiotu \nC1\nDostarczenie wiedzy o założeniach i zadaniach Zdrowia Publicznego. \nC2\nWyrobienie umiejętności wskazywania na kulturowe, społeczne i ekonomiczne uwarunkowania zdrowia publicznego oraz zagrożenia zdrowotne współczesnego człowieka. \nC3\nWyposażenie absolwenta w umiejętności związane z profilaktyką chorób oraz promocją zdrowia w zakresie dysfunkcji i niepełnosprawności. Badanie przesiewowe w profilaktyce dysfunkcji i niepełnosprawności.\n C4\nDostarczenie wiedzy o założeniach , zadaniach i organizacji narodowego systemu opieki zdrowotnej w Polsce i na świecie, a także działaniach NFZ. \n C5\n Dostarczenie wiedzy o jakości w opiece zdrowotnej.\n II. OPIS EFEKTÓW UCZENIA SIĘ DLA PRZEDMIOTU I ODNIESIENIE DO CELÓW PRZEDMIOTU I EUs ZE STANDARDU\nNumer efektu uczenia się dla przedmiotu (symbol)\nStudent, który zaliczył przedmiotznarozumiepotrafijest gotów do:\nOdniesienie do celów przedmiotu\nOdniesienie do efektów uczenia się ze standardu\nEUs_W1\nczynniki decydujące o zdrowiu oraz o zagrożeniu zdrowia;\nC1\nB.W11.\nEUs_W2\nzasady edukacji zdrowotnej i promocji zdrowia oraz elementy polityki społecznej dotyczącej ochrony zdrowia;\nC2\nB.W12.\nEUs_U1\norganizować działania ukierunkowane na edukację zdrowotną, promocję zdrowia i profilaktykę niepełnosprawności;\nC1, C4\nB.U4.\nEUs_U2\nprzeprowadzić badanie przesiewowe w profilaktyce dysfunkcji i niepełnosprawności.\nC1, C3\nB.U5.\nIII. MACIERZ EFEKTÓW KSZTAŁCENIA DLA PRZEDMIOTU W ODNIESIENIU DO FORM ZAJĘĆ, STOSOWANYCH METOD DYDAKTYCZNYCH ORAZ SPOSOBÓW WERYFIKACJI\nNumer efektu uczenia się DLA PRZEDMIOTU (symbol)\nForma zajęć dydaktycznych\nMetody dydaktyczne\nSposób weryfikacji efektów kształcenia (forma zaliczeń)\nWykład\nZajęcia seminaryjne\nĆwiczenia\nZajęcia praktyczne\nPraktyka zawodowa\nBez nauczyciela\nEUs_W1\n2 \nWykład, prezentacja multimedialna\nZaliczenie na ocenę, test pytań zamkniętych, jednokrotnego wyboru\nEUs_W2\nWykład, prezentacja multimedialna\nZaliczenie na ocenę, test pytań zamkniętych, jednokrotnego wyboru\nEUs_U1\nPrezentacja multimedialnadyskusja\nZaliczenie na ocenę, test pytań zamkniętych, jednokrotnego wyboru\nEUs_U2\nprezentacja multimedialnadyskusja\nZaliczenie na ocenę, test pytań zamkniętych, jednokrotnego wyboru\nIV. TREŚCI PRZEDMIOTU I ODNIESIENIE DO EFEKTÓW UCZENIA SIĘ DLA PRZEDMIOTU\nSYMBOL\nWYKŁADY\nLICZBA GODZIN\nOdniesienie do efektów uczenia się dla przedmiotu\nWk1\n Kulturowe, społeczne i ekonomiczne uwarunkowania zdrowia publicznego. Charakterystyka istoty profilaktyki i prewencji chorób. Podstawy organizacji Narodowego Systemu Zdrowia w Polsce. Zasady funkcjonowania rynku usług medycznych w Polsce oraz wybranych krajach Unii Europejskiej\nEUs_W1\nEUs_W2\nWk2\nStruktura i funkcje jednostek opieki zdrowotnej. Definiowanie pojęć jakości w opiece zdrowotnej, kryteria opieki zdrowotnej. Metody i techniki oceny stanu zdrowia populacji\nEUs_W1\nEUs_W2\nSUMA GODZIN\nSYMBOL\nSEMINARIA\nLICZBA GODZIN\nOdniesienie do efek-tów uczenia się dla przedmiotu\nS1\nPrzedstawienia modeli poznawczych zachowań zdrowotnych\nEUs_U1\nEUs_U2\nS2\nWyjaśnienia podstawowych pojęć medycyny środowiskowej\nWyjaśnienia znaczenie stresu dla zdrowia. \nŹródła zanieczyszczenia powietrza, gleby i wód\nEUs_U1\nS3\nWyjaśnienia znaczenie stresu dla zdrowia. \nŹródła zanieczyszczenia powietrza, gleby i wód\nEUs_U2\nS4\nMiędzynarodowe programy ochrony ludności przed niekorzystnymi wpływami środowiska. Organizacja działań ukierunkowane na edukację zdrowotną, promocję zdrowia i profilaktykę niepełnosprawności.\nEUs_U1\nS5\nCele strategiczne i cele szczegółowe Narodowego Programu Zdrowia. Badania przesiewowe w profilaktyce dysfunkcji i niepełnosprawności.\nEUs_U1\nEUs_U2\nSUMA GODZIN\nSYMBOL\nĆWICZENIA\nLICZBA GODZIN\nOdniesienie do efek-tów uczenia się dla przedmiotu\nĆw1\nĆw2\nĆw3\nSUMA GODZIN\nSYMBOL\nSYMULACJA MEDYCZNA\nLICZBA GODZIN\nOdniesienie do efektów uczenia się dla przedmiotu\nSM1\nSM2\nSUMA GODZIN\nSYMBOL\nZAJĘCIA PRAKTYCZNE\nLICZBA GODZIN\nOdniesienie do efektów uczenia się dla przedmiotu\nZ1\nZ2\nSUMA GODZIN\nSYMBOL\nPARKTYKA ZAWODOWA\nLICZBA GODZIN\nOdniesienie do efektów uczenia się dla przedmiotu\nP1\nP2\nSUMA GODZIN\nV. METODY WALIDACJI KOŃCOWYCH EFEKTÓW UCZENIA SIĘ\nZaliczenia:\nZaliczenie na ocenę, test pytań zamkniętych, jednokrotnego wyboru\nEgzamin:\n Nie dotyczy\nPraktyka zawodowa:\n Nie dotyczy\nVI. ZALECANA LITERATURA \nPODSTAWOWA\n1. Jethon Z.: Medycyna zapobiegawcza i środowiskowa. PZWL, Warszawa 2000\n2. Kozierkiewicz A.: Zdrowie 21. Zdrowie dla wszystkich w XXI w. Kraków, Uniwersyteckie Wydanie Medyczne, Vesalius 2001\n3. Kulik B., Latalski M.: Zdrowie Publiczne. Czelej sp. z oo, Lublin 2002\nWojt 4. Wojtczak A. Zdrowie Publiczne , PZWL, Warszawa , 2008\n5. Wojtczak A: Zdrowie publiczne wyzwaniem dla systemów zdrowia XXI wieku. PZWL, Warszawa, 2009.\nUZUPEŁNIAJĄCA\n1. Leowski J: Polityka zdrowotna a zdrowie publiczne. CEDEWU. Warszawa, 2008. \nVII. INNE PRZYDATNE INFORMACJE O PRZEDMIOCIE\nMateriały do zajęć\nX\nMiejsca odbywania się zajęć\nX \nMiejsca i godziny konsultacji\n X \nVIII. NAKŁAD PRACY STUDENTA (BILANS PUNKTÓW ECTS)\nRodzaj zajęć\nLiczba godzin\nPunkty ECTS\nWykłady\nSeminaria \nSymulacja medyczna\nĆwiczenia\nBezkontaktowe\nZajęcia praktyczne\nPraktyki zawodowe\nSUMA\nIX. KRYTERIA OCENIANIA\nEfekt uczenia się dla przedmiotu\nNa ocenę niedostateczną (2,0)\nNa ocenę dostateczną (3,0)\nNa ocenę dość dobrą (3,5)\nNa ocenę dobrą (4,0)\nNa ocenę ponad dobrą (4,5)\nNa ocenę bardzo dobrą (5,0)\nEUs_W1\nNie opanował materiału o czynnikach decydujących o zdrowiu oraz o zagrożeniu zdrowia\nOpanował materiał w minimalnym zakresię o czynnikach decydujących o zdrowiu oraz o zagrożeniu zdrowia\nOpanował materiał w niewielkim zakresie o czynnikach decydujących o zdrowiu oraz o zagrożeniu zdrowia\nOpanował materiał w podstawowym zakresie o czynnikach decydujących o zdrowiu oraz o zagrożeniu zdrowia\nOpanował materiał w niemal całkowitym zakresie o czynnikach decydujących o zdrowiu oraz o zagrożeniu zdrowia\nOpanował materiał w całkowitym zakresie o czynnikach decydujących o zdrowiu oraz o zagrożeniu zdrowia\nEUs_W2\nNie opanował materiału nt. zasad edukacji zdrowotnej i promocji zdrowia\nOpanował materiał w minimalnym zakresie nt. zasad edukacji zdrowotnej i promocji zdrowia\nOpanował materiał w niewielkim zakresie nt. zasad edukacji zdrowotnej i promocji zdrowia\nOpanował materiał w podstawowym zakresie nt. zasad edukacji zdrowotnej i promocji zdrowia\nOpanował materiał w niemal całkowitym zakresie nt. zasad edukacji zdrowotnej i promocji zdrowia\nOpanował materiał w całkowitym zakresie nt. zasad edukacji zdrowotnej i promocji zdrowia\nEUs_U1\nNie opanował materiału o organizowaniu działań ukierunkowanych na edukację zdrowotną, promocję zdrowia i profilaktykę niepełnosprawności\nOpanował materiał w minimalnym zakresie o organizowaniu działań ukierunkowanych na edukację zdrowotną, promocję zdrowia i profilaktykę niepełnosprawności\nOpanował materiał w niewielkim zakresie o organizowaniu działań ukierunkowanych na edukację zdrowotną, promocję zdrowia i profilaktykę niepełnosprawności\nOpanował materiał w podstawowym zakresie o organizowaniu działań ukierunkowanych na edukację zdrowotną, promocję zdrowia i profilaktykę niepełnosprawności\nOpanował materiał w niemal całkowitym zakresie o organizowaniu działań ukierunkowanych na edukację zdrowotną, promocję zdrowia i profilaktykę niepełnosprawności\nOpanował materiał w całkowitym zakresie o organizowaniu działań ukierunkowanych na edukację zdrowotną, promocję zdrowia i profilaktykę niepełnosprawności\nEUs_U2\nNie opanował materiału nt. przeprowadzenia badań przesiewowych w profilaktyce dysfunkcji i niepełnosprawności\nOpanował materiał w minimalnym zakresie nt. przeprowadzenia badań przesiewowych w profilaktyce dysfunkcji i niepełnosprawności\nOpanował materiał w niewielkim zakresie nt. przeprowadzenia badań przesiewowych w profilaktyce dysfunkcji i niepełnosprawności\nOpanował materiał w podstawowym zakresie nt. przeprowadzenia badań przesiewowych w profilaktyce dysfunkcji i niepełnosprawności\nOpanował materiał w niemal całkowitym zakresie nt. przeprowadzenia badań przesiewowych w profilaktyce dysfunkcji i niepełnosprawności\nOpanował materiał w całkowitym zakresie nt. przeprowadzenia badań przesiewowych w profilaktyce dysfunkcji i niepełnosprawności\n Zatwierdzono:\n ...\n (data i podpis Kierownika Katedry lub Zakładu", "quality_ai": "LOW", "confidence": 99.97}
|
3 |
+
{"text": "Sztuka romańska (styl romański, romanizm, romańszczyzna) – styl w sztukach plastycznych XI–XIII wieku, ukształtowany w Europie zachodniej (na zachód od Renu), na terenach zajmowanych obecnie przez dzisiejsze północne Włochy, Francję i zachodnie Niemcy. Wkrótce zasięgiem nowego stylu objęte zostały kolejne tereny Europy i wraz z prowadzonymi wyprawami krzyżowymi przeniknął na Bliski Wschód. Czas trwania sztuki romańskiej był niejednolity; występowały także różnice w stylu między poszczególnymi regionami. Sztuka ta wyrosła na bazie antyku oraz doświadczeń sztuki karolińskiej i ottońskiej, także bizantyńskiej. Romanizm związany był z działalnością Kościoła katolickiego i stąd obecny był w głównej mierze w sztuce sakralnej.", "quality_ai": "HIGH", "confidence": 99.92}
|
4 |
+
{"text": "Przypisy\n Jerzy Z. Łoziński: Pomniki sztuki w Polsce. T. 1: Małopolska. Warszawa: Wydawnictwo „Arkady”, 1985, s. 401–402. ISBN 83-213-3005-3.\nLinki zewnętrzne\nTransRomanica – The Romanesque Routes of European Heritage\nKlasztor Cystersów w Sulejowie – Podklasztorzu\nEwangeliarz Anastazji na stronie Polona.pl\nSakramentarz tyniecki na stronie Polona.pl\nKontrola autorytatywna (ruch artystyczny):\nLCCN: sh85007873GND: 4050482-7NDL: 00569670BnF: 11930977jBNCF: 4264NKC: ph125218BNE: XX525353J9U: 987007295497105171\nEncyklopedie internetowe:\nBritannica: art/Romanesque-art\nKategoria: Sztuka romańska", "quality_ai": "LOW", "confidence": 92.54}
|
5 |
+
{"text": "Na temat historii sakramentarza w wiekach XII–XIX jest niewiele informacji. Prawdopodobnie przynajmniej do XIII w. był używany jako księga liturgiczna, później traktowano go jako cenny zabytek. Wiadomo, że w 1656, w czasie wojen szwedzkich, księga została zrabowana z Tyńca przez Szwedów, a następnie odkupiona w Krakowie, skąd powróciła do klasztoru. Informuje o tym notka na pierwszej karcie: Anno domini 1656 die 11 Augusti tę xięgę w klasztorze tynieckim wyrabowano gdy obóz Szwedzi rozproszyli Polaków tamże pod tymże klasztorem y tę xiążkę odkupiono w Krakowie. Obecna oprawa rękopisu pochodzi z XVII wieku[4]. W opracowaniach przyjmowano, że księga mogła mieć wcześniej drogocenną oprawę z kości słoniowej, która została odłączona po zrabowaniu przez Szwedów[6]. Możliwe jednak, że zniszczona oprawa została wymieniona jeszcze przed zrabowaniem rękopisu.", "quality_ai": "HIGH", "confidence": 96.03}
|
6 |
+
{"text": "Przednia okładka\nPrzednia okładka\n \nMiniatura Maiestas Domini\nMiniatura Maiestas Domini\n \nMiniatura Ukrzyżowanie\nMiniatura Ukrzyżowanie\n \nInicjał całostronnicowy V\nInicjał całostronnicowy V\n \nInicjał całostronnicowy D\nInicjał całostronnicowy D\n \nOzdobna strona spisana na purpurze\nOzdobna strona spisana na purpurze\n \nOzdobna strona spisana na purpurze\nOzdobna strona spisana na purpurze\n \nPierwsza strona tekstu\nPierwsza strona tekstu\n \nStrona z inicjałem C\nStrona z inicjałem C\n \nStrona z dopisanymi glosami i neumami\nStrona z dopisanymi glosami i neumami", "quality_ai": "LOW", "confidence": 92.64}
|
7 |
+
{"text": "Uchwała Nr 19\nZarządu Związku Rzemiosła Polskiego\nz dnia 5 grudnia 2017 r.\nznak: NO-I-130/Standardy/17\nw sprawie: ustalenia standardów wymagań egzaminacyjnych na egzamin czeladniczy przeprowadzany\nprzez komisje egzaminacyjne izb rzemieślniczych.\nNa podstawie art.3, ust.3a ustawy o rzemiośle z dnia 22 marca 1989 r. (Dz. U. 2016, poz. 1285) oraz §2\nust.2, pkt.7 Statutu Związku Rzemiosła Polskiego - Zarząd Związku Rzemiosła Polskiego uchwala co\nnastępuje:\n§1\n1. Ustala się standard wymagań egzaminacyjnych w zawodzie odpowiadającym danemu rodzajowi\nrzemiosła, występującym w klasyfikacji zawodów i specjalności rynku pracy oraz klasyfikacji zawodów\nszkolnictwa zawodowego uwzględniając wymagania określone w podstawie programowej\nkształcenia w zawodach, dla n/w zawodu:\nNr\nstandardu\nKod z wykazu zawodów, wg\nklasyfikacji zawodów i\nspecjalności dla potrzeb\nrynku pracy\nNazwa zawodu\n6/cz 721104 MODELARZ ODLEWNICZY\n§2\nStandard, o którym mowa w § 1, uwzględniony jest w ramach przygotowywania zadań egzaminacyjnych\ndo etapu praktycznego i do części pisemnej oraz części ustnej etapu teoretycznego egzaminu\nczeladniczego, o którym mowa w § 12 rozporządzenia Ministra Edukacji Narodowej z dnia 10 stycznia\n2017 r. w sprawie egzaminu czeladniczego, egzaminu mistrzowskiego oraz egzaminu sprawdzającego\nprzeprowadzanych przez komisje egzaminacyjne izb rzemieślniczych (Dz.U. z 2017 r., poz. 89)\n§3\nUchwała wchodzi w życie z dniem podjęcia.\n/-/ /-/\n ZASTĘPCA PREZESA PREZES Zarządu ZRP\n ", "quality_ai": "MEDIUM", "confidence": 62.49}
|
8 |
+
{"text": "Alternatywy 4 to jeden z najważniejszych i najbardziej kultowych polskich seriali komediowych, który stał się nie tylko rozrywką, ale i wyjątkowym zwierciadłem epoki schyłkowego PRL-u. Wyreżyserowany przez Stanisława Bareję według scenariusza Janusza Płońskiego i Macieja Rybińskiego (wzbogaconego o autorskie pomysły Barei), serial powstał na początku lat 80., a jego emisja była przez kilka lat blokowana przez cenzurę ze względu na ostrą, satyryczną krytykę rzeczywistości socjalistycznej.\n\nFabuła i bohaterowie\nAkcja serialu rozgrywa się w nowo wybudowanym bloku na warszawskim Ursynowie przy fikcyjnej ulicy Alternatywy 4, gdzie wprowadzają się lokatorzy pochodzący z różnych grup społecznych: robotnik, lekarz, nauczycielka, karierowicz, emerytka, naukowiec, artystka, przedstawiciel prywatnej inicjatywy, a nawet stypendysta z Harvardu. Ich codzienne życie staje się areną walki z absurdami PRL-owskiej biurokracji, niedoróbkami budowlanymi, reglamentacją towarów i wszechobecną kontrolą. Centralną postacią jest gospodarz domu Stanisław Anioł (Roman Wilhelmi) - drobny karierowicz, marzący o władzy i porządku, który swoimi działaniami doprowadza mieszkańców do granic wytrzymałości.\n\nW obsadzie znaleźli się wybitni polscy aktorzy: Bożena Dykiel (Mieczysława Anioł), Witold Pyrkosz (Józef Balcerek), Zofia Czerwińska (Zofia Balcerkowa), Jerzy Turek (Tadeusz Kubiak), Wojciech Pokora (docent Furman), Stanisława Celińska (Bożena Lewicka), Mieczysław Voit (profesor Rozwadowski), Jerzy Bończak (Krzysztof Manc), Jerzy Kryszak (lekarz Kołek), Kazimierz Kaczor (Zygmunt Kotek), Janusz Gajos (Jan Winnicki) i wielu innych.\n\nTematyka i przesłanie\nSerial jest mistrzowską satyrą na realia późnego PRL-u. Bareja i jego współscenarzyści stworzyli spójny ekranowy mikrokosmos, będący parafrazą państwa policyjnego z całą machiną nacisku i obywatelami, których jedynym celem wydaje się przechytrzenie wszechwładnego gospodarza. Każdy odcinek to ironiczny komentarz do absurdów codzienności: walki o towar, awarii budowlanych, biurokracji, kolejek, czy prób narzucania ideologii. Serial nie stroni od aluzji do wydarzeń historycznych i politycznych - pojawiają się odniesienia do emigracji po Marcu 1968, strajków robotniczych, a nawet zbrodni katyńskiej, przemycane w dialogach i retrospekcjach bohaterów.\n\nWażnym motywem jest również siła wspólnoty - mieszkańcy, mimo różnic, uczą się współpracować i przeciwstawiać się opresji, co w finale prowadzi do spektakularnej „zemsty” na Aniele podczas wizyty zagranicznej delegacji, gdy blok zostaje celowo przemieniony w ruderę, a Anioł kompromituje się przed władzami.\n\nProdukcja i recepcja\nProdukcja serialu rozpoczęła się w 1981 roku, jednak ze względu na wprowadzenie stanu wojennego i bojkot telewizji przez aktorów, realizacja była kilkakrotnie przerywana i wznowiona dopiero w 1982 roku. Po ukończeniu w 1983 roku emisja została zablokowana przez cenzurę na ponad trzy lata. Serial krążył w nielegalnym obiegu na kasetach wideo, a do oficjalnej telewizji trafił dopiero w 1986 roku, już po usunięciu niektórych najbardziej krytycznych scen. Dopiero w 2014 roku Telewizja Polska opublikowała zrekonstruowaną wersję, przywracającą ocenzurowane fragmenty.\n\nPoczątkowo przyjęcie przez krytyków było chłodne - zarzucano brak spójności i chaotyczny montaż, jednak widzowie szybko docenili serial za inteligentny humor, celne obserwacje i galerię barwnych postaci. „Alternatywy 4” zyskały status kultowego dzieła, a cytaty, sceny i postacie na stałe weszły do polskiej popkultury.\n\nDziedzictwo i znaczenie\nDziś „Alternatywy 4” są nie tylko rozrywką, ale także cenną lekcją historii i socjologicznym portretem schyłkowego PRL-u. Serial regularnie powraca na antenę, doczekał się cyfrowej rekonstrukcji, a jego bohaterowie i motywy są przedmiotem analiz, wspomnień i licznych odniesień w kulturze współczesnej. Stanowi wzorzec polskiej satyry, będąc jednocześnie świadectwem epoki, w której powstał - epoki absurdu, sprytu i walki o godność w codziennym życiu.\n", "quality_ai": "HIGH", "confidence": 99.98}
|
9 |
+
{"text": "Akslop, może to jakieś duńskie miasto\njestem tu przejazdem, co prawda na\nnieco dłużej, bo ministrowie rolnictwa\nusiedli na bańkach z mlekiem i zatarasowali\nwszystkie szosy, zdążono mnie trochę rozwałkować\nlokalnymi osobliwościami, jak Diwron\nczy Cziweżór. kochałem tutejsze dziewczyny,\npolicja parę razy pogoniła mnie po\nchodnikach, mieszkańcy są bardzo serdeczni,\nnamawiają, żebym został na dłużej, obiecuję\nwam, gdziekolwiek się znajdę, zawsze pamiętać będę\nAkslop.", "quality_ai": "HIGH", "confidence": 73.6}
|
10 |
+
{"text": "Bielik - orzeł, czy nie orzeł?\nBielik, birkut, orzeł bielik, orzeł białogłów, orzeł białogon. Z pewnością znalazłoby się jeszcze kilka nazw określających tego potężnego drapieżnika, którego sylwetka prawdopodobnie od wieków zdobi godło naszego państwa. Prawdopodobnie, bo nierozwiązany spór o to trwa od wielu lat. Przedmiotem sporu są oczywiście dwa największe ptaki drapieżne występujące w naszym kraju - tytułowy bielik oraz minimalnie mniejszy orzeł przedni.\n\nGdyby wziąć pod uwagę fakt, że ten pierwszy z wiekiem stają się coraz jaśniejszy, niemal siwy z czysto białym ogonem, to jest mu już całkiem blisko do orła z naszego godła. Kolejnym argumentem przemawiającym za bielikiem są nieopierzone skoki widoczne na symbolu narodowym. Orły z rodzaju Aquila, do których należy orzeł przedni, mają skoki pokryte piórami do samej nasady palców.\n\nWedług naukowców bielik jednak nie jest orłem, ale należy do rodzaju Haliaeetus, czyli orłanów. Genetycznie więc bliżej mu do kani rudej, niż do rodzaju Aquila (orłów właściwych), którego przedstawicielem jest wspomniany już orzeł przedni. Nie zmienia to faktu, że w wielu językach, wliczając w to również nazwę naukową, bielik jest nazywany orłem. Przykładami mogą być niemiecki Seeadler (orzeł morski), angielski White-tailed eagle (orzeł z białym ogonem), czy wreszcie Haliaeetus albicilla, gdzie pierwszy człon jest połączeniem dwóch greckich słów hals – morze i aetos – orzeł. Określenie albi-cilla oznacza biały ogon.\n\nSporo zamieszania w nazewnictwo wprowadziło również dodanie bielikowi przedrostka orzeł w latach 70., kiedy gatunkowi zagrażało wyginięcie. Bezlitośnie był wówczas tępiony jako szkodnik, rzekomo siejący spustoszenie w hodowlach ryb. Nazwanie go orłem miało podnieść jego prestiż i wzbudzić większy szacunek wśród potencjalnych prześladowców. Faktycznie, bielik żywi się rybami. Zwłaszcza tymi łatwo dostępnymi - słabszymi, chorymi, martwymi, czy przebywającymi tuż pod powierzchnią wody. Ptaki te nauczyły się również doskonale korzystać z resztek wyrzucanych za burtę przez statki rybackie. Często można obserwować bieliki podążające za kutrami i cierpliwie wyczekujące na wyrzuconą z jego pokładu rybę, którą błyskawicznie podejmują z powierzchni wody.\n\nCiekawostką jest, że uprawiają również kleptopasożytnictwo, czyli podbierają upolowaną zdobycz wydrom, kormoranom, mewom, czy rybołowom. Ryby nie są jednak jedynym składnikiem diety bielików. Równie chętnie polują one na ptactwo wodno-błotne, zwłaszcza łyski i krzyżówki. W tym przypadku wyspecjalizowały się w polowaniu w tandemie, polegającym na tym, że jeden z osobników przelatując nad szuwarami, stara się wypłoszyć z nich kaczki lub łyski, a w tym samym czasie drugi przypuszcza atak na ofiarę, która nieostrożnie pojawiła się na otwartej wodzie.\n\nBardzo ważnym składnikiem diety bielików jest również padlina, która w okresie zimowym, zwłaszcza u osobników młodych stanowi znaczący procent pobieranego pokarmu. Drapieżniki te słysząc stado kruków, które już wcześniej namierzyły resztki, po ich głosie szybko lokalizują stołówkę. Jeśli jest ona atrakcyjna, może się na niej zgromadzić nawet kilkanaście bielików.\n\nPo kryzysie z lat 70. sytuacja gatunku w naszym kraju zdecydowanie się poprawiła i aktualnie bielik nie jest ptakiem, którego spotkanie graniczy z cudem. Pamiętać jednak należy, że nadal podlega statusowi ochrony ścisłej, a wokół jego gniazd wyznacza się strefy ochronne. Umieszczony jest również w Polskiej Czerwonej Księdze Zwierząt i Załączniku Dyrektywy Ptasiej.", "quality_ai": "HIGH", "confidence": 99.92}
|
output/test.parquet
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:835c69e800a83f0149deb32f0834f191eb5369db1d1994a090949427241949f7
|
3 |
+
size 22777
|
requirements.txt
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
annotated-types==0.7.0
|
2 |
+
blis==1.3.0
|
3 |
+
catalogue==2.0.10
|
4 |
+
certifi==2025.7.9
|
5 |
+
charset-normalizer==3.4.2
|
6 |
+
click==8.2.1
|
7 |
+
cloudpathlib==0.21.1
|
8 |
+
cmudict==1.0.33
|
9 |
+
colorama==0.4.6
|
10 |
+
confection==0.1.5
|
11 |
+
cymem==2.0.11
|
12 |
+
idna==3.10
|
13 |
+
importlib_metadata==8.7.0
|
14 |
+
importlib_resources==6.5.2
|
15 |
+
Jinja2==3.1.6
|
16 |
+
joblib==1.5.1
|
17 |
+
langcodes==3.5.0
|
18 |
+
language_data==1.3.0
|
19 |
+
marisa-trie==1.2.1
|
20 |
+
markdown-it-py==3.0.0
|
21 |
+
MarkupSafe==3.0.2
|
22 |
+
mdurl==0.1.2
|
23 |
+
murmurhash==1.0.13
|
24 |
+
numpy==2.2.6
|
25 |
+
packaging==25.0
|
26 |
+
pandas==2.3.1
|
27 |
+
pl_core_news_md @ https://github.com/explosion/spacy-models/releases/download/pl_core_news_md-3.8.0/pl_core_news_md-3.8.0-py3-none-any.whl#sha256=4f433a556880c0b7ef01970e7c612c0e30103b893e6d26ed54672d8e056ad9b1
|
28 |
+
preshed==3.0.10
|
29 |
+
pyarrow==20.0.0
|
30 |
+
pydantic==2.11.7
|
31 |
+
pydantic_core==2.33.2
|
32 |
+
Pygments==2.19.2
|
33 |
+
pyphen==0.17.2
|
34 |
+
python-dateutil==2.9.0.post0
|
35 |
+
pytz==2025.2
|
36 |
+
requests==2.32.4
|
37 |
+
rich==14.0.0
|
38 |
+
scikit-learn==1.7.0
|
39 |
+
scipy==1.16.0
|
40 |
+
setuptools==78.1.1
|
41 |
+
shellingham==1.5.4
|
42 |
+
six==1.17.0
|
43 |
+
smart_open==7.3.0.post1
|
44 |
+
spacy==3.8.2
|
45 |
+
spacy-legacy==3.0.12
|
46 |
+
spacy-loggers==1.0.5
|
47 |
+
srsly==2.5.1
|
48 |
+
textstat==0.7.7
|
49 |
+
thinc==8.3.6
|
50 |
+
threadpoolctl==3.6.0
|
51 |
+
tqdm==4.67.1
|
52 |
+
typer==0.16.0
|
53 |
+
typing-inspection==0.4.1
|
54 |
+
typing_extensions==4.14.1
|
55 |
+
tzdata==2025.2
|
56 |
+
urllib3==2.5.0
|
57 |
+
wasabi==1.1.3
|
58 |
+
weasel==0.4.1
|
59 |
+
wheel==0.45.1
|
60 |
+
wrapt==1.17.2
|
61 |
+
xgboost==2.0.3
|
62 |
+
zipp==3.23.0
|
text_analyzer/__pycache__/analyzer.cpython-311.pyc
ADDED
Binary file (5.96 kB). View file
|
|
text_analyzer/__pycache__/analyzer.cpython-312.pyc
ADDED
Binary file (5.45 kB). View file
|
|
text_analyzer/__pycache__/constants.cpython-311.pyc
ADDED
Binary file (7.95 kB). View file
|
|
text_analyzer/__pycache__/constants.cpython-312.pyc
ADDED
Binary file (11.9 kB). View file
|
|
text_analyzer/__pycache__/utils.cpython-311.pyc
ADDED
Binary file (654 Bytes). View file
|
|
text_analyzer/__pycache__/utils.cpython-312.pyc
ADDED
Binary file (617 Bytes). View file
|
|
text_analyzer/analyzer.py
ADDED
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# text_analyzer/analyzer.py (WERSJA POPRAWIONA)
|
2 |
+
|
3 |
+
"""
|
4 |
+
Główny moduł biblioteki zawierający klasę TextAnalyzer.
|
5 |
+
"""
|
6 |
+
import spacy
|
7 |
+
import textstat
|
8 |
+
import re
|
9 |
+
from typing import Dict, List, Tuple, Iterable
|
10 |
+
|
11 |
+
from . import constants
|
12 |
+
from .features import base_features, linguistic_features, regex_features, spacy_features, structural_features
|
13 |
+
|
14 |
+
class TextAnalyzer:
|
15 |
+
"""
|
16 |
+
Główna klasa do kompleksowej analizy tekstu w języku polskim.
|
17 |
+
"""
|
18 |
+
def __init__(self, disable_pipelines: List[str] = None):
|
19 |
+
"""
|
20 |
+
Inicjalizuje analizator.
|
21 |
+
|
22 |
+
Args:
|
23 |
+
disable_pipelines (List[str], optional): Lista komponentów spaCy do wyłączenia
|
24 |
+
w celu przyspieszenia przetwarzania.
|
25 |
+
Np. ["parser", "ner"] jeśli nie są potrzebne.
|
26 |
+
"""
|
27 |
+
try:
|
28 |
+
# Wyłączamy komponenty, jeśli użytkownik tego zażąda
|
29 |
+
self.nlp = spacy.load(constants.SPACY_MODEL_PL, disable=disable_pipelines or [])
|
30 |
+
self.nlp.max_length = constants.NLP_MAX_LENGTH
|
31 |
+
except OSError:
|
32 |
+
# ... (obsługa błędu bez zmian) ...
|
33 |
+
print(f"Błąd: Nie znaleziono modelu spaCy '{constants.SPACY_MODEL_PL}'.")
|
34 |
+
print(f"python -m spacy download {constants.SPACY_MODEL_PL}")
|
35 |
+
raise
|
36 |
+
textstat.set_lang('pl_PL')
|
37 |
+
|
38 |
+
def _preprocess(self, text: str) -> Tuple:
|
39 |
+
# Ta metoda jest teraz bardziej pomocnicza, doc jest przekazywany z zewnątrz
|
40 |
+
text_lower = text.lower()
|
41 |
+
words = text.split()
|
42 |
+
words_lower = text_lower.split()
|
43 |
+
lines = text.splitlines()
|
44 |
+
sentences = re.findall(r'[^.!?]+[.!?]', text)
|
45 |
+
return text_lower, words, words_lower, lines, sentences
|
46 |
+
|
47 |
+
def analyze(self, text: str) -> Dict[str, float]:
|
48 |
+
"""Analizuje pojedynczy tekst. Dobre do testów, mniej wydajne dla wielu tekstów."""
|
49 |
+
doc = self.nlp(text)
|
50 |
+
return self._analyze_single_doc(text, doc)
|
51 |
+
|
52 |
+
def _analyze_single_doc(self, text: str, doc: spacy.tokens.Doc) -> Dict[str, float]:
|
53 |
+
"""Wewnętrzna logika analizy dla pojedynczego tekstu i obiektu doc."""
|
54 |
+
if not isinstance(text, str) or not text.strip():
|
55 |
+
return {feature_name: 0.0 for feature_name in constants.COLUMN_ORDER}
|
56 |
+
|
57 |
+
text_lower, words, words_lower, lines, sentences = self._preprocess(text)
|
58 |
+
|
59 |
+
all_features = {}
|
60 |
+
all_features.update(base_features.calculate_all_base_features(text, text_lower, words, words_lower, lines))
|
61 |
+
all_features.update(linguistic_features.calculate_all_linguistic_features(text, text_lower, words, words_lower, sentences))
|
62 |
+
all_features.update(structural_features.calculate_all_structural_features(text, lines, sentences))
|
63 |
+
all_features.update(regex_features.calculate_all_regex_features(text))
|
64 |
+
all_features.update(spacy_features.calculate_all_spacy_features(doc, text, sentences))
|
65 |
+
|
66 |
+
return all_features
|
67 |
+
|
68 |
+
def analyze_batch(self, texts: Iterable[str], batch_size: int = 100) -> Iterable[Dict[str, float]]:
|
69 |
+
"""
|
70 |
+
Analizuje wsadowo kolekcję tekstów używając nlp.pipe dla maksymalnej wydajności.
|
71 |
+
|
72 |
+
Args:
|
73 |
+
texts (Iterable[str]): Kolekcja (np. lista) tekstów do analizy.
|
74 |
+
batch_size (int): Rozmiar paczki przekazywanej do spaCy.
|
75 |
+
|
76 |
+
Yields:
|
77 |
+
Iterable[Dict[str, float]]: Generator zwracający słownik cech dla każdego tekstu.
|
78 |
+
"""
|
79 |
+
# Używamy nlp.pipe, który jest generatorem i przetwarza teksty wsadowo
|
80 |
+
# as_tuples=True pozwala przekazać teksty razem z ich oryginalnym kontekstem (choć tu nie używamy)
|
81 |
+
docs = self.nlp.pipe(texts, batch_size=batch_size)
|
82 |
+
|
83 |
+
# Przetwarzamy każdy dokument z generatora
|
84 |
+
for i, doc in enumerate(docs):
|
85 |
+
original_text = texts[i] # Potrzebujemy oryginalnego tekstu
|
86 |
+
yield self._analyze_single_doc(original_text, doc)
|
text_analyzer/constants.py
ADDED
@@ -0,0 +1,284 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Moduł zawierający stałe, listy słów i prekompilowane wyrażenia regularne
|
3 |
+
używane w całej bibliotece do analizy tekstu.
|
4 |
+
"""
|
5 |
+
|
6 |
+
import re
|
7 |
+
|
8 |
+
BAD_WORDS = ['burdel', 'burdelmama', 'chuj', 'chujnia', 'ciota', 'cipa', 'cyc', 'debil', 'dmuchać', 'do kurwy nędzy',
|
9 |
+
'dupa', 'dupek', 'duperele', 'dziwka', 'fiut', 'gówno', 'gówno prawda', 'huj', 'huj ci w dupę', 'jajco',
|
10 |
+
'ja pierdolę', 'jebać', 'jebany', 'kurwa', 'kurwy', 'kutafon', 'kutas', 'lizać pałę', 'obciągać chuja',
|
11 |
+
'obciągać fiuta', 'pierdolec', 'pierdolić', 'pierdolnąć', 'pierdolnięty', 'pizda', 'pojeb', 'pojebany',
|
12 |
+
'popierdolony', 'robić loda', 'ruchać', 'rzygać', 'skurwysyn', 'sraczka', 'srać', 'suka', 'syf', 'wkurwiać', 'zajebisty']
|
13 |
+
|
14 |
+
STOP_WORDS = ["a","aby","ach","acz","aczkolwiek","aj","albo","ale","ależ","ani","aż","bardziej","bardzo","bez","bo","bowiem",
|
15 |
+
"by","byli","bym","bynajmniej","być","był","była","było","były","będzie","będą","cali","cała","cały","chce","choć","ci",
|
16 |
+
"ciebie","cię","co","cokolwiek","coraz","coś","czasami","czasem","czemu","czy","czyli","często","daleko","dla","dlaczego",
|
17 |
+
"dlatego","do","dobrze","dokąd","dość","dr","dużo","dwa","dwaj","dwie","dwoje","dzisiaj","dziś","gdy","gdyby","gdyż","gdzie",
|
18 |
+
"gdziekolwiek","gdzieś","go","godz","hab","i","ich","ii","iii","ile","im","inna","inne","inny","innych","inż","iv","ix","iż",
|
19 |
+
"ja","jak","jakaś","jakby","jaki","jakichś","jakie","jakiś","jakiż","jakkolwiek","jako","jakoś","je","jeden","jedna","jednak",
|
20 |
+
"jednakże","jedno","jednym","jedynie","jego","jej","jemu","jest","jestem","jeszcze","jeśli","jeżeli","już","ją","każdy","kiedy",
|
21 |
+
"kierunku","kilka","kilku","kimś","kto","ktokolwiek","ktoś","która","które","którego","której","który","których","którym","którzy",
|
22 |
+
"ku","lat","lecz","lub","ma","mają","mam","mamy","mało","mgr","mi","miał","mimo","między","mnie","mną","mogą","moi","moim","moja",
|
23 |
+
"moje","może","możliwe","można","mu","musi","my","mój","na","nad","nam","nami","nas","nasi","nasz","nasza","nasze","naszego","naszych",
|
24 |
+
"natomiast","natychmiast","nawet","nic","nich","nie","niech","niego","niej","niemu","nigdy","nim","nimi","nią","niż","no","nowe","np",
|
25 |
+
"nr","o","o.o.","obok","od","ok","około","on","ona","one","oni","ono","oraz","oto","owszem","pan","pana","pani","pl","po","pod",
|
26 |
+
"podczas","pomimo","ponad","ponieważ","powinien","powinna","powinni","powinno","poza","prawie","prof","przecież","przed","przede","przedtem",
|
27 |
+
"przez","przy","raz","razie","roku","również","sam","sama","się","skąd","sobie","sobą","sposób","swoje","są","ta","tak","taka","taki","takich",
|
28 |
+
"takie","także","tam","te","tego","tej","tel","temu","ten","teraz","też","to","tobie","tobą","toteż","totobą","trzeba","tu","tutaj","twoi","twoim",
|
29 |
+
"twoja","twoje","twym","twój","ty","tych","tylko","tym","tys","tzw","u","ul","vi","vii","viii","vol","w","wam","wami","was","wasi","wasz","wasza",
|
30 |
+
"wasze","we","według","wie","wiele","wielu","więc","więcej","wszyscy","wszystkich","wszystkie","wszystkim","wszystko","wtedy","www","wy","właśnie",
|
31 |
+
"wśród","xi","xii","xiii","xiv","xv","z","za","zapewne","zawsze","zaś","ze","zeznowu","znowu","znów","został","zł","żaden","żadna","żadne","żadnych",
|
32 |
+
"że","żeby"]
|
33 |
+
|
34 |
+
PII_REGEX_PATTERNS = {
|
35 |
+
'date_reg' : re.compile(r'(?<!\w)([1-2]\d{3}[\/.\\-](12|11|[0-1]?[0-9])[\/.\\-](3[0-1]|2[0-9]|1[0-9]|0?[1-9]))|((?<!\w)(3[0-1]|2[0-9]|1[0-9]|0?[1-9])([\/.\\-](12|11|1[0-9]|0?[1-9])|([\/.\\ -](stycz\w+|lut\w+|mar[cz]\w+|kwie\w+|maj\w|czerw\w+|lip\w+|sierp\w+|wrze[sś]\w+|paź\w+|listop\w+|grud\w+)))(([\/.\\ -]([0-9]){2}|[\/.\\ -]([1-2][0-9]{3})))?)(?![\d])'),
|
36 |
+
'address_reg' : re.compile(r"(?<=\W)(?:ul\.|pl\.|al\.|bulw\.|os\.|aleja|bulwar|ulica|skwer|park|rondo)[^\S\r\n]?"
|
37 |
+
r"(?:(?:świętego|trasa|bł\.|im\.|gen\.|dr\.|ks\.|ks\.[^\S\r\n]bp\.|kpt\.|ks\.[^\S\r\n]kan\.|"
|
38 |
+
r"ks\.[^\S\r\n]+kard\.|marsz\.|mjr\.|o\.|ppłk\.|prof\.|św\.|[^\S\r\n])*)"
|
39 |
+
r"(?:(?:\d{1,3}\s+)?[A-ZĄĆĘŁŃÓŚŻŹ][\w'-]+(?:\s*[A-ZĄĆĘŁŃÓŚŻŹ][\w'-]+)*"
|
40 |
+
r"(?:\s*\d{1,3})*){1,}(?:(?:[^\S\r\n]+(?:i|z|im\.)[^\S\r\n]+)?"
|
41 |
+
r"(?:[^\S\r\n]*(?:\d{1,3}[^\S\r\n]+)?[A-ZĄĆĘŁŃÓŚŻŹ][\w'-]+(?:\s*[A-ZĄĆĘŁŃÓŚŻŹ][\w'-]+)*"
|
42 |
+
r"(?:\s*\d{1,3})*)*)*(?!\n\d{2}-\d{3})"),
|
43 |
+
'post_code_reg' : re.compile(r'(?<=\s)([0-9]{2}-[0-9]{3})(?!\w)'),
|
44 |
+
'ip_reg' : re.compile(r'(?<=\s)((25[0-5]|2[0-4][0-9]|\d?[1-9][0-9]|\d)(\.|$)){3}(25[0-5]|2[0-4][0-9]|\d?[1-9][0-9]|\d)(?!:\s)'),
|
45 |
+
'nip_reg' : re.compile(r'(?:(?<=NIP: )|(?<=NIP ))([0-9]{10}|\d{3}-\d{2}-\d{2}-\d{3}|\d{3}-\d{3}-\d{2}-\d{2})(?!\w)'),
|
46 |
+
'regon_reg' : re.compile(r'(?:(?<=REGON: )|(?<=REGON ))([0-9]{9})(?!\w)'),
|
47 |
+
'phone_reg' : re.compile(r'(?<=\s)((?P<a>\()?(\+|00)?48(?(2)\)|)?)?(\s{0,}(4|5|6|7|8)\d{2})((?P<char>[ -]?)\d{3})((?P=char)\d{3})(?!\w)'),
|
48 |
+
'domestic_phone_reg' : re.compile(r'(?!\s)((?P<a>\()?(\+|00)?48(?(2)\)|)?)?(\s*\d{2})?((\s*[2-9]\d{2})((?P<char>[ -]+)\d{2})((?P=char)\d{2})|(\s*[2-9]\d{1})((?P<char2>[ -]+)\d{3})((?P=char2)\d{2}))\s'),
|
49 |
+
'iban_reg' : re.compile(r'(?<=\s)(?:PL)?(?:\d{2}(?:[\s]*\d{4}){6})(?!\w)'),
|
50 |
+
'email_reg' : re.compile(r'(?<=\s)([a-zA-Z0-9][\w.+-]*@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]{2,20})(?!\w)'),
|
51 |
+
'pesel_reg' : re.compile(r'(?<=\s)(\d{2})(1[0-2]|2[1-9]|3[1-2]|0[1-9])(3[0-1]|2\d|1\d|0[1-9])(\d{5})(?!\w)'),
|
52 |
+
'currency_reg' : re.compile(r'(?<=\s)(([1-9]\d{0,2}[,. ]){0,2}[,. ]?([1-9]\d{1,2}|\d{1,3})([,.]\d{2}|\d+)\s*(zł|Zł|pln|PLN|\$|€|£)|(\$|€|£)\s*([1-9]\d{0,2}[,. ]){0,2}[,. ]?([1-9]\d{1,2}|\d{1,3})([,.]\d{2}|\d+))'),
|
53 |
+
'isbn_reg' : re.compile(r'(?<=\s)978(?:-?\d){10}(?!\w)'),
|
54 |
+
'issn_reg' : re.compile(r'(?<=\s)((ISSN|eISSN) [\S]{4}\-[\S]{4})(?!\w)')
|
55 |
+
}
|
56 |
+
|
57 |
+
MARKDOWN_PATTERNS = {
|
58 |
+
'header': re.compile(r'^#+\s', re.MULTILINE),
|
59 |
+
'bold': re.compile(r'\*\*[^*\n]+?\*\*'),
|
60 |
+
'italic': re.compile(r'\*[^*\n]+?\*'),
|
61 |
+
'unordered_list': re.compile(r'^\s*[-*+]\s', re.MULTILINE),
|
62 |
+
'ordered_list': re.compile(r'^\s*\d+\.\s', re.MULTILINE),
|
63 |
+
'link': re.compile(r'\[([^\]]+)\]\(([^\)]+)\)'),
|
64 |
+
'image': re.compile(r'!\[([^\]]*)\]\(([^\)]+)\)'),
|
65 |
+
'inline_code': re.compile(r'`[^`\n]+`'),
|
66 |
+
'code_block': re.compile(r'```[\s\S]*?```'),
|
67 |
+
'blockquote': re.compile(r'^>\s', re.MULTILINE),
|
68 |
+
'horizontal_rule': re.compile(r'^([-*_])\1{2,}$', re.MULTILINE)
|
69 |
+
}
|
70 |
+
|
71 |
+
PUNCTUATION_PATTERN = re.compile(r'[.,!?;:]')
|
72 |
+
NON_WORD_CHARS_PATTERN = re.compile(r'[^\w\s]')
|
73 |
+
EXCESSIVE_SPACES_PATTERN = re.compile(r' {4,}')
|
74 |
+
CAMEL_CASE_PATTERN = re.compile(r"\b[a-ząęćłńóśżź]+[A-ZĄĘĆŁŃÓŚŻŹ]+[a-ząęćłńóśżź]+[a-ząęćłńóśżźA-ZĄĘĆŁŃÓŚŻŹ]*\b")
|
75 |
+
ALLOWED_CHARS_PATTERN = re.compile(r'[a-zA-Z0-9ąćęłńóśźż\s.,;:\-!?]')
|
76 |
+
COMMON_CHARACTERS = list(" \t\n!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^`°abcdefghijklmnopqrstuvwxyz{|}~ÓóĄąĆćĘꣳŃńŚśŹźŻż")
|
77 |
+
SPACY_MODEL_PL = 'pl_core_news_md'
|
78 |
+
NLP_MAX_LENGTH = 5_000_000
|
79 |
+
|
80 |
+
|
81 |
+
COLUMN_ORDER = [
|
82 |
+
'characters',
|
83 |
+
'words',
|
84 |
+
'sentences',
|
85 |
+
'avg_sentence_length',
|
86 |
+
'nouns',
|
87 |
+
'verbs',
|
88 |
+
'adjectives',
|
89 |
+
'adverbs',
|
90 |
+
'punctuations',
|
91 |
+
'symbols',
|
92 |
+
'stopwords',
|
93 |
+
'oovs',
|
94 |
+
'avg_word_length',
|
95 |
+
'noun_ratio',
|
96 |
+
'verb_ratio',
|
97 |
+
'adj_ratio',
|
98 |
+
'lexical_density',
|
99 |
+
'gunning_fog',
|
100 |
+
'camel_case',
|
101 |
+
'pos_x',
|
102 |
+
'pos_num',
|
103 |
+
'capitalized_words',
|
104 |
+
'unique_characters_all',
|
105 |
+
'unique_characters_lower',
|
106 |
+
'characters_out_of_common',
|
107 |
+
'word_isupper<5',
|
108 |
+
'word_isupper>5',
|
109 |
+
'count_caps',
|
110 |
+
'single_char_count',
|
111 |
+
'single_char_ratio',
|
112 |
+
'digit_count',
|
113 |
+
'digit_ratio',
|
114 |
+
'punct_frequency',
|
115 |
+
'count_digit_to_caps',
|
116 |
+
'bracet_count',
|
117 |
+
'bracket_ratio',
|
118 |
+
'average_lines',
|
119 |
+
'short_line_count_3',
|
120 |
+
'short_line_count_5',
|
121 |
+
'short_line_count_10',
|
122 |
+
'short_line_ratio_3',
|
123 |
+
'short_line_ratio_5',
|
124 |
+
'short_line_ratio_10',
|
125 |
+
'lexical_diversity',
|
126 |
+
'contextual_word_repetitions_count',
|
127 |
+
'contextual_word_repetitions_ratio',
|
128 |
+
'html_tags',
|
129 |
+
'bbcode_tags',
|
130 |
+
'urls',
|
131 |
+
'text_to_markup_ratio',
|
132 |
+
'emoticons',
|
133 |
+
'slang_words',
|
134 |
+
'slang_words_ratio',
|
135 |
+
'incomplete_sentences',
|
136 |
+
'excessive_chars',
|
137 |
+
'blank_lines',
|
138 |
+
'blank_lines_ratio',
|
139 |
+
'duplicated_lines',
|
140 |
+
'duplicate_line_ratio',
|
141 |
+
'count_special_chars',
|
142 |
+
'tabs',
|
143 |
+
'multispaces',
|
144 |
+
'short_line_count_20',
|
145 |
+
'date_reg',
|
146 |
+
'address_reg',
|
147 |
+
'post_code_reg',
|
148 |
+
'ip_reg',
|
149 |
+
'nip_reg',
|
150 |
+
'regon_reg',
|
151 |
+
'phone_reg',
|
152 |
+
'iban_reg',
|
153 |
+
'email_reg',
|
154 |
+
'pesel_reg',
|
155 |
+
'currency_reg',
|
156 |
+
'isbn_reg',
|
157 |
+
'issn_reg',
|
158 |
+
'short_line_ratio_20',
|
159 |
+
'ellipsis_fractions',
|
160 |
+
'line_counts',
|
161 |
+
'non_alpha_word_fractions',
|
162 |
+
'lorem_ipsum_ratio',
|
163 |
+
'mean_word_length',
|
164 |
+
'stop_word_ratio',
|
165 |
+
'entropy',
|
166 |
+
'javascript_counts_per_line',
|
167 |
+
'lines_with_bullet',
|
168 |
+
'ratio_of_bulletpoints',
|
169 |
+
'overall_uppercase_ratio',
|
170 |
+
'bad_word_count',
|
171 |
+
'fraction_duplicate_5_ngram',
|
172 |
+
'fraction_duplicate_6_ngram',
|
173 |
+
'fraction_duplicate_7_ngram',
|
174 |
+
'fraction_duplicate_8_ngram',
|
175 |
+
'fraction_duplicate_9_ngram',
|
176 |
+
'fraction_duplicate_10_ngram',
|
177 |
+
'fraction_top_2_ngram',
|
178 |
+
'fraction_top_3_ngram',
|
179 |
+
'fraction_top_4_ngram',
|
180 |
+
'fraction_top_5_ngram',
|
181 |
+
'symbol_to_word_ratio',
|
182 |
+
'avg_paragraph_length',
|
183 |
+
'paragraph_length_variance',
|
184 |
+
'unique_sentence_beginnings_ratio',
|
185 |
+
'semicolons_per_sentence',
|
186 |
+
'dashes_per_sentence',
|
187 |
+
'colons_per_sentence',
|
188 |
+
'formal_words_ratio',
|
189 |
+
'commas_per_sentence',
|
190 |
+
'short_sentences_ratio',
|
191 |
+
'long_sentences_ratio',
|
192 |
+
'cohesive_words_per_sentence',
|
193 |
+
'quotes_and_references_per_sentence',
|
194 |
+
'headers_per_1000_chars_md',
|
195 |
+
'average_header_level_md',
|
196 |
+
'bold_per_1000_chars_md',
|
197 |
+
'italic_per_1000_chars_md',
|
198 |
+
'unordered_list_items_per_1000_chars_md',
|
199 |
+
'ordered_list_items_per_1000_chars_md',
|
200 |
+
'links_per_1000_chars_md',
|
201 |
+
'images_per_1000_chars_md',
|
202 |
+
'inline_code_fragments_per_1000_chars_md',
|
203 |
+
'code_blocks_per_1000_chars_md',
|
204 |
+
'blockquotes_per_1000_chars_md',
|
205 |
+
'horizontal_rules_per_1000_chars_md',
|
206 |
+
'char_ratio_#',
|
207 |
+
'char_ratio_*',
|
208 |
+
'char_ratio_-',
|
209 |
+
'char_ratio_+',
|
210 |
+
'char_ratio_[',
|
211 |
+
'char_ratio_]',
|
212 |
+
'char_ratio_(',
|
213 |
+
'char_ratio_)',
|
214 |
+
'char_ratio_`',
|
215 |
+
'char_ratio_>',
|
216 |
+
'char_ratio__',
|
217 |
+
'char_ratio_!',
|
218 |
+
'special_chars_ratio_md',
|
219 |
+
'lowercase_ratio_md',
|
220 |
+
'uppercase_ratio_md',
|
221 |
+
'digit_ratio_md',
|
222 |
+
'whitespace_ratio_md',
|
223 |
+
'table_pipe_count',
|
224 |
+
'table_pipe_ratio',
|
225 |
+
'table_pipe_per_1000_chars',
|
226 |
+
'table_lines_count',
|
227 |
+
'table_lines_ratio',
|
228 |
+
'table_header_separators_count',
|
229 |
+
'avg_pipes_per_table_line',
|
230 |
+
'estimated_avg_columns',
|
231 |
+
'word_count',
|
232 |
+
'unique_word_count',
|
233 |
+
'top_word_count',
|
234 |
+
'top_word_ratio',
|
235 |
+
'top_5_ratio',
|
236 |
+
'top_10_ratio',
|
237 |
+
'hapax_legomena_ratio',
|
238 |
+
'looping_suspicion',
|
239 |
+
'polish_diacritics_count',
|
240 |
+
'polish_diacritics_ratio',
|
241 |
+
'polish_diacritics_per_word',
|
242 |
+
'diacritics_to_letters_ratio',
|
243 |
+
'replacement_char_count',
|
244 |
+
'replacement_char_ratio',
|
245 |
+
'not_allowed_chars_count',
|
246 |
+
'not_allowed_chars_ratio',
|
247 |
+
'encoding_suspicion',
|
248 |
+
'single_char_word_count',
|
249 |
+
'single_char_unique_count',
|
250 |
+
'single_char_upper_count',
|
251 |
+
'single_char_lower_count',
|
252 |
+
'single_char_upper_unique_count',
|
253 |
+
'single_char_lower_unique_count',
|
254 |
+
'single_char_top_1_codepoint',
|
255 |
+
'single_char_top_2_codepoint',
|
256 |
+
'single_char_top_3_codepoint',
|
257 |
+
'question_sentence_ratio',
|
258 |
+
'single_word_line_ratio',
|
259 |
+
'repeated_word_line_ratio',
|
260 |
+
'lix',
|
261 |
+
'rix',
|
262 |
+
'diacritics_std_dev',
|
263 |
+
'ner_count',
|
264 |
+
'ner_person_ratio',
|
265 |
+
'ner_org_ratio',
|
266 |
+
'ner_loc_ratio',
|
267 |
+
'ner_misc_ratio',
|
268 |
+
'case_diversity',
|
269 |
+
'tense_diversity',
|
270 |
+
'mood_diversity',
|
271 |
+
'top_words_total_count',
|
272 |
+
'top_words_noun_ratio',
|
273 |
+
'top_words_verb_ratio',
|
274 |
+
'top_words_adj_ratio',
|
275 |
+
'top_words_other_ratio',
|
276 |
+
'top_words_noun_prop_of_all_nouns',
|
277 |
+
'top_words_verb_prop_of_all_verbs',
|
278 |
+
'top_words_adj_prop_of_all_adjs',
|
279 |
+
'top_words_other_prop_of_all_others',
|
280 |
+
'avg_dependency_tree_depth',
|
281 |
+
'digit_start_lines'
|
282 |
+
]
|
283 |
+
|
284 |
+
|
text_analyzer/features/__pycache__/base_features.cpython-311.pyc
ADDED
Binary file (18.9 kB). View file
|
|
text_analyzer/features/__pycache__/base_features.cpython-312.pyc
ADDED
Binary file (17.8 kB). View file
|
|
text_analyzer/features/__pycache__/linguistic_features.cpython-311.pyc
ADDED
Binary file (14.7 kB). View file
|
|
text_analyzer/features/__pycache__/linguistic_features.cpython-312.pyc
ADDED
Binary file (10.2 kB). View file
|
|
text_analyzer/features/__pycache__/regex_features.cpython-311.pyc
ADDED
Binary file (2.46 kB). View file
|
|
text_analyzer/features/__pycache__/regex_features.cpython-312.pyc
ADDED
Binary file (2 kB). View file
|
|
text_analyzer/features/__pycache__/spacy_features.cpython-311.pyc
ADDED
Binary file (16.1 kB). View file
|
|
text_analyzer/features/__pycache__/spacy_features.cpython-312.pyc
ADDED
Binary file (17.7 kB). View file
|
|
text_analyzer/features/__pycache__/structural_features.cpython-311.pyc
ADDED
Binary file (10.7 kB). View file
|
|
text_analyzer/features/__pycache__/structural_features.cpython-312.pyc
ADDED
Binary file (11.3 kB). View file
|
|
text_analyzer/features/base_features.py
ADDED
@@ -0,0 +1,243 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Moduł do ekstrakcji podstawowych, statystycznych cech tekstu.
|
3 |
+
|
4 |
+
Zawiera funkcje do analizy na poziomie znaków, słów i linii, które nie
|
5 |
+
wymagają zaawansowanych modeli lingwistycznych.
|
6 |
+
"""
|
7 |
+
import re
|
8 |
+
from collections import Counter
|
9 |
+
from typing import Dict, List
|
10 |
+
|
11 |
+
from ..utils import safe_divide
|
12 |
+
from ..constants import (PUNCTUATION_PATTERN, EXCESSIVE_SPACES_PATTERN,
|
13 |
+
ALLOWED_CHARS_PATTERN, COMMON_CHARACTERS)
|
14 |
+
|
15 |
+
# --- Funkcje analizujące znaki ---
|
16 |
+
|
17 |
+
def analyze_character_stats(text: str, text_lower: str) -> Dict[str, float]:
|
18 |
+
"""Oblicza podstawowe statystyki na poziomie znaków."""
|
19 |
+
total_chars = len(text)
|
20 |
+
char_counts = Counter(text)
|
21 |
+
|
22 |
+
if not total_chars:
|
23 |
+
return {
|
24 |
+
'characters': 0, 'digit_count': 0, 'digit_ratio': 0.0,
|
25 |
+
'overall_uppercase_ratio': 0.0, 'unique_characters_all': 0,
|
26 |
+
'unique_characters_lower': 0, 'characters_out_of_common': 0,
|
27 |
+
'tabs': 0, 'multispaces': 0
|
28 |
+
}
|
29 |
+
|
30 |
+
return {
|
31 |
+
'characters': total_chars,
|
32 |
+
'digit_count': sum(ch.isdigit() for ch in text),
|
33 |
+
'digit_ratio': safe_divide(sum(ch.isdigit() for ch in text), total_chars),
|
34 |
+
'overall_uppercase_ratio': safe_divide(sum(ch.isupper() for ch in text), total_chars),
|
35 |
+
'unique_characters_all': len(set(text)),
|
36 |
+
'unique_characters_lower': len(set(text_lower)),
|
37 |
+
'characters_out_of_common': len([c for c in text if c not in COMMON_CHARACTERS]),
|
38 |
+
'tabs': text.count('\t'),
|
39 |
+
'multispaces': len(EXCESSIVE_SPACES_PATTERN.findall(text))
|
40 |
+
}
|
41 |
+
|
42 |
+
def analyze_punctuation_stats(text: str) -> Dict[str, float]:
|
43 |
+
"""Analizuje występowanie interpunkcji i specyficznych znaków."""
|
44 |
+
total_chars = len(text)
|
45 |
+
if not total_chars:
|
46 |
+
return {
|
47 |
+
'punct_frequency': 0.0, 'bracet_count': 0, 'bracket_ratio': 0.0,
|
48 |
+
'count_special_chars': 0
|
49 |
+
}
|
50 |
+
|
51 |
+
open_paren = text.count('(')
|
52 |
+
close_paren = text.count(')')
|
53 |
+
open_bracket = text.count('[')
|
54 |
+
close_bracket = text.count(']')
|
55 |
+
|
56 |
+
return {
|
57 |
+
'punct_frequency': safe_divide(len(PUNCTUATION_PATTERN.findall(text)), total_chars),
|
58 |
+
'bracet_count': open_paren + close_paren + open_bracket + close_bracket,
|
59 |
+
'bracket_ratio': safe_divide(open_bracket, close_bracket),
|
60 |
+
'count_special_chars': len(re.findall(r'(\?|!){3,}', text))
|
61 |
+
}
|
62 |
+
|
63 |
+
def analyze_advanced_char_features(text: str) -> Dict[str, float]:
|
64 |
+
"""Analizuje zaawansowane cechy rozkładu znaków i słów (dawniej analyze_char_features)."""
|
65 |
+
total_chars = len(text)
|
66 |
+
words_found = re.findall(r'\w+', text)
|
67 |
+
word_count = len(words_found)
|
68 |
+
|
69 |
+
if not total_chars or not word_count:
|
70 |
+
return {
|
71 |
+
'word_count': 0, 'unique_word_count': 0, 'top_word_count': 0, 'top_word_ratio': 0.0,
|
72 |
+
'top_5_ratio': 0.0, 'top_10_ratio': 0.0, 'hapax_legomena_ratio': 0.0,
|
73 |
+
'looping_suspicion': 0, 'polish_diacritics_count': 0, 'polish_diacritics_ratio': 0.0,
|
74 |
+
'polish_diacritics_per_word': 0.0, 'diacritics_to_letters_ratio': 0.0,
|
75 |
+
'replacement_char_count': 0, 'replacement_char_ratio': 0.0,
|
76 |
+
'not_allowed_chars_count': 0, 'not_allowed_chars_ratio': 0.0,
|
77 |
+
'encoding_suspicion': 0, 'single_char_word_count': 0, 'single_char_unique_count': 0,
|
78 |
+
'single_char_upper_count': 0, 'single_char_lower_count': 0,
|
79 |
+
'single_char_upper_unique_count': 0, 'single_char_lower_unique_count': 0,
|
80 |
+
'single_char_top_1_codepoint': 0, 'single_char_top_2_codepoint': 0,
|
81 |
+
'single_char_top_3_codepoint': 0
|
82 |
+
}
|
83 |
+
|
84 |
+
word_freq = Counter(words_found)
|
85 |
+
most_common = word_freq.most_common(10)
|
86 |
+
|
87 |
+
# Polish diacritics
|
88 |
+
polish_diacritics = 'ąćęłńóśźżĄĆĘŁŃÓŚŹŻ'
|
89 |
+
char_counts = Counter(text)
|
90 |
+
diac_count = sum(char_counts.get(ch, 0) for ch in polish_diacritics)
|
91 |
+
letters_count = sum(1 for ch in text if ch.isalpha())
|
92 |
+
|
93 |
+
# Single char words
|
94 |
+
single_chars = [w for w in words_found if len(w) == 1]
|
95 |
+
single_char_freq = Counter(single_chars)
|
96 |
+
top_3_single = single_char_freq.most_common(3)
|
97 |
+
top_codes = [ord(w) for w, _ in top_3_single]
|
98 |
+
while len(top_codes) < 3: top_codes.append(0)
|
99 |
+
|
100 |
+
# Encoding
|
101 |
+
replacement_count = char_counts.get('\uFFFD', 0)
|
102 |
+
not_allowed_count = sum(1 for ch in text if not ALLOWED_CHARS_PATTERN.match(ch))
|
103 |
+
replacement_ratio = safe_divide(replacement_count, total_chars)
|
104 |
+
not_allowed_ratio = safe_divide(not_allowed_count, total_chars)
|
105 |
+
|
106 |
+
top_word_ratio = safe_divide(most_common[0][1] if most_common else 0, word_count)
|
107 |
+
top_5_ratio = safe_divide(sum(cnt for _, cnt in most_common[:5]), word_count)
|
108 |
+
|
109 |
+
features = {
|
110 |
+
'word_count': word_count,
|
111 |
+
'unique_word_count': len(word_freq),
|
112 |
+
'top_word_count': most_common[0][1] if most_common else 0,
|
113 |
+
'top_word_ratio': top_word_ratio,
|
114 |
+
'top_5_ratio': top_5_ratio,
|
115 |
+
'top_10_ratio': safe_divide(sum(cnt for _, cnt in most_common[:10]), word_count),
|
116 |
+
'hapax_legomena_ratio': safe_divide(sum(1 for cnt in word_freq.values() if cnt == 1), word_count),
|
117 |
+
'looping_suspicion': 1 if (top_word_ratio > 0.15 or top_5_ratio > 0.4) else 0,
|
118 |
+
'polish_diacritics_count': diac_count,
|
119 |
+
'polish_diacritics_ratio': safe_divide(diac_count, total_chars),
|
120 |
+
'polish_diacritics_per_word': safe_divide(diac_count, word_count),
|
121 |
+
'diacritics_to_letters_ratio': safe_divide(diac_count, letters_count),
|
122 |
+
'replacement_char_count': replacement_count,
|
123 |
+
'replacement_char_ratio': replacement_ratio,
|
124 |
+
'not_allowed_chars_count': not_allowed_count,
|
125 |
+
'not_allowed_chars_ratio': not_allowed_ratio,
|
126 |
+
'encoding_suspicion': 1 if (replacement_ratio > 0.01 or not_allowed_ratio > 0.05) else 0,
|
127 |
+
'single_char_word_count': len(single_chars),
|
128 |
+
'single_char_unique_count': len(single_char_freq),
|
129 |
+
'single_char_upper_count': sum(1 for w in single_chars if w.isupper()),
|
130 |
+
'single_char_lower_count': sum(1 for w in single_chars if w.islower()),
|
131 |
+
'single_char_upper_unique_count': len({w for w in single_chars if w.isupper()}),
|
132 |
+
'single_char_lower_unique_count': len({w for w in single_chars if w.islower()}),
|
133 |
+
'single_char_top_1_codepoint': top_codes[0],
|
134 |
+
'single_char_top_2_codepoint': top_codes[1],
|
135 |
+
'single_char_top_3_codepoint': top_codes[2],
|
136 |
+
}
|
137 |
+
return features
|
138 |
+
|
139 |
+
# --- Funkcje analizujące słowa ---
|
140 |
+
|
141 |
+
def analyze_word_stats(words: List[str], words_lower: List[str]) -> Dict[str, float]:
|
142 |
+
total_words = len(words)
|
143 |
+
if not total_words: return {'mean_word_length': 0.0, 'lexical_diversity': 0.0, 'count_caps': 0.0, 'word_isupper<5': 0, 'word_isupper>5': 0, 'count_digit_to_caps': 0.0}
|
144 |
+
|
145 |
+
digit_count = sum(1 for w in words if any(ch.isdigit() for ch in w))
|
146 |
+
caps_count = sum(1 for w in words if w.isupper()) # Używamy 'words' z oryginalną wielkością
|
147 |
+
|
148 |
+
return {
|
149 |
+
'mean_word_length': safe_divide(sum(len(w) for w in words_lower), total_words),
|
150 |
+
'lexical_diversity': safe_divide(len(set(words_lower)), total_words),
|
151 |
+
'count_caps': safe_divide(caps_count, total_words),
|
152 |
+
'word_isupper<5': sum(1 for w in words if w.isupper() and len(w) < 5),
|
153 |
+
'word_isupper>5': sum(1 for w in words if w.isupper() and len(w) >= 5),
|
154 |
+
'count_digit_to_caps': safe_divide(digit_count, caps_count)
|
155 |
+
}
|
156 |
+
|
157 |
+
def count_contextual_word_repetitions(words_lower: List[str]) -> Dict[str, float]:
|
158 |
+
"""Liczy powtórzenia tego samego słowa bezpośrednio po sobie."""
|
159 |
+
count = sum(1 for i in range(len(words_lower) - 1) if words_lower[i] == words_lower[i+1])
|
160 |
+
return {
|
161 |
+
"contextual_word_repetitions_count": count,
|
162 |
+
"contextual_word_repetitions_ratio": safe_divide(count, len(words_lower))
|
163 |
+
}
|
164 |
+
|
165 |
+
def count_single_chars_and_ratio(text: str) -> Dict[str, float]:
|
166 |
+
"""Liczy słowa składające się z jednego znaku (wersja z oryginalnego kodu)."""
|
167 |
+
t = " " + text + " "
|
168 |
+
count = sum(1 for i in range(1, len(t) - 1) if t[i-1].isspace() and t[i+1].isspace())
|
169 |
+
return {
|
170 |
+
'single_char_count': count,
|
171 |
+
'single_char_ratio': safe_divide(count, len(t))
|
172 |
+
}
|
173 |
+
|
174 |
+
# --- Funkcje analizujące linie ---
|
175 |
+
|
176 |
+
def analyze_line_length_stats(lines: List[str]) -> Dict[str, float]:
|
177 |
+
"""Oblicza statystyki związane z długością linii."""
|
178 |
+
total_lines = len(lines)
|
179 |
+
if not total_lines:
|
180 |
+
return {
|
181 |
+
'average_lines': 0.0, 'short_line_count_3': 0,
|
182 |
+
'short_line_count_5': 0, 'short_line_count_10': 0, 'short_line_count_20': 0,
|
183 |
+
'short_line_ratio_3': 0.0, 'short_line_ratio_5': 0.0,
|
184 |
+
'short_line_ratio_10': 0.0, 'short_line_ratio_20': 0.0
|
185 |
+
}
|
186 |
+
|
187 |
+
line_lengths = [len(line) for line in lines]
|
188 |
+
stats = {'average_lines': safe_divide(sum(line_lengths), total_lines)}
|
189 |
+
|
190 |
+
for threshold in [3, 5, 10, 20]:
|
191 |
+
count = sum(1 for length in line_lengths if length < threshold)
|
192 |
+
stats[f'short_line_count_{threshold}'] = count
|
193 |
+
stats[f'short_line_ratio_{threshold}'] = safe_divide(count, total_lines)
|
194 |
+
return stats
|
195 |
+
|
196 |
+
def analyze_line_content(lines: List[str]) -> Dict[str, float]:
|
197 |
+
"""Analizuje zawartość linii pod kątem specyficznych wzorców."""
|
198 |
+
total_lines = len(lines)
|
199 |
+
if not total_lines:
|
200 |
+
return {
|
201 |
+
'blank_lines': 0, 'blank_lines_ratio': 0.0,
|
202 |
+
'ellipsis_fractions': 0.0, 'line_counts': 0,
|
203 |
+
'digit_start_lines': 0, 'duplicated_lines': 0, 'duplicate_line_ratio': 0.0
|
204 |
+
}
|
205 |
+
|
206 |
+
non_empty_lines = [line for line in lines if line.strip()]
|
207 |
+
blanks_count = total_lines - len(non_empty_lines)
|
208 |
+
ellipsis_lines_count = sum(1 for line in lines if line.strip().endswith(('...', '…')))
|
209 |
+
digit_start_lines_count = sum(1 for line in non_empty_lines if line.strip() and line.strip()[0].isdigit())
|
210 |
+
|
211 |
+
line_counts = Counter(non_empty_lines)
|
212 |
+
duplicated_lines_count = sum(cnt - 1 for cnt in line_counts.values() if cnt > 1)
|
213 |
+
|
214 |
+
return {
|
215 |
+
'blank_lines': blanks_count,
|
216 |
+
'blank_lines_ratio': safe_divide(blanks_count, total_lines),
|
217 |
+
'ellipsis_fractions': safe_divide(ellipsis_lines_count, total_lines),
|
218 |
+
'line_counts': total_lines,
|
219 |
+
'digit_start_lines': digit_start_lines_count,
|
220 |
+
'duplicated_lines': duplicated_lines_count,
|
221 |
+
'duplicate_line_ratio': safe_divide(duplicated_lines_count, len(non_empty_lines))
|
222 |
+
}
|
223 |
+
|
224 |
+
def count_lorem_ipsum(text_lower: str) -> Dict[str, float]:
|
225 |
+
"""Oblicza stosunek "lorem ipsum"."""
|
226 |
+
count = text_lower.count('lorem ipsum')
|
227 |
+
return {'lorem_ipsum_ratio': safe_divide(count, len(text_lower))}
|
228 |
+
|
229 |
+
# --- Główna funkcja agregująca ---
|
230 |
+
|
231 |
+
def calculate_all_base_features(text: str, text_lower: str, words: List[str], words_lower: List[str], lines: List[str]) -> Dict[str, float]:
|
232 |
+
"""Agreguje wszystkie podstawowe cechy tekstu z tego modułu."""
|
233 |
+
features = {}
|
234 |
+
features.update(analyze_character_stats(text, text_lower))
|
235 |
+
features.update(analyze_punctuation_stats(text))
|
236 |
+
features.update(analyze_advanced_char_features(text))
|
237 |
+
features.update(analyze_word_stats(words, words_lower))
|
238 |
+
features.update(count_contextual_word_repetitions(words_lower))
|
239 |
+
features.update(count_single_chars_and_ratio(text))
|
240 |
+
features.update(analyze_line_length_stats(lines))
|
241 |
+
features.update(analyze_line_content(lines))
|
242 |
+
features.update(count_lorem_ipsum(text_lower))
|
243 |
+
return features
|
text_analyzer/features/linguistic_features.py
ADDED
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# text_analyzer/features/linguistic_features.py
|
2 |
+
"""
|
3 |
+
Moduł do ekstrakcji cech lingwistycznych i stylistycznych tekstu.
|
4 |
+
"""
|
5 |
+
import math
|
6 |
+
import re
|
7 |
+
from collections import Counter
|
8 |
+
from typing import Dict, List
|
9 |
+
|
10 |
+
from ..utils import safe_divide
|
11 |
+
from ..constants import STOP_WORDS, BAD_WORDS, NON_WORD_CHARS_PATTERN
|
12 |
+
|
13 |
+
# --- Funkcje analizujące leksykę (słownictwo) ---
|
14 |
+
|
15 |
+
def calculate_stop_word_ratio(words_lower: List[str]) -> Dict[str, float]:
|
16 |
+
"""Oblicza stosunek stop-words do wszystkich słów."""
|
17 |
+
total_words = len(words_lower)
|
18 |
+
stop_word_count = sum(1 for w in words_lower if w in STOP_WORDS)
|
19 |
+
return {'stop_word_ratio': safe_divide(stop_word_count, total_words)}
|
20 |
+
|
21 |
+
def count_bad_words(words_lower: List[str]) -> Dict[str, int]:
|
22 |
+
"""Liczy wystąpienia wulgaryzmów."""
|
23 |
+
return {'bad_word_count': sum(1 for w in words_lower if w in BAD_WORDS)}
|
24 |
+
|
25 |
+
def calculate_unigram_entropy(words_lower: List[str]) -> Dict[str, float]:
|
26 |
+
"""Oblicza entropię rozkładu unigramów (słów)."""
|
27 |
+
total_words = len(words_lower)
|
28 |
+
if not total_words: return {'entropy': 0.0}
|
29 |
+
counts = Counter(words_lower)
|
30 |
+
entropy = -sum((cnt / total_words) * math.log(cnt / total_words) for cnt in counts.values())
|
31 |
+
return {'entropy': entropy}
|
32 |
+
|
33 |
+
def count_non_alpha_words(text: str) -> Dict[str, float]:
|
34 |
+
"""Liczy stosunek znaków niealfabetycznych do wszystkich."""
|
35 |
+
total_chars = len(text)
|
36 |
+
if not total_chars: return {'non_alpha_word_fractions': 0.0}
|
37 |
+
non_alpha = sum(1 for char in text if not char.isalpha())
|
38 |
+
return {'non_alpha_word_fractions': safe_divide(non_alpha, total_chars)}
|
39 |
+
|
40 |
+
def calculate_symbol_to_word_ratio(words: List[str], text: str) -> Dict[str, float]:
|
41 |
+
"""Oblicza stosunek symboli do słów."""
|
42 |
+
total_words = len(words)
|
43 |
+
char_counts = Counter(text)
|
44 |
+
triple_dot_count = text.count('...')
|
45 |
+
symbol_count = char_counts.get('#', 0) + triple_dot_count + char_counts.get('…', 0)
|
46 |
+
return {'symbol_to_word_ratio': safe_divide(symbol_count, total_words)}
|
47 |
+
|
48 |
+
|
49 |
+
# --- Funkcje analizujące n-gramy ---
|
50 |
+
|
51 |
+
def calculate_ngram_fractions(words: List[str]) -> Dict[str, float]:
|
52 |
+
"""Analizuje frakcje powtarzających się i najczęstszych n-gramów."""
|
53 |
+
normalized_text = NON_WORD_CHARS_PATTERN.sub('', ' '.join(words))
|
54 |
+
total_chars = len(normalized_text)
|
55 |
+
|
56 |
+
keys = [f'fraction_duplicate_{n}_ngram' for n in range(5, 11)] + \
|
57 |
+
[f'fraction_top_{n}_ngram' for n in range(2, 6)]
|
58 |
+
if total_chars == 0 or len(words) < 10:
|
59 |
+
return {key: 0.0 for key in keys}
|
60 |
+
|
61 |
+
results = {}
|
62 |
+
for n in range(2, 11):
|
63 |
+
if len(words) < n: continue
|
64 |
+
ngrams_gen = (' '.join(words[i:i + n]) for i in range(len(words) - n + 1))
|
65 |
+
counts = Counter(ngrams_gen)
|
66 |
+
if n >= 5:
|
67 |
+
dup_chars = sum(len(ngram) for ngram, cnt in counts.items() if cnt > 1)
|
68 |
+
results[f'fraction_duplicate_{n}_ngram'] = safe_divide(dup_chars, total_chars)
|
69 |
+
if 2 <= n <= 5:
|
70 |
+
if counts:
|
71 |
+
top_ngram, _ = counts.most_common(1)[0]
|
72 |
+
results[f'fraction_top_{n}_ngram'] = safe_divide(len(top_ngram), total_chars)
|
73 |
+
else:
|
74 |
+
results[f'fraction_top_{n}_ngram'] = 0.0
|
75 |
+
return results
|
76 |
+
|
77 |
+
# --- Funkcje analizujące styl tekstu ---
|
78 |
+
|
79 |
+
def analyze_stylistic_metrics(text: str, words: List[str], sentences: List[str]) -> Dict[str, float]:
|
80 |
+
# Używamy prostej tokenizacji, aby zachować zgodność ze starym kodem
|
81 |
+
sentences_from_regex = re.findall(r'[^.!?]+[.!?]', text)
|
82 |
+
num_sentences = len(sentences_from_regex)
|
83 |
+
words_per_sentence = [len(s.split()) for s in sentences_from_regex]
|
84 |
+
|
85 |
+
formal_words = ['Pan', 'Pani', 'Państwo', 'uprzejmie', 'proszę', 'dziękuję']
|
86 |
+
formal_count = sum(text.count(word) for word in formal_words)
|
87 |
+
|
88 |
+
cohesive_words = ['jednak', 'ponadto', 'w konsekwencji', 'zatem', 'więc', 'dlatego', 'natomiast', 'niemniej']
|
89 |
+
cohesion_count = sum(text.lower().count(word) for word in cohesive_words)
|
90 |
+
|
91 |
+
quote_count = len(re.findall(r'[„"]', text)) // 2
|
92 |
+
reference_count = len(re.findall(r'\[[0-9]+\]', text))
|
93 |
+
|
94 |
+
first_words = [s.split()[0].lower() for s in sentences_from_regex if s.split()]
|
95 |
+
|
96 |
+
return {
|
97 |
+
'formal_words_ratio': safe_divide(formal_count, len(words)),
|
98 |
+
'cohesive_words_per_sentence': safe_divide(cohesion_count, num_sentences),
|
99 |
+
'quotes_and_references_per_sentence': safe_divide(quote_count + reference_count, num_sentences),
|
100 |
+
'unique_sentence_beginnings_ratio': safe_divide(len(set(first_words)), num_sentences),
|
101 |
+
'commas_per_sentence': safe_divide(text.count(','), num_sentences),
|
102 |
+
'semicolons_per_sentence': safe_divide(text.count(';'), num_sentences),
|
103 |
+
'dashes_per_sentence': safe_divide(text.count('—') + text.count('–'), num_sentences),
|
104 |
+
'colons_per_sentence': safe_divide(text.count(':'), num_sentences),
|
105 |
+
'short_sentences_ratio': safe_divide(sum(1 for c in words_per_sentence if c < 5), num_sentences),
|
106 |
+
'long_sentences_ratio': safe_divide(sum(1 for c in words_per_sentence if c > 30), num_sentences)
|
107 |
+
}
|
108 |
+
|
109 |
+
# --- Główna funkcja agregująca ---
|
110 |
+
|
111 |
+
def calculate_all_linguistic_features(text: str, text_lower: str, words: List[str], words_lower: List[str], sentences: List[str]) -> Dict[str, float]:
|
112 |
+
"""Agreguje wszystkie cechy lingwistyczne i stylistyczne z tego modułu."""
|
113 |
+
features = {}
|
114 |
+
features.update(calculate_stop_word_ratio(words_lower))
|
115 |
+
features.update(count_bad_words(words_lower))
|
116 |
+
features.update(calculate_unigram_entropy(words_lower))
|
117 |
+
features.update(count_non_alpha_words(text)) # Oryginalnie liczone na całym tekście
|
118 |
+
features.update(calculate_symbol_to_word_ratio(words, text))
|
119 |
+
features.update(calculate_ngram_fractions(words))
|
120 |
+
features.update(analyze_stylistic_metrics(text, words, sentences))
|
121 |
+
features['javascript_counts_per_line'] = text_lower.count('javascript') # Oryginał to była suma, nie na linię
|
122 |
+
return features
|
text_analyzer/features/regex_features.py
ADDED
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# text_analyzer/features/regex_features.py
|
2 |
+
"""
|
3 |
+
Moduł do ekstrakcji cech opartych na wyrażeniach regularnych.
|
4 |
+
|
5 |
+
Odpowiedzialny za wykrywanie i zliczanie predefiniowanych wzorców,
|
6 |
+
takich jak dane osobowe i wrażliwe (PII), numery identyfikacyjne,
|
7 |
+
daty, adresy e-mail i inne specyficzne formaty.
|
8 |
+
"""
|
9 |
+
|
10 |
+
from typing import Dict
|
11 |
+
|
12 |
+
from ..constants import PII_REGEX_PATTERNS
|
13 |
+
|
14 |
+
def calculate_all_regex_features(text: str) -> Dict[str, int]:
|
15 |
+
"""
|
16 |
+
Przeszukuje tekst w poszukiwaniu wszystkich zdefiniowanych wzorców i
|
17 |
+
zlicza ich wystąpienia, zachowując zgodność z oryginalnym zestawem cech.
|
18 |
+
|
19 |
+
Args:
|
20 |
+
text (str): Tekst do analizy.
|
21 |
+
|
22 |
+
Returns:
|
23 |
+
Dict[str, int]: Słownik, w którym klucze to nazwy wzorców (np. 'email_reg'),
|
24 |
+
a wartości to liczba znalezionych dopasowań.
|
25 |
+
"""
|
26 |
+
if not text:
|
27 |
+
# Zwracamy słownik z zerami dla wszystkich kluczy, które by powstały.
|
28 |
+
# Usuwamy 'domestic_phone_reg', bo nie jest to cecha końcowa.
|
29 |
+
original_keys = [k for k in PII_REGEX_PATTERNS.keys() if k != 'domestic_phone_reg']
|
30 |
+
return {name: 0 for name in original_keys}
|
31 |
+
|
32 |
+
features = {}
|
33 |
+
|
34 |
+
# Przechodzimy przez wszystkie prekompilowane wzorce
|
35 |
+
for name, pattern in PII_REGEX_PATTERNS.items():
|
36 |
+
try:
|
37 |
+
matches = pattern.findall(text)
|
38 |
+
features[name] = len(matches)
|
39 |
+
except Exception as e:
|
40 |
+
# Dodajemy obsługę błędów na wypadek problemów z regexem
|
41 |
+
print(f"Błąd podczas przetwarzania wzorca '{name}': {e}")
|
42 |
+
features[name] = 0
|
43 |
+
|
44 |
+
# Odtworzenie oryginalnej logiki: suma telefonów jest przypisana do 'phone_reg'
|
45 |
+
# a 'domestic_phone_reg' nie jest cechą końcową.
|
46 |
+
if 'phone_reg' in features and 'domestic_phone_reg' in features:
|
47 |
+
features['phone_reg'] += features['domestic_phone_reg']
|
48 |
+
# Usuwamy klucz pomocniczy, aby nie pojawił się w wynikach końcowych.
|
49 |
+
del features['domestic_phone_reg']
|
50 |
+
|
51 |
+
return features
|
text_analyzer/features/spacy_features.py
ADDED
@@ -0,0 +1,196 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Moduł do ekstrakcji zaawansowanych cech lingwistycznych przy użyciu spaCy.
|
3 |
+
"""
|
4 |
+
import re
|
5 |
+
import math
|
6 |
+
from collections import Counter
|
7 |
+
from statistics import mean, variance
|
8 |
+
from typing import Dict, List
|
9 |
+
|
10 |
+
import textstat
|
11 |
+
import spacy
|
12 |
+
|
13 |
+
from ..utils import safe_divide
|
14 |
+
from ..constants import CAMEL_CASE_PATTERN
|
15 |
+
|
16 |
+
# --- Funkcje analizujące podstawowe statystyki z Doc ---
|
17 |
+
|
18 |
+
def analyze_pos_stats(doc: spacy.tokens.Doc) -> Dict[str, float]:
|
19 |
+
"""Oblicza statystyki części mowy (POS), interpunkcji i stopwords."""
|
20 |
+
words = [t for t in doc if not t.is_punct and not t.is_space and t.pos_ != 'SYM']
|
21 |
+
words_count = len(words)
|
22 |
+
|
23 |
+
if not words_count:
|
24 |
+
return {'words': 0, 'nouns': 0, 'verbs': 0, 'adjectives': 0, 'adverbs': 0,
|
25 |
+
'punctuations': 0, 'symbols': 0, 'stopwords': 0, 'oovs': 0,
|
26 |
+
'pos_x': 0, 'pos_num': 0, 'noun_ratio': 0.0, 'verb_ratio': 0.0,
|
27 |
+
'adj_ratio': 0.0}
|
28 |
+
|
29 |
+
stats = {
|
30 |
+
'words': words_count, 'nouns': sum(1 for t in doc if t.pos_ == "NOUN"),
|
31 |
+
'verbs': sum(1 for t in doc if t.pos_ == "VERB"),
|
32 |
+
'adjectives': sum(1 for t in doc if t.pos_ == "ADJ"),
|
33 |
+
'adverbs': sum(1 for t in doc if t.pos_ == "ADV"),
|
34 |
+
'punctuations': sum(1 for t in doc if t.is_punct),
|
35 |
+
'symbols': sum(1 for t in doc if t.pos_ == "SYM"),
|
36 |
+
'stopwords': sum(1 for t in doc if t.is_stop),
|
37 |
+
'oovs': sum(1 for t in doc if t.is_oov),
|
38 |
+
'pos_x': sum(1 for t in doc if t.pos_ == "X"),
|
39 |
+
'pos_num': sum(1 for t in doc if t.pos_ == "NUM"),
|
40 |
+
}
|
41 |
+
stats['noun_ratio'] = safe_divide(stats['nouns'], words_count)
|
42 |
+
stats['verb_ratio'] = safe_divide(stats['verbs'], words_count)
|
43 |
+
stats['adj_ratio'] = safe_divide(stats['adjectives'], words_count)
|
44 |
+
return stats
|
45 |
+
|
46 |
+
def analyze_doc_level_stats(doc: spacy.tokens.Doc, text: str) -> Dict[str, float]:
|
47 |
+
"""Analizuje cechy na poziomie całego dokumentu."""
|
48 |
+
words = [t for t in doc if not t.is_punct and not t.is_space and t.pos_ != 'SYM']
|
49 |
+
words_count = len(words)
|
50 |
+
sentences_count = len(list(doc.sents))
|
51 |
+
|
52 |
+
return {
|
53 |
+
'sentences': sentences_count,
|
54 |
+
'avg_word_length': safe_divide(sum(len(t.text) for t in words), words_count),
|
55 |
+
'avg_sentence_length': safe_divide(words_count, sentences_count),
|
56 |
+
'lexical_density': safe_divide(len({t.lemma_ for t in words}), words_count),
|
57 |
+
'gunning_fog': textstat.gunning_fog(text) if text.strip() else 0.0,
|
58 |
+
'camel_case': sum(1 for t in words if CAMEL_CASE_PATTERN.match(t.text)),
|
59 |
+
'capitalized_words': sum(1 for t in words if t.text.isupper()),
|
60 |
+
}
|
61 |
+
|
62 |
+
# --- Funkcje analizujące zaawansowane cechy lingwistyczne ---
|
63 |
+
|
64 |
+
def analyze_named_entities(doc: spacy.tokens.Doc) -> Dict[str, float]:
|
65 |
+
"""Analizuje rozpoznane jednostki nazwane (NER)."""
|
66 |
+
alpha_words = [t for t in doc if t.is_alpha]
|
67 |
+
if not alpha_words:
|
68 |
+
return {"ner_count": 0, "ner_person_ratio": 0.0, "ner_org_ratio": 0.0,
|
69 |
+
"ner_loc_ratio": 0.0, "ner_misc_ratio": 0.0}
|
70 |
+
|
71 |
+
ents = doc.ents
|
72 |
+
return {
|
73 |
+
"ner_count": len(ents),
|
74 |
+
"ner_person_ratio": safe_divide(sum(1 for e in ents if e.label_ == "persName"), len(alpha_words)),
|
75 |
+
"ner_org_ratio": safe_divide(sum(1 for e in ents if e.label_ == "orgName"), len(alpha_words)),
|
76 |
+
"ner_loc_ratio": safe_divide(sum(1 for e in ents if e.label_ in ["placeName", "locName"]), len(alpha_words)),
|
77 |
+
"ner_misc_ratio": safe_divide(sum(1 for e in ents if e.label_ not in ["persName", "orgName", "placeName", "locName"]), len(alpha_words)),
|
78 |
+
}
|
79 |
+
|
80 |
+
def analyze_morphology(doc: spacy.tokens.Doc) -> Dict[str, float]:
|
81 |
+
"""Analizuje różnorodność morfologiczną."""
|
82 |
+
alpha_tokens = [t for t in doc if t.is_alpha]
|
83 |
+
if not alpha_tokens:
|
84 |
+
return {"case_diversity": 0.0, "tense_diversity": 0.0, "mood_diversity": 0.0}
|
85 |
+
|
86 |
+
cases, tenses, moods = [], [], []
|
87 |
+
for token in alpha_tokens:
|
88 |
+
if token.morph:
|
89 |
+
cases.extend(token.morph.get("Case", []))
|
90 |
+
tenses.extend(token.morph.get("Tense", []))
|
91 |
+
moods.extend(token.morph.get("Mood", []))
|
92 |
+
|
93 |
+
return {"case_diversity": safe_divide(len(set(cases)), len(alpha_tokens)),
|
94 |
+
"tense_diversity": safe_divide(len(set(tenses)), len(alpha_tokens)),
|
95 |
+
"mood_diversity": safe_divide(len(set(moods)), len(alpha_tokens))}
|
96 |
+
|
97 |
+
def analyze_dependency_complexity(doc: spacy.tokens.Doc) -> Dict[str, float]:
|
98 |
+
"""Oblicza średnią głębokość drzewa zależności."""
|
99 |
+
depths = []
|
100 |
+
for sent in doc.sents:
|
101 |
+
if not list(sent): continue
|
102 |
+
max_depth = 0
|
103 |
+
for token in sent:
|
104 |
+
dist = 0
|
105 |
+
curr = token
|
106 |
+
while curr.head != curr and dist < 100:
|
107 |
+
curr = curr.head
|
108 |
+
dist += 1
|
109 |
+
max_depth = max(max_depth, dist)
|
110 |
+
depths.append(max_depth)
|
111 |
+
return {"avg_dependency_tree_depth": mean(depths) if depths else 0.0}
|
112 |
+
|
113 |
+
def analyze_pos_frequencies(doc: spacy.tokens.Doc, top_k=10) -> Dict[str, float]:
|
114 |
+
"""Analizuje częstotliwość POS dla najczęstszych słów."""
|
115 |
+
tokens = [t for t in doc if t.is_alpha]
|
116 |
+
if not tokens:
|
117 |
+
return {"top_words_total_count": 0, "top_words_noun_ratio": 0.0, "top_words_verb_ratio": 0.0,
|
118 |
+
"top_words_adj_ratio": 0.0, "top_words_other_ratio": 0.0, "top_words_noun_prop_of_all_nouns": 0.0,
|
119 |
+
"top_words_verb_prop_of_all_verbs": 0.0, "top_words_adj_prop_of_all_adjs": 0.0,
|
120 |
+
"top_words_other_prop_of_all_others": 0.0}
|
121 |
+
|
122 |
+
word_counts = Counter(t.text.lower() for t in tokens)
|
123 |
+
top_words_list = [w for w, _ in word_counts.most_common(top_k)]
|
124 |
+
|
125 |
+
top_tokens = [t for t in tokens if t.text.lower() in top_words_list]
|
126 |
+
total_top_count = len(top_tokens)
|
127 |
+
|
128 |
+
top_noun = sum(1 for t in top_tokens if t.pos_ == 'NOUN')
|
129 |
+
top_verb = sum(1 for t in top_tokens if t.pos_ == 'VERB')
|
130 |
+
top_adj = sum(1 for t in top_tokens if t.pos_ == 'ADJ')
|
131 |
+
top_other = total_top_count - (top_noun + top_verb + top_adj)
|
132 |
+
|
133 |
+
total_nouns = sum(1 for t in tokens if t.pos_ == "NOUN")
|
134 |
+
total_verbs = sum(1 for t in tokens if t.pos_ == "VERB")
|
135 |
+
total_adjs = sum(1 for t in tokens if t.pos_ == "ADJ")
|
136 |
+
total_others = len(tokens) - (total_nouns + total_verbs + total_adjs)
|
137 |
+
|
138 |
+
return {
|
139 |
+
"top_words_total_count": total_top_count,
|
140 |
+
"top_words_noun_ratio": safe_divide(top_noun, total_top_count),
|
141 |
+
"top_words_verb_ratio": safe_divide(top_verb, total_top_count),
|
142 |
+
"top_words_adj_ratio": safe_divide(top_adj, total_top_count),
|
143 |
+
"top_words_other_ratio": safe_divide(top_other, total_top_count),
|
144 |
+
"top_words_noun_prop_of_all_nouns": safe_divide(top_noun, total_nouns),
|
145 |
+
"top_words_verb_prop_of_all_verbs": safe_divide(top_verb, total_verbs),
|
146 |
+
"top_words_adj_prop_of_all_adjs": safe_divide(top_adj, total_adjs),
|
147 |
+
"top_words_other_prop_of_all_others": safe_divide(top_other, total_others),
|
148 |
+
}
|
149 |
+
|
150 |
+
# --- Funkcje wymagające tylko tekstu ---
|
151 |
+
|
152 |
+
def compute_readability_indices(text: str, sentences: List[str]) -> Dict[str, float]:
|
153 |
+
"""Oblicza wskaźniki czytelności LIX i RIX."""
|
154 |
+
if not text.strip(): return {"lix": 0.0, "rix": 0.0}
|
155 |
+
words = re.findall(r'\w+', text)
|
156 |
+
num_words = len(words)
|
157 |
+
num_sentences = len(sentences)
|
158 |
+
long_words = sum(1 for w in words if len(w) > 6)
|
159 |
+
lix = safe_divide(num_words, num_sentences) + safe_divide(long_words * 100, num_words)
|
160 |
+
rix = safe_divide(long_words, num_words) * 100
|
161 |
+
return {"lix": lix, "rix": rix}
|
162 |
+
|
163 |
+
def analyze_polish_diacritics_distribution(text: str) -> Dict[str, float]:
|
164 |
+
"""Analizuje rozkład polskich znaków diakrytycznych."""
|
165 |
+
polish_diacritics = 'ąćęłńóśźżĄĆĘŁŃÓŚŹŻ'
|
166 |
+
total = len(text)
|
167 |
+
if total == 0: return {"diacritics_std_dev": 0.0}
|
168 |
+
counts = Counter(text)
|
169 |
+
diac_counts = [counts[ch] for ch in polish_diacritics if ch in counts]
|
170 |
+
if not diac_counts: return {"diacritics_std_dev": 0.0}
|
171 |
+
diac_freqs = [c / total for c in diac_counts]
|
172 |
+
mean_freq = mean(diac_freqs)
|
173 |
+
variance_val = sum((x - mean_freq) ** 2 for x in diac_freqs) / len(diac_freqs)
|
174 |
+
return {"diacritics_std_dev": math.sqrt(variance_val)}
|
175 |
+
|
176 |
+
def analyze_question_sentences(sentences: List[str]) -> Dict[str, float]:
|
177 |
+
"""Oblicza stosunek zdań pytających do wszystkich."""
|
178 |
+
if not sentences: return {"question_sentence_ratio": 0.0}
|
179 |
+
questions = sum(1 for s in sentences if s.strip().endswith('?'))
|
180 |
+
return {"question_sentence_ratio": safe_divide(questions, len(sentences))}
|
181 |
+
|
182 |
+
# --- Główna funkcja agregująca ---
|
183 |
+
|
184 |
+
def calculate_all_spacy_features(doc: spacy.tokens.Doc, text: str, sentences: List[str]) -> Dict[str, float]:
|
185 |
+
"""Agreguje wszystkie zaawansowane cechy lingwistyczne."""
|
186 |
+
features = {}
|
187 |
+
features.update(analyze_pos_stats(doc))
|
188 |
+
features.update(analyze_doc_level_stats(doc, text))
|
189 |
+
features.update(analyze_named_entities(doc))
|
190 |
+
features.update(analyze_morphology(doc))
|
191 |
+
features.update(analyze_dependency_complexity(doc))
|
192 |
+
features.update(analyze_pos_frequencies(doc))
|
193 |
+
features.update(compute_readability_indices(text, sentences))
|
194 |
+
features.update(analyze_polish_diacritics_distribution(text))
|
195 |
+
features.update(analyze_question_sentences(sentences))
|
196 |
+
return features
|
text_analyzer/features/structural_features.py
ADDED
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# text_analyzer/features/structural_features.py
|
2 |
+
"""
|
3 |
+
Moduł do ekstrakcji cech strukturalnych i formatowania tekstu.
|
4 |
+
"""
|
5 |
+
import re
|
6 |
+
from collections import Counter
|
7 |
+
from statistics import mean, variance
|
8 |
+
from typing import Dict, List
|
9 |
+
|
10 |
+
from ..utils import safe_divide
|
11 |
+
from ..constants import MARKDOWN_PATTERNS
|
12 |
+
|
13 |
+
# --- Funkcje analizujące strukturę paragrafów ---
|
14 |
+
|
15 |
+
def analyze_paragraph_stats(text: str) -> Dict[str, float]:
|
16 |
+
"""Analizuje statystyki paragrafów."""
|
17 |
+
paragraphs = [p.strip() for p in text.split('\n\n') if p.strip()]
|
18 |
+
if not paragraphs:
|
19 |
+
return {'avg_paragraph_length': 0.0, 'paragraph_length_variance': 0.0}
|
20 |
+
para_lengths_in_words = [len(p.split()) for p in paragraphs]
|
21 |
+
return {
|
22 |
+
'avg_paragraph_length': mean(para_lengths_in_words) if para_lengths_in_words else 0.0,
|
23 |
+
'paragraph_length_variance': variance(para_lengths_in_words) if len(para_lengths_in_words) > 1 else 0.0
|
24 |
+
}
|
25 |
+
|
26 |
+
# --- Funkcje analizujące formatowanie i elementy nietekstowe ---
|
27 |
+
|
28 |
+
def analyze_formatting_and_links(text: str, sentences: List[str]) -> Dict[str, float]:
|
29 |
+
"""Zlicza elementy formatowania (HTML, BBCode), linki, emotikony i slang."""
|
30 |
+
total_chars = len(text)
|
31 |
+
words = text.split()
|
32 |
+
html_tags = re.findall(r'<[^>]+>', text)
|
33 |
+
bbcode_tags = re.findall(r'\[[^\]]+\]', text)
|
34 |
+
slang_words_count = len(re.findall(r'\b(?:lol|omg|lmao|xd|wtf)\b', text.lower()))
|
35 |
+
markup_len = sum(len(tag) for tag in html_tags) + sum(len(tag) for tag in bbcode_tags)
|
36 |
+
incomplete_sentences_count = sum(1 for s in sentences if len(s.split()) < 3)
|
37 |
+
|
38 |
+
return {
|
39 |
+
'html_tags': len(html_tags),
|
40 |
+
'bbcode_tags': len(bbcode_tags),
|
41 |
+
'urls': len(re.findall(r'https?://\S+|www\.\S+', text)),
|
42 |
+
'text_to_markup_ratio': safe_divide(total_chars - markup_len, total_chars),
|
43 |
+
'emoticons': len(re.findall(r'[:;=]-?[)(DPp]', text)),
|
44 |
+
'slang_words': slang_words_count,
|
45 |
+
'slang_words_ratio': safe_divide(slang_words_count, len(words)),
|
46 |
+
'excessive_chars': len(re.findall(r'(\.|,|-|_){4,}', text)),
|
47 |
+
'incomplete_sentences': incomplete_sentences_count,
|
48 |
+
}
|
49 |
+
|
50 |
+
def analyze_markdown_features(text: str) -> Dict[str, float]:
|
51 |
+
"""Analizuje użycie składni Markdown, w tym wskaźniki poszczególnych znaków."""
|
52 |
+
total_chars = len(text)
|
53 |
+
if not total_chars:
|
54 |
+
# Zwróć zera dla wszystkich cech, aby uniknąć błędów
|
55 |
+
keys = [f'{name}_per_1000_chars_md' for name in MARKDOWN_PATTERNS.keys()]
|
56 |
+
keys += [f'char_ratio_{ch}' for ch in ['#', '*', '-', '+', '[', ']', '(', ')', '`', '>', '_', '!']]
|
57 |
+
keys += ['average_header_level_md', 'special_chars_ratio_md', 'lowercase_ratio_md',
|
58 |
+
'uppercase_ratio_md', 'digit_ratio_md', 'whitespace_ratio_md']
|
59 |
+
return {key: 0.0 for key in keys}
|
60 |
+
|
61 |
+
features = {'average_header_level_md': 0.0}
|
62 |
+
|
63 |
+
# Podstawowe elementy Markdown
|
64 |
+
for name, pattern in MARKDOWN_PATTERNS.items():
|
65 |
+
count = len(pattern.findall(text))
|
66 |
+
# Zmieniamy nazwy kluczy, aby pasowały do column_order
|
67 |
+
key_name_map = {'header': 'headers', 'bold': 'bold', 'italic': 'italic',
|
68 |
+
'unordered_list': 'unordered_list_items', 'ordered_list': 'ordered_list_items',
|
69 |
+
'link': 'links', 'image': 'images', 'inline_code': 'inline_code_fragments',
|
70 |
+
'code_block': 'code_blocks', 'blockquote': 'blockquotes', 'horizontal_rule': 'horizontal_rules'}
|
71 |
+
features[f'{key_name_map[name]}_per_1000_chars_md'] = safe_divide(count * 1000, total_chars)
|
72 |
+
|
73 |
+
# Średni poziom nagłówków
|
74 |
+
headers = MARKDOWN_PATTERNS['header'].findall(text)
|
75 |
+
if headers:
|
76 |
+
header_levels = [h.count('#') for h in headers]
|
77 |
+
features['average_header_level_md'] = safe_divide(sum(header_levels), len(header_levels))
|
78 |
+
|
79 |
+
# Cechy znakowe
|
80 |
+
char_counts = Counter(text)
|
81 |
+
special_chars_list = ['#', '*', '-', '+', '[', ']', '(', ')', '`', '>', '_', '!']
|
82 |
+
for ch in special_chars_list:
|
83 |
+
features[f'char_ratio_{ch}'] = safe_divide(char_counts.get(ch, 0), total_chars)
|
84 |
+
|
85 |
+
features['special_chars_ratio_md'] = safe_divide(sum(char_counts.get(ch, 0) for ch in special_chars_list), total_chars)
|
86 |
+
features['lowercase_ratio_md'] = safe_divide(sum(1 for c in text if c.islower()), total_chars)
|
87 |
+
features['uppercase_ratio_md'] = safe_divide(sum(1 for c in text if c.isupper()), total_chars)
|
88 |
+
features['digit_ratio_md'] = safe_divide(sum(1 for c in text if c.isdigit()), total_chars)
|
89 |
+
features['whitespace_ratio_md'] = safe_divide(sum(1 for c in text if c.isspace()), total_chars)
|
90 |
+
|
91 |
+
return features
|
92 |
+
|
93 |
+
def analyze_markdown_table_structure(text: str, lines: List[str]) -> Dict[str, float]:
|
94 |
+
"""Analizuje cechy związane z tabelami w Markdown."""
|
95 |
+
total_chars = len(text)
|
96 |
+
total_lines = len(lines)
|
97 |
+
if not total_chars:
|
98 |
+
return {'table_pipe_count': 0, 'table_pipe_ratio': 0.0, 'table_pipe_per_1000_chars': 0.0,
|
99 |
+
'table_lines_count': 0, 'table_lines_ratio': 0.0, 'table_header_separators_count': 0,
|
100 |
+
'avg_pipes_per_table_line': 0.0, 'estimated_avg_columns': 0.0}
|
101 |
+
|
102 |
+
pipe_count = text.count('|')
|
103 |
+
table_lines = [line for line in lines if '|' in line]
|
104 |
+
header_separators = len([line for line in table_lines if re.match(r'^[\|\-\:\s]+$', line.strip())])
|
105 |
+
|
106 |
+
return {
|
107 |
+
'table_pipe_count': pipe_count,
|
108 |
+
'table_pipe_ratio': safe_divide(pipe_count, total_chars),
|
109 |
+
'table_pipe_per_1000_chars': safe_divide(pipe_count * 1000, total_chars),
|
110 |
+
'table_lines_count': len(table_lines),
|
111 |
+
'table_lines_ratio': safe_divide(len(table_lines), total_lines),
|
112 |
+
'table_header_separators_count': header_separators,
|
113 |
+
'avg_pipes_per_table_line': safe_divide(pipe_count, len(table_lines)),
|
114 |
+
'estimated_avg_columns': safe_divide(pipe_count, (len(table_lines) * 2)) if table_lines else 0
|
115 |
+
}
|
116 |
+
|
117 |
+
# --- Funkcje analizujące strukturę linii ---
|
118 |
+
|
119 |
+
def analyze_line_structure(lines: List[str]) -> Dict[str, float]:
|
120 |
+
"""Analizuje linie pod kątem specyficznych struktur."""
|
121 |
+
total_lines = len(lines)
|
122 |
+
non_empty_lines = [line.strip() for line in lines if line.strip()]
|
123 |
+
|
124 |
+
if not total_lines:
|
125 |
+
return {'lines_with_bullet': 0, 'ratio_of_bulletpoints': 0.0,
|
126 |
+
'single_word_line_ratio': 0.0, 'repeated_word_line_ratio': 0.0}
|
127 |
+
|
128 |
+
bullets = {'•', '○', '‣', '-', '–', '—', '·', '⚪', '⚫', '▢', '■', '→', '★', '✓', '✕', '◇', '◆', '➤', '«', '»'}
|
129 |
+
bullet_count = sum(1 for line in non_empty_lines if line and line[0] in bullets)
|
130 |
+
|
131 |
+
return {
|
132 |
+
'lines_with_bullet': bullet_count,
|
133 |
+
'ratio_of_bulletpoints': safe_divide(bullet_count, len(non_empty_lines)),
|
134 |
+
'single_word_line_ratio': safe_divide(sum(1 for l in non_empty_lines if len(l.split()) == 1), total_lines),
|
135 |
+
'repeated_word_line_ratio': safe_divide(sum(1 for l in non_empty_lines if len(l.split()) > 1 and len(set(l.split())) == 1), total_lines)
|
136 |
+
}
|
137 |
+
|
138 |
+
# --- Główna funkcja agregująca ---
|
139 |
+
|
140 |
+
def calculate_all_structural_features(text: str, lines: List[str], sentences: List[str]) -> Dict[str, float]:
|
141 |
+
"""Agreguje wszystkie cechy strukturalne i formatowania."""
|
142 |
+
features = {}
|
143 |
+
features.update(analyze_paragraph_stats(text))
|
144 |
+
features.update(analyze_formatting_and_links(text, sentences))
|
145 |
+
features.update(analyze_markdown_features(text))
|
146 |
+
features.update(analyze_markdown_table_structure(text, lines))
|
147 |
+
features.update(analyze_line_structure(lines))
|
148 |
+
|
149 |
+
return features
|
text_analyzer/utils.py
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
def safe_divide(numerator: float, denominator: float) -> float:
|
2 |
+
"""
|
3 |
+
Dzieli dwie liczby, zwracając 0, jeśli mianownik jest zerem.
|
4 |
+
|
5 |
+
Args:
|
6 |
+
numerator (float): Licznik.
|
7 |
+
denominator (float): Mianownik.
|
8 |
+
|
9 |
+
Returns:
|
10 |
+
float: Wynik dzielenia lub 0.
|
11 |
+
"""
|
12 |
+
return numerator / denominator if denominator != 0 else 0.0
|