adgw
/

Joblib
adgw commited on
Commit
5c8f9d2
·
verified ·
1 Parent(s): 43b197b
Files changed (34) hide show
  1. dummy.py +134 -0
  2. input_jsonl/test.jsonl +10 -0
  3. input_parquet/test.parquet +3 -0
  4. main_jsonl.py +155 -0
  5. main_parquet.py +167 -0
  6. models/model.joblib +3 -0
  7. models/scaler.pkl +3 -0
  8. output/test.jsonl +10 -0
  9. output/test.parquet +3 -0
  10. requirements.txt +62 -0
  11. text_analyzer/__pycache__/analyzer.cpython-311.pyc +0 -0
  12. text_analyzer/__pycache__/analyzer.cpython-312.pyc +0 -0
  13. text_analyzer/__pycache__/constants.cpython-311.pyc +0 -0
  14. text_analyzer/__pycache__/constants.cpython-312.pyc +0 -0
  15. text_analyzer/__pycache__/utils.cpython-311.pyc +0 -0
  16. text_analyzer/__pycache__/utils.cpython-312.pyc +0 -0
  17. text_analyzer/analyzer.py +86 -0
  18. text_analyzer/constants.py +284 -0
  19. text_analyzer/features/__pycache__/base_features.cpython-311.pyc +0 -0
  20. text_analyzer/features/__pycache__/base_features.cpython-312.pyc +0 -0
  21. text_analyzer/features/__pycache__/linguistic_features.cpython-311.pyc +0 -0
  22. text_analyzer/features/__pycache__/linguistic_features.cpython-312.pyc +0 -0
  23. text_analyzer/features/__pycache__/regex_features.cpython-311.pyc +0 -0
  24. text_analyzer/features/__pycache__/regex_features.cpython-312.pyc +0 -0
  25. text_analyzer/features/__pycache__/spacy_features.cpython-311.pyc +0 -0
  26. text_analyzer/features/__pycache__/spacy_features.cpython-312.pyc +0 -0
  27. text_analyzer/features/__pycache__/structural_features.cpython-311.pyc +0 -0
  28. text_analyzer/features/__pycache__/structural_features.cpython-312.pyc +0 -0
  29. text_analyzer/features/base_features.py +243 -0
  30. text_analyzer/features/linguistic_features.py +122 -0
  31. text_analyzer/features/regex_features.py +51 -0
  32. text_analyzer/features/spacy_features.py +196 -0
  33. text_analyzer/features/structural_features.py +149 -0
  34. 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