manaviel85370 commited on
Commit
da88570
·
1 Parent(s): 479a9e7

add pages and all

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitignore +1 -0
  2. .streamlit/config.toml +2 -0
  3. pages/0_DB_Overview.py +67 -0
  4. pages/1_Get_Urls.py +131 -0
  5. pages/2_Get_Event_Data.py +121 -0
  6. pages/3_Sort_Event_Data.py +67 -0
  7. pages/4_Control.py +128 -0
  8. pages/5_Playground.py +217 -0
  9. pages/6_Pipeline.py +136 -0
  10. pages/TEST.py +0 -2
  11. requirements.txt +23 -1
  12. src/__init__.py +0 -0
  13. src/configuration/__init__.py +0 -0
  14. src/configuration/config.py +7 -0
  15. src/crawler/CrawlerV2.py +215 -0
  16. src/crawler/__init__.py +0 -0
  17. src/crawler/crawler_service.py +106 -0
  18. src/crawler/maps_api.py +23 -0
  19. src/crawler/serp_maps.py +62 -0
  20. src/crawler/serp_search.py +29 -0
  21. src/crawler/utils/keywords.py +45 -0
  22. src/crawler/utils/maps_types.py +25 -0
  23. src/crawler/utils/regEx.py +97 -0
  24. src/nlp/__init__.py +0 -0
  25. src/nlp/config.cfg +17 -0
  26. src/nlp/data/ner.json +1 -0
  27. src/nlp/data/ner/texts.json +0 -0
  28. src/nlp/data/test.txt +14 -0
  29. src/nlp/dates_txt +4 -0
  30. src/nlp/event.jpg +0 -0
  31. src/nlp/experimental/__init__.py +0 -0
  32. src/nlp/experimental/annotations.json +241 -0
  33. src/nlp/experimental/annotations_v1.json +430 -0
  34. src/nlp/experimental/data/img.png +0 -0
  35. src/nlp/experimental/data/test.md +34 -0
  36. src/nlp/experimental/gliner/ner_fine_tuning.py +42 -0
  37. src/nlp/experimental/gliner/open_information_extraction.py +22 -0
  38. src/nlp/experimental/gliner/summarization.py +194 -0
  39. src/nlp/experimental/keyword_extraction.py +33 -0
  40. src/nlp/experimental/layout_parser.py +23 -0
  41. src/nlp/experimental/llm/__init__.py +0 -0
  42. src/nlp/experimental/llm/inference_api_test.py +227 -0
  43. src/nlp/experimental/llm/llm_image_document_question_answering.py +8 -0
  44. src/nlp/experimental/llm/llm_ner.py +32 -0
  45. src/nlp/experimental/ner/__init__.py +0 -0
  46. src/nlp/experimental/ner/create_spacy_annotations.py +158 -0
  47. src/nlp/experimental/ner/few_shot_ner.py +52 -0
  48. src/nlp/experimental/ner/nu_ner.py +33 -0
  49. src/nlp/experimental/ner/spacy_ner.py +47 -0
  50. src/nlp/experimental/ner/spacy_ner_rule_based.py +58 -0
.gitignore CHANGED
@@ -1,2 +1,3 @@
1
  .idea
2
  .venv
 
 
1
  .idea
2
  .venv
3
+ .env
.streamlit/config.toml ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ [theme]
2
+ base="light"
pages/0_DB_Overview.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ from src.utils.markdown_processing.md_preprocessing import convert_html_to_md
3
+ from src.persistence.db import *
4
+ import streamlit_nested_layout
5
+
6
+
7
+ @st.cache_resource
8
+ def init_connection():
9
+ return init_db()
10
+
11
+ def render_url_content(element):
12
+ with st.container(border=True, height=400):
13
+ md = convert_html_to_md(element["cleaned_html"])
14
+ st.markdown(md, unsafe_allow_html=True)
15
+
16
+ db = init_connection()
17
+
18
+ # Titel der App
19
+ st.title("Übersicht über die Datenbank-Inhalte")
20
+ st.subheader("Aktuelle Einträge in der DB")
21
+ st.write("""
22
+ - **unsorted_urls**: Enthält Daten-Objekte bestehend aus Start-Urls, Url-Typ (z.b. "city", "theater") sowie vom Crawler gefundene Sub-Urls.
23
+ - **event_urls**: Enthält Daten-Objekte bestehend aus Url, Referenz zur Basis-Url (Start-Url), Klasse (EventDetail / EventOverview) sowie HTML der Seite""")
24
+ df = pd.DataFrame({
25
+ "DB-Collection":[
26
+ CollectionNames.UNSORTED_URLS,
27
+ CollectionNames.EVENT_URLS],
28
+ "Anzahl an Einträgen":[
29
+ db.unsorted_urls.count_documents({}),
30
+ db.event_urls.count_documents({})],
31
+ "Bereits verarbeitet":[
32
+ db.unsorted_urls.count_documents({"crawled": True}),
33
+ db.event_urls.count_documents({"final":True})
34
+ ]})
35
+ st.table(df)
36
+
37
+ overview_pages = list(db.event_urls.find(filter={"class":"EventOverview", "final":True}, projection={"url":1,"base_url_id":1,"cleaned_html":1}))
38
+ detail_pages = list(db.event_urls.find(filter={"class":"EventDetail", "final":True}, projection={"url":1,"base_url_id":1,"cleaned_html":1, "data":1}) )
39
+
40
+ st.subheader("Fertige Einträge in Event Urls:")
41
+ st.write("Die fertigen Daten sind mithilfe der gpt-api in markdown übersetzt und nur der Veranstaltungsbereich heraus geschnitten.")
42
+ data = [el for el in detail_pages if "data" in el]
43
+ st.write(f"Fertig verarbeitete Urls: {len(data)} von {len(detail_pages)}")
44
+
45
+ st.subheader("Einträge in Event Urls")
46
+ st.write("""
47
+ Die Übersicht zeigt die finalen Daten aus **event_urls**, sortiert nach ihrer Klasse.""")
48
+ with st.expander(f"Event-Übersichtsseiten ({len(overview_pages)})"):
49
+ for el in overview_pages:
50
+ try:
51
+ with st.expander(f"{el['url']} - ({db.unsorted_urls.find_one(filter={'_id':el['base_url_id']}, projection={'url_type':1})['url_type']})"):
52
+ render_url_content(el)
53
+ except Exception as e:
54
+ st.write(f"Fehler: {e}")
55
+
56
+
57
+ with st.expander(f"Event-Detailseiten ({len(detail_pages)})"):
58
+ for el in detail_pages:
59
+ try:
60
+ with st.expander(f"{el['url']}"):
61
+ render_url_content(el)
62
+ except Exception as e:
63
+ st.write(f"Fehler bei {el['url']}: {e} ")
64
+
65
+
66
+
67
+
pages/1_Get_Urls.py ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from src.crawler.CrawlerV2 import Crawler
2
+ from src.crawler.crawler_service import *
3
+ from src.crawler.utils.maps_types import MAPS_TYPES
4
+ from src.crawler.maps_api import get_maps_results
5
+ from src.persistence.db import *
6
+ import random
7
+ import streamlit_nested_layout
8
+
9
+
10
+ @st.cache_resource
11
+ def init_connection():
12
+ return init_db()
13
+
14
+ def crawl(item):
15
+ results =[]
16
+ try:
17
+ st.info(f"Crawle {item['url']}")
18
+
19
+ if "overview_pages" not in item:
20
+ crawler = Crawler(item["url"], item["url_type"], depth=2)
21
+ results = crawler.crawl()
22
+ except Exception as e:
23
+ st.error(f"Fehler beim crawlen: {e}")
24
+ db.unsorted_urls.delete_one({"_id":item["_id"]})
25
+ return
26
+
27
+ # Übersicht-Seiten erkennen
28
+ overview_regex = re.compile(
29
+ r"^https?:\/\/([a-zA-Z0-9.-]*\/)*(?!(advent))(kalender|.*veranstaltungen|veranstaltungskalender|.*events?|.*event-?kalender|([a-zA-Z]*)?programm|gottesdienste|auff(ü|ue)hrungen|termine|spielplan)(\/?|(\/?[a-zA-Z]*)\.[a-zA-Z]*)?$",
30
+ re.IGNORECASE)
31
+ overview_pages = set()
32
+ # URLs sortieren
33
+
34
+ sub_urls=[]
35
+ for url in results:
36
+ if overview_regex.match(url):
37
+ overview_pages.add(url)
38
+ else:
39
+ sub_urls.append(url)
40
+ if not overview_pages:
41
+ overview_regex = re.compile(
42
+ r"^https?:\/\/([a-zA-Z0-9.-]*\/)*(?!(advent))(kalender|.*veranstaltungen|veranstaltungskalender|.*events?|.*event-?kalender|([a-zA-Z]*)?programm|gottesdienste|auff(ü|ue)hrungen|termine|spielplan)(\/?|(\/?[a-zA-Z]*)\.[a-zA-Z]*)?",
43
+ re.IGNORECASE)
44
+ for url in results:
45
+ match = overview_regex.search(url)
46
+ if match:
47
+ overview_pages.add(match.group())
48
+ overview_pages = {url.casefold() for url in overview_pages}
49
+
50
+ with st.expander("Gefundene Suburls"):
51
+ for url in sub_urls:
52
+ st.write(url)
53
+ with st.expander("Gefundene Übersichtsseiten:"):
54
+ for url in overview_pages:
55
+ st.write(url)
56
+
57
+ # Update DB entry
58
+ new_values = {"$set": {"crawled": True}}
59
+ if overview_pages:
60
+ new_values["$set"]["overview_pages"] = list(overview_pages)
61
+ if sub_urls:
62
+ item["sub_urls"] = sub_urls
63
+ new_values["$set"]["sub_urls"] = sub_urls
64
+
65
+ db.unsorted_urls.update_one({"_id":item["_id"]}, new_values)
66
+ print(db.unsorted_urls.find_one({"_id":item["_id"]}))
67
+
68
+ db = init_connection()
69
+
70
+ # content
71
+ st.title("Event-Urls-Suche mit Crawler und Google API")
72
+ st.write("""
73
+ Wähle aus für wie viele Urls der **Crawler** gestartert werden soll. Diese werden zufällig aus den noch nicht gecrawlten Urls aus der DB ausgewählt.
74
+ Wenn **"Google Maps Ergebnisse finden"** aktiviert ist, werden bei den Stadtportalen zusätzlich noch neue Veranstaltungsorte gesucht.""")
75
+ with st.form("Crawler Settings"):
76
+ count = st.number_input("Wie viele URLs sollen gecrawled werden?", step=1)
77
+ maps = st.checkbox("Google Maps Ergebnisse finden",disabled=True)
78
+ st.info("Aktuell können keine neuen Start-URLs generiert werden. Billing für GCP fehlt.")
79
+ # Every form must have a submit button.
80
+ submitted = st.form_submit_button("Starte Crawler")
81
+ if submitted:
82
+ for i in range(count):
83
+ item = db.unsorted_urls.find_one({"crawled": None })
84
+ with st.expander(f"Ergebnisse für {item['url']} in {item['meta']['location']}"):
85
+
86
+ if item["url_type"] == "city" and maps:
87
+ for type_id in random.sample(MAPS_TYPES, 5):
88
+ print(item)
89
+ if "maps_searches" not in item or "maps_searches" in item and type_id not in item["maps_searches"]:
90
+ st.info(f"Suche Maps Ergebnisse für {type_id} in {item['meta']['location']}")
91
+ maps_results = get_maps_results(type_id, item["meta"]["location"])
92
+ if maps_results:
93
+ new_elements = []
94
+ with st.expander("Maps Ergebnisse"):
95
+ for result in maps_results:
96
+ if result.website_uri \
97
+ and "facebook" not in result.website_uri \
98
+ and "instagram" not in result.website_uri \
99
+ and "tiktok" not in result.website_uri \
100
+ and result.website_uri not in [e["url"] for e in new_elements]:
101
+ element = {
102
+ "url_type": type_id,
103
+ "url": result.website_uri,
104
+ "meta":{
105
+ "website_host": result.display_name.text,
106
+ "location": result.formatted_address.split(", ")[1],
107
+ "address": result.formatted_address,
108
+ "maps_types": list(result.types)
109
+ }}
110
+ st.write(f"{element['meta']['website_host']} - {element['url']}")
111
+ new_elements.append(element)
112
+ if new_elements:
113
+ db.unsorted_urls.insert_many(new_elements)
114
+
115
+ if "maps_searches" in item:
116
+ maps_searches = item["maps_searches"]
117
+ maps_searches.append(type_id)
118
+ item["maps_searches"] = maps_searches
119
+ else:
120
+ item["maps_searches"] = [type_id]
121
+ else:
122
+ st.success("Maps Ergebnisse bereits in DB")
123
+
124
+ crawl(item)
125
+
126
+
127
+
128
+
129
+
130
+
131
+
pages/2_Get_Event_Data.py ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from src.crawler.CrawlerV2 import *
3
+ from bs4 import BeautifulSoup
4
+ from src.utils.apis.gpt_api import classify_text
5
+ import random
6
+
7
+ from src.utils.helpers import clean_html, strip_html_to_text
8
+ from src.utils.markdown_processing.md_preprocessing import convert_html_to_md
9
+ from src.persistence.db import init_db
10
+ from lxml import etree
11
+ import streamlit_nested_layout
12
+
13
+ @st.cache_resource
14
+ def init_connection():
15
+ return init_db()
16
+
17
+ def get_html(url:str):
18
+ response = requests.get(url)
19
+ if response.status_code >= 400:
20
+ print(f"Skipping {overview_url} with status code {response.status_code}")
21
+ return None
22
+ else:
23
+ return response.content
24
+
25
+ def process_url(url:str, el):
26
+ try:
27
+ page_content = get_html(url)
28
+ if page_content:
29
+ cleaned_html = clean_html(page_content)
30
+ cleaned_text = strip_html_to_text(cleaned_html)
31
+ md = convert_html_to_md(cleaned_html)
32
+ gpt_class = classify_text(md)
33
+ with st.expander(url):
34
+ st.write("Bereinigtes HTML:")
35
+ with st.container(border=True, height=400):
36
+ st.markdown(md)
37
+ st.write(f"GPT Klasse: {gpt_class['class']}")
38
+
39
+ if gpt_class["class"] != "None":
40
+ new_element = {
41
+ "base_url_id": el["_id"],
42
+ "base_url": el["url"],
43
+ "url": url,
44
+ "html": page_content,
45
+ "cleaned_html": cleaned_html,
46
+ "cleaned_text": cleaned_text,
47
+ "class": gpt_class["class"]
48
+ }
49
+ db.event_urls.update_one(
50
+ {"url": new_element["url"]},
51
+ {"$setOnInsert": new_element},
52
+ upsert=True
53
+ )
54
+ return new_element
55
+ except Exception as e:
56
+ st.error(f"Es is ein Fehler aufgetreten, die url {url} wird übersprungen\n Fehlermeldung: {e}")
57
+
58
+
59
+ db = init_connection()
60
+
61
+ suburls_count = 10
62
+
63
+ # content
64
+ st.title("Generieren von Event Daten")
65
+ st.write("""
66
+ Hier werden die potentiellen Übersichtsseiten aus unsorted_urls sowie alle Links auf dieser Seite mithilfe der GPT API überprüft und den beiden Klassen **"EventDetail"** und **"EventOverview"** zugeordnet. Die Event Daten werden dann als neues Objekt in event_urls gespeichert.""")
67
+ st.info(
68
+ f"Es werden immer nur max. {suburls_count} Suburls der Übersichtsseiten angeschaut, damit die Daten ausgeglichen bleiben")
69
+ counter = st.number_input("Wie viele der Urls sollen insgesamt überprüft werden?", step=1)
70
+ get_event_data = st.button("Event Daten sammeln")
71
+
72
+ if get_event_data:
73
+ for i in range(counter):
74
+ el = db.unsorted_urls.find_one({"overview_pages": { "$exists": True, "$ne": [] } , "checked": None})
75
+ print(el)
76
+ if el:
77
+ with st.container(border=True):
78
+ if counter <= 0:
79
+ break
80
+ st.subheader(f"Daten sammeln für {el['url']} mit {len(el['overview_pages'])} Übersichtsseiten")
81
+ update_element = el
82
+
83
+ for overview_url in el["overview_pages"]:
84
+
85
+ st.info(f"Überprüfe Übersichtsseite: {overview_url}")
86
+
87
+ new_element=process_url(overview_url,el)
88
+ if new_element:
89
+ soup = BeautifulSoup(new_element["cleaned_html"],"lxml")
90
+ links = soup.find_all(["a"])
91
+ urls = set()
92
+ with st.expander("Suburls"):
93
+ try:
94
+ for link in links:
95
+ href = link["href"]
96
+ url = urljoin(overview_url, href)
97
+ url = urlparse(url)._replace(query="", fragment="").geturl()
98
+ if overview_url != url and check_regex(url, PATTERNS) and str(urlparse(overview_url).scheme)+str(urlparse(overview_url).netloc) != url:
99
+ urls.add(url)
100
+ except:
101
+ print("Exception while processing links")
102
+ if len(urls) > suburls_count:
103
+ urls= set(random.sample(list(urls), 10))
104
+ for url in urls:
105
+ new_element = process_url(url, el)
106
+ if not urls:
107
+ st.info("Es wurden keine Eventseiten unter der Übersichtsseite gefunden")
108
+ update_element["overview_pages"].remove(overview_url)
109
+
110
+ else:
111
+ update_element["overview_pages"].remove(overview_url)
112
+ counter = counter - 1
113
+ if counter <= 0:
114
+ break
115
+ new_values = {"$set": {"overview_pages": update_element["overview_pages"], "checked": True }}
116
+ db.unsorted_urls.update_one({"_id": update_element["_id"]}, new_values )
117
+
118
+
119
+
120
+
121
+
pages/3_Sort_Event_Data.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from src.utils.helpers import clean_html
2
+ from src.utils.markdown_processing.md_preprocessing import convert_html_to_md
3
+ from src.persistence.db import *
4
+ from src.utils.apis.gpt_api import remove_boilerplate
5
+
6
+
7
+ @st.cache_resource
8
+ def init_connection():
9
+ return init_db()
10
+
11
+
12
+ def render_url_content(element):
13
+ cleaned_html = clean_html(element["html"])
14
+ md = convert_html_to_md(cleaned_html)
15
+ with st.container(border=True, height=400):
16
+ st.markdown(md)
17
+
18
+ def save_event_url():
19
+ data = None
20
+ if current_element["class"] == "EventDetail":
21
+ md = convert_html_to_md(clean_html(current_element["html"]))
22
+ data = remove_boilerplate(md)
23
+ result = db.event_urls.update_one({"_id": current_element["_id"]}, { "$set": { "final": True, "data": data} })
24
+
25
+ def remove_url():
26
+ result = db.event_urls.delete_one({"_id": current_element["_id"]}),
27
+
28
+ # Variables
29
+ db = init_connection()
30
+ current_element = db.event_urls.find_one(filter={"final": None})
31
+
32
+ if current_element:
33
+ current_url = current_element['url']
34
+
35
+ # Page Content
36
+ st.header("Event Daten Sortieren")
37
+ st.subheader(f"{db.event_urls.count_documents({'final':None})} URLs sind noch unsortiert")
38
+ st.write("""
39
+ Hier wird das Datenset endgültig bereinigt. Wenn die von der GPT API zugeordnete Klasse (EventDetail / EventOverview)
40
+ falsch ist, muss die URL gelöscht werden. Wenn es korrekt zugeordnet ist können die Daten gespeichert werden. \n
41
+ **ACHTUNG** Teilweise sind die Daten unvollständig. Das liegt daran, dass das HTML gekürzt wurde,
42
+ für die Sortierung ist das irrelvant, also auch abgeschnittene Events gehören in die Event-DB.\n
43
+ **Übersichtsseiten müssen Listen von Events enthalten. Eine Seite mit Kategorien oder anderen Links ist keine Übersichtsseite.**
44
+ """)
45
+ st.info("Es sollen nur deutsche Texte verarbeitet werden. Alle anderen Texte müssen gelöscht werden. (Teilweise englisch ist okay)")
46
+ st.write("")
47
+ try:
48
+ st.write(f"""### Aktuelle Seite: \n{current_url} ({db.unsorted_urls.find_one(filter={"_id": current_element["base_url_id"]}, projection={"url_type":1})["url_type"]})""")
49
+ st.write(f"""#### Predicted Class: {current_element["class"]}""")
50
+ render_url_content(current_element)
51
+ except Exception as e:
52
+ st.write(f"Fehler: {e}")
53
+ st.write(current_url)
54
+ # Buttons
55
+ col1, col2= st.columns([1, 1])
56
+
57
+
58
+ with col1:
59
+ st.button("Als Event-URL speichern", on_click=save_event_url)
60
+ with col2:
61
+ st.button("URL löschen", on_click=remove_url)
62
+
63
+ else:
64
+ st.write("Es sind aktuell keine Daten in der DB zur Berarbeitung vorhanden.")
65
+
66
+
67
+
pages/4_Control.py ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ from src.utils.helpers import clean_html
3
+ from src.utils.markdown_processing.md_preprocessing import convert_html_to_md
4
+ from src.nlp.playground.pipelines.title_extractor import TitleExtractor
5
+ from src.utils.helpers import normalize_data
6
+ from src.persistence.db import *
7
+ from src.utils.apis.gpt_api import remove_boilerplate
8
+ import torch
9
+
10
+
11
+ @st.cache_resource
12
+ def init_connection():
13
+ return init_db()
14
+
15
+ def remove_url():
16
+ result = db.event_urls.delete_one({"_id": current_element["_id"]})
17
+ st.session_state.elements = db.event_urls.find({"final":True, "class": "EventDetail"},{"_id":1, "url":1, "data":1, "html":1, "information":1})
18
+
19
+ def next():
20
+ db.event_urls.update_one({"_id": current_element["_id"]}, { "$set": { "information":event_information } })
21
+ st.session_state.index+=1
22
+
23
+ def prev():
24
+ st.session_state.index-=1
25
+
26
+ # Variables
27
+ db = init_connection()
28
+ if "index" not in st.session_state:
29
+ st.session_state.index = 0
30
+ if "elements" not in st.session_state:
31
+ elements = db.event_urls.find({"final":True, "class": "EventDetail"},{"_id":1, "url":1, "data":1, "html":1, "information":1})
32
+
33
+ # preprocessing of html content: get cleaned markdown
34
+ for el in elements:
35
+ if "data" not in el:
36
+ print(el["url"])
37
+ md = convert_html_to_md(clean_html(el["html"]))
38
+ try:
39
+ st.info("GPT-API Anfrage läuft")
40
+ gpt_md = remove_boilerplate(md)
41
+ st.info("Verarbeitung beendet")
42
+ el["data"] = gpt_md
43
+ db.event_urls.update_one({"_id": el["_id"]}, { "$set": { 'data': el["data"] } })
44
+ except Exception as e:
45
+ st.error(f"Es ist ein Fehler aufgetreten: {e} \n")
46
+ db.event_urls.delete_one({"_id": el["_id"]})
47
+ st.session_state.elements = db.event_urls.find({"final":True, "class": "EventDetail"},{"_id":1, "url":1, "data":1, "html":1, "information":1})
48
+ if "predictions_on" not in st.session_state:
49
+ st.session_state.predictions_on = False
50
+
51
+
52
+ current_element = st.session_state.elements[st.session_state.index]
53
+
54
+ predictions_on = st.toggle("Predictions an (Zeigt Extrahierte Daten an, die Seite lädt dadurch langsamer).")
55
+ if predictions_on != st.session_state.predictions_on:
56
+ st.session_state.predictions_on = predictions_on
57
+
58
+ if current_element:
59
+ current_url = current_element['url']
60
+
61
+ try:
62
+ st.write(f"""### Aktuelle Seite: \n{current_url} """)
63
+ if "data" not in current_element:
64
+ md = convert_html_to_md(clean_html(current_element["html"]))
65
+ try:
66
+ gpt_md = remove_boilerplate(md)
67
+ current_element["data"] = gpt_md
68
+ db.event_urls.update_one({"_id": current_element["_id"]}, { "$set": { 'data': current_element["data"] } })
69
+ except Exception as e:
70
+ st.error(f"Es ist ein Fehler aufgetreten: {e} \nDer Datenbankeintrag wird gelöscht.")
71
+ db.event_urls.delete_one({"_id": current_element["_id"]})
72
+ data = current_element["data"]
73
+ normalized_text = normalize_data(data)
74
+ predicted_title = None
75
+ predicted_date = None
76
+ if st.session_state.predictions_on:
77
+ predicted_title = TitleExtractor().extract_title(normalized_text)
78
+ # predicted_date = extract_entities(normalized_text, ["date", "date_range"])
79
+ # predicted_date = [ {d["text"],d["label"]} for d in predicted_date ] if predicted_date else None
80
+ st.subheader("Normalisierte Daten:")
81
+ with st.container(border=True, height=400):
82
+ st.markdown(normalized_text)
83
+ with st.expander("Code ansehen"):
84
+ with st.container( height=400):
85
+ st.code(normalized_text)
86
+ actual_title = st.text_input("Tatsächlicher Titel eingeben:", key="title"+ str(current_element["_id"]),
87
+ value=current_element.get("information", {}).get("actual", {}).get("title", None))
88
+ actual_date = None
89
+
90
+ event_information = {"actual": {"title":actual_title}}
91
+ data = {
92
+ "Information": [
93
+ "Titel",
94
+ # "Datum"
95
+ ],
96
+ "Tatsächlicher Wert":
97
+ [
98
+ actual_title,
99
+ # actual_date
100
+ ],
101
+ "Predicted Wert": [
102
+ predicted_title,
103
+ # predicted_date
104
+ ],
105
+ }
106
+ df = pd.DataFrame(data)
107
+
108
+ st.subheader("Vergleich der Titel:")
109
+ st.table(df)
110
+
111
+ except Exception as e:
112
+ st.write(f"Fehler: {e}")
113
+ st.write(current_url)
114
+ col1, col2, col3, col4= st.columns([1, 1, 1, 1])
115
+
116
+
117
+ with col1:
118
+ st.button("Zurück", on_click=prev)
119
+ with col3:
120
+ st.button("URL löschen", on_click=remove_url)
121
+ with col4:
122
+ st.button("Speichern und Weiter",on_click=next)
123
+
124
+ else:
125
+ st.write("Es sind aktuell keine Daten in der DB zur Bearbeitung vorhanden.")
126
+
127
+
128
+
pages/5_Playground.py ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import logging
3
+ import os
4
+ import sys
5
+ import gc
6
+ import psutil
7
+ import streamlit as st
8
+ import pandas as pd
9
+ st.info(f"Speicherauslastung vor imports: {psutil.virtual_memory().percent}%. Keys in Cache: {[k for k in st.session_state]}")
10
+
11
+
12
+ from src.configuration.config import SessionStateConfig
13
+ from src.nlp.playground.textsummarization import SumySummarizer
14
+ from src.nlp.playground.pipelines.title_extractor import TitleExtractor
15
+ from src.utils.helpers import normalize_data
16
+ from src.utils.markdown_processing.CustomMarkdownAnalyzer.MarkdownAnalyzer import MarkdownAnalyzer
17
+ from src.nlp.playground.llm import QwenLlmHandler
18
+ from src.nlp.playground.ner import GlinerHandler
19
+ from src.persistence.db import init_db
20
+ from src.nlp.playground.textclassification import ZeroShotClassifier, CategoryMode, CustomMode
21
+
22
+ entities_schema = [
23
+ '"title": str | None"',
24
+ '"organizer": str | None"',
25
+ '"startDate": str | None"',
26
+ '"endDate": str | None"',
27
+ '"startTime": str | None"',
28
+ '"endTime": str | None"',
29
+ '"admittanceTime": str | None"',
30
+ '"locationName": str | None"',
31
+ '"street": str | None"',
32
+ '"houseNumber": str | None"',
33
+ '"postalCode": str | None"',
34
+ '"city": str | None"',
35
+ '"price": list[float] | None"',
36
+ '"currency": str | None"',
37
+ '"priceFree": bool | None"',
38
+ '"ticketsRequired": bool | None"',
39
+ '"categories": list[str] | None"',
40
+ '"eventDescription": str | None"',
41
+ '"accesibilityInformation": str | None"',
42
+ '"keywords": list[str] | None"'
43
+ ]
44
+
45
+
46
+ @st.cache_resource
47
+ def init_connection():
48
+ return init_db()
49
+
50
+ @st.cache_resource
51
+ def init_data():
52
+ return db.event_urls.find(filter={"class":"EventDetail", "final":True}, projection={"url":1,"base_url_id":1,"cleaned_html":1, "data":1})
53
+
54
+ def render_md(md):
55
+ st.subheader("Original Text:")
56
+ with st.container(border=True, height=400):
57
+ st.markdown(md)
58
+
59
+ def render_table(table_data):
60
+ st.subheader("Extrahierte Daten:")
61
+ df = pd.DataFrame(table_data)
62
+ st.table(df)
63
+ st.markdown("---")
64
+
65
+ def init_session_state(key, value):
66
+ if key not in st.session_state:
67
+ clear_st_cache()
68
+ st.session_state[key] = value
69
+
70
+ def clear_st_cache():
71
+ keys = list(st.session_state.keys())
72
+ for key in keys:
73
+ st.session_state.pop(key)
74
+
75
+
76
+ db = init_connection()
77
+ data = init_data()
78
+
79
+ st.info(f"Speicherauslastung: {psutil.virtual_memory().percent}%. Keys in Cache: {[k for k in st.session_state]}")
80
+
81
+
82
+ with st.expander("Large Language Models"):
83
+ with st.form("Settings LLM"):
84
+ count = st.number_input("Wie viele Veranstaltungen sollen gestest werden?", step=1)
85
+ st.write("Welche Informationen sollen extrahiert werden?")
86
+ options = []
87
+ for ent in entities_schema:
88
+ option = st.checkbox(ent ,key=ent)
89
+ options.append(option)
90
+ submit_llm = st.form_submit_button("Start")
91
+
92
+ if submit_llm:
93
+ selected_entities = [entity for entity, selected in zip(entities_schema, options) if selected]
94
+ init_session_state(SessionStateConfig.QWEN_LLM_HANDLER, QwenLlmHandler())
95
+ qwen_llm_handler = st.session_state[SessionStateConfig.QWEN_LLM_HANDLER]
96
+ try:
97
+ for event in data:
98
+ extracted_data = qwen_llm_handler.extract_data(text=event["data"], entities= ", ".join(selected_entities))
99
+ table_data = [{"Key": key, "Value": value} for key, value in extracted_data.items()]
100
+
101
+ render_md(event["data"])
102
+ render_table(table_data)
103
+
104
+ count -= 1
105
+ if count == 0:
106
+ break
107
+ except Exception as e:
108
+ st.write(f"Es ist ein Fehler aufgetreten: {e}")
109
+
110
+ with st.expander("Named Entity Recognition"):
111
+ with st.form("Settings NER"):
112
+ count = st.number_input("Wie viele Veranstaltungen sollen gestest werden?", step=1)
113
+ label_input = st.text_input("Gebe die Labels der Entitäten getrennt durch Komma an.")
114
+ submit_ner = st.form_submit_button("Start")
115
+
116
+ if submit_ner:
117
+ init_session_state(SessionStateConfig.GLINER_HANDLER, GlinerHandler())
118
+ gliner_handler = st.session_state[SessionStateConfig.GLINER_HANDLER]
119
+ if label_input:
120
+ labels = label_input.split(",")
121
+ for event in data:
122
+ text = normalize_data(event["data"])
123
+ render_md(text)
124
+
125
+ extracted_data = gliner_handler.extract_entities(text, labels)
126
+ table_data = [{"Key": element["label"], "Value": element["text"] } for element in extracted_data]
127
+ render_table(table_data)
128
+
129
+ count -= 1
130
+ if count == 0:
131
+ break
132
+
133
+ with st.expander("Textclassification"):
134
+ with st.form("Settings TextClassification"):
135
+ mode = st.selectbox("Classification Mode", ["Categories", "Custom"])
136
+ custom_labels = st.text_input("(Nur bei Custom Mode) Gib die Klassen Labels ein, durch Komma getrennt.", placeholder="Theater,Oper,Film")
137
+ custom_hypothesis_template = st.text_input("(Nur bei Custom Mode) Gib das Template ein. {} ist dabei der Platzhalter für die Labels", placeholder="Die Art der Veranstaltung ist {}")
138
+ count = st.number_input("Wie viele Veranstaltungen sollen gestest werden?", step=1)
139
+ submit_textclass = st.form_submit_button("Start")
140
+
141
+ if submit_textclass:
142
+ init_session_state(SessionStateConfig.ZERO_SHOT_CLASSIFIER, ZeroShotClassifier())
143
+ classifier = st.session_state[SessionStateConfig.ZERO_SHOT_CLASSIFIER]
144
+ if mode == "Categories":
145
+ classifier_mode = CategoryMode()
146
+ elif custom_labels and custom_hypothesis_template:
147
+ classifier_mode = CustomMode(labels=custom_labels.split(","), hypothesis_template=custom_hypothesis_template)
148
+ for event in data:
149
+ text = normalize_data(event["data"])
150
+ predictions = classifier.classify(text, classifier_mode)
151
+ table_data = [{"Kategorie": p.label, "Score": p.score} for p in predictions]
152
+
153
+ render_md(text)
154
+ render_table(table_data)
155
+
156
+ count -= 1
157
+ if count == 0:
158
+ break
159
+
160
+ with st.expander("Titel Extraktion"):
161
+ with st.form("Settings TitleExtraction"):
162
+ count = st.number_input("Wie viele Veranstaltungen sollen gestest werden?", step=1)
163
+ submit_title_extr = st.form_submit_button("Start")
164
+
165
+ if submit_title_extr:
166
+ init_session_state("title_extractor", TitleExtractor())
167
+ title_extractor = st.session_state.title_extractor
168
+ st.info(f"Speicherauslastung: {psutil.virtual_memory().percent}%. Keys in Cache: {[k for k in st.session_state]}")
169
+
170
+ for event in data:
171
+ text = normalize_data(event["data"])
172
+ prediction = title_extractor.extract_title(text)
173
+ try:
174
+ pred2 = title_extractor.extract_title_classy_classification(text)
175
+ except FileNotFoundError as e:
176
+ pred2 = "ERROR: Train Model before usage"
177
+ table_data = [{"Label": "Titel (ZeroShot)", "Value": prediction}, {"Label": "Titel (FewShot)", "Value": pred2}]
178
+
179
+ render_md(text)
180
+ render_table(table_data)
181
+
182
+ count -= 1
183
+ if count == 0:
184
+ break
185
+
186
+ with st.expander("Textsummarization"):
187
+ with st.form("Settings Textsummarization"):
188
+ count = st.number_input("Wie viele Veranstaltungen sollen gestest werden?", step=1)
189
+ submit_textsummarization = st.form_submit_button("Start")
190
+
191
+ if submit_textsummarization:
192
+ init_session_state(SessionStateConfig.SUMY_SUMMARIZER, SumySummarizer())
193
+ sumy_summarizer = st.session_state[SessionStateConfig.SUMY_SUMMARIZER]
194
+ st.info(f"Speicherauslastung: {psutil.virtual_memory().percent}%. Keys in Cache: {[k for k in st.session_state]}")
195
+ for event in data:
196
+ try:
197
+ md = normalize_data(event["data"])
198
+ md_analyzer = MarkdownAnalyzer(md).identify_all()["block_elements"]
199
+ md_analyzer = sorted(md_analyzer, key=lambda el: el.line)
200
+ text = "\n\n".join([el.text for el in md_analyzer])
201
+ sumy_summary = sumy_summarizer.summarize(text)
202
+ summary = []
203
+ for element in md_analyzer:
204
+ if any(sentence in element.markdown for sentence in sumy_summary):
205
+ summary.append(element.markdown)
206
+
207
+ render_md(md)
208
+ st.subheader("Extrahierte Daten:")
209
+ with st.container(border=True, height=400):
210
+ st.markdown("\n\n".join(summary))
211
+
212
+ except Exception as e:
213
+ st.error(f"Fehler:{e}")
214
+ logging.exception("message")
215
+ count -= 1
216
+ if count == 0:
217
+ break
pages/6_Pipeline.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import streamlit.components.v1 as components
3
+
4
+ from src.configuration.config import SessionStateConfig
5
+ from src.nlp.playground.Event import Event
6
+ from src.nlp.playground.ner import GlinerHandler
7
+ from src.nlp.playground.pipelines.description_extractor import DescriptionExtractor
8
+ from src.nlp.playground.textclassification import ZeroShotClassifier, CategoryMode, CustomMode
9
+ from src.nlp.playground.pipelines.title_extractor import TitleExtractor
10
+ from src.persistence.db import init_db
11
+ from src.utils.apis.gpt_api import remove_boilerplate
12
+ from src.utils.helpers import normalize_data
13
+ from src.utils.markdown_processing.CustomMarkdownAnalyzer.MarkdownAnalyzer import MarkdownAnalyzer
14
+ from src.utils.markdown_processing.md_preprocessing import convert_html_to_md
15
+
16
+ @st.cache_resource
17
+ def init_connection():
18
+ return init_db()
19
+
20
+ @st.cache_resource
21
+ def init_data():
22
+ return db.event_urls.find(filter={"class":"EventDetail", "final":True}, projection={"url":1,"base_url_id":1,"cleaned_html":1, "data":1})
23
+
24
+
25
+ if "title_extractor" not in st.session_state:
26
+ st.session_state["title_extractor"] = TitleExtractor()
27
+ if "description_extractor" not in st.session_state:
28
+ st.session_state["description_extractor"] = DescriptionExtractor()
29
+ if SessionStateConfig.ZERO_SHOT_CLASSIFIER not in st.session_state:
30
+ st.session_state[SessionStateConfig.ZERO_SHOT_CLASSIFIER] = ZeroShotClassifier()
31
+ if SessionStateConfig.GLINER_HANDLER not in st.session_state:
32
+ st.session_state[SessionStateConfig.GLINER_HANDLER] = GlinerHandler()
33
+
34
+ db = init_connection()
35
+ data = init_data()
36
+ element = next(data,None)
37
+
38
+
39
+ st.subheader("Bereinigtes HTML")
40
+ st.write(element["url"])
41
+ st.components.v1.html(element["cleaned_html"], height=500, scrolling=True)
42
+
43
+ start_pipeline = st.button("Starte Pipeline")
44
+ if element:
45
+ title_extractor = st.session_state.title_extractor
46
+ description_extractor = st.session_state.description_extractor
47
+ classifier = st.session_state[SessionStateConfig.ZERO_SHOT_CLASSIFIER]
48
+ gliner_handler = st.session_state.gliner_handler
49
+ if start_pipeline:
50
+ html = element["cleaned_html"]
51
+ md = convert_html_to_md(html)
52
+ with st.expander("Markdown"):
53
+ with st.container(border=True, height=400):
54
+ st.markdown(md)
55
+ with st.expander("Markdown Code"):
56
+ with st.container(height=400):
57
+ st.code(md)
58
+
59
+ cleaned_md = remove_boilerplate(md)
60
+ st.info("Remove boilerplate with GPT API")
61
+ with st.expander("Gekürztes Markdown"):
62
+ with st.container(border=True, height=400):
63
+ st.markdown(cleaned_md)
64
+
65
+ normalized_md = normalize_data(cleaned_md)
66
+ with st.expander("Normalisiertes Markdown"):
67
+ with st.container(border=True, height=400):
68
+ st.markdown(normalized_md)
69
+
70
+
71
+ text = normalized_md
72
+ analyzer = MarkdownAnalyzer(text)
73
+ results = analyzer.identify_all()["block_elements"]
74
+ table_data = [{"Class": r.__class__.__name__, "Markdown": r.markdown} for r in results]
75
+ with st.expander("Markdown Elemente"):
76
+ st.table(table_data)
77
+
78
+ with st.expander("Markdown Segmente"):
79
+ segments = analyzer.segmentation()
80
+ for s in segments:
81
+ with st.container(border=True):
82
+ for e in s:
83
+ st.markdown(e.markdown)
84
+
85
+ extracted_event = Event()
86
+
87
+ st.info("Extracting title...")
88
+ extracted_event.title = title_extractor.extract_title(cleaned_md)
89
+
90
+ # extracted_event.categories = ZeroShotClassifier().classify(text, CategoryMode())
91
+ extracted_event.categories = []
92
+ st.info("Extracting Categories...")
93
+ family_category = [cat.label for cat in classifier.classify(text,CustomMode(["Kinder_und_Familie","Adults_only"],"Die Veranstaltung ist für {}")) if cat.score >= 0.8]
94
+ topic_category = [classifier.classify(text,CustomMode(
95
+ ["Kunst","Kultur", "Musik", "Sport", "Bildung", "Tanz", "Wissenschaft", "Unterhaltung", "Gesundheit", "Wellness", "Business", "Politik","Religion"],
96
+ "In der Veranstaltung geht es um {}"))[0].label]
97
+ type_category = [classifier.classify(text,CustomMode(
98
+ ["Oper", "Theater", "Konzert", "Gottesdienst", "Ausstellung", "Museum", "Planetarium", "Führung", "Film", "Vortrag", "Show", "Turnier", "Wettkampf", "Markt", "Feier", "Party"],
99
+ "Die Art der Veranstaltung ist {}"))[0].label]
100
+
101
+ extracted_event.categories.extend(family_category)
102
+ extracted_event.categories.extend(topic_category)
103
+ extracted_event.categories.extend(type_category)
104
+
105
+ st.info("Extracting Organizer and Location...")
106
+ entities = gliner_handler.extract_entities(text, ["EVENT_ORGANIZER","EVENT_LOCATION_NAME","EVENT_ADDRESS"])
107
+ extracted_event.organizers = list(set([item["text"] for item in entities if item["label"] == "EVENT_ORGANIZER"]))
108
+ extracted_event.locations = list(set([item["text"] for item in entities if item["label"] == "EVENT_LOCATION_NAME"]))
109
+ extracted_event.address = list(set([item["text"] for item in entities if item["label"] == "EVENT_ADDRESS"]))
110
+ st.info("Extracting Dates and Times...")
111
+ date_entities = gliner_handler.extract_entities(text, ["SINGLE_DATE", "DATE_RANGE", "START_TIME","END_TIME", "ADMITTANCE_TIME"])
112
+ st.write(date_entities)
113
+ st.info("Extracting Price Information...")
114
+ price_entities = gliner_handler.extract_entities(text, ["KOSTEN"])
115
+ extracted_event.prices = list(set([item["text"] for item in price_entities if any(char.isdigit() for char in item["text"])]))
116
+
117
+ st.info("Extracting Description...")
118
+ extracted_event.description = description_extractor.extract_description(text, extracted_event.title)
119
+
120
+ event_data = []
121
+ event_data.append({'Field': 'Title', 'Value': extracted_event.title})
122
+ event_data.append({'Field': 'Categories', 'Value': ", ".join(extracted_event.categories)})
123
+ event_data.append({'Field': 'Organizers', 'Value': ", ".join(extracted_event.organizers)})
124
+ event_data.append({'Field': 'Locations', 'Value': ", ".join(extracted_event.locations)})
125
+ event_data.append({'Field': 'Address', 'Value': ", ".join(extracted_event.address)})
126
+ event_data.append({'Field': 'Prices', 'Value': ", ".join(extracted_event.prices)})
127
+
128
+
129
+ st.subheader("Extrahierte Daten")
130
+ st.table(event_data)
131
+ st.write("Event Description:")
132
+ with st.container(border=True, height=400):
133
+ st.markdown(extracted_event.description)
134
+
135
+
136
+
pages/TEST.py DELETED
@@ -1,2 +0,0 @@
1
- import streamlit as st
2
- st.write("Hello world")
 
 
 
requirements.txt CHANGED
@@ -1 +1,23 @@
1
- streamlit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ streamlit
2
+ streamlit-nested-layout
3
+ html2text
4
+ httpx
5
+ bs4
6
+ sumy
7
+ gliner
8
+ markdownify
9
+ google-maps-places
10
+ openai
11
+ dateparser
12
+ lxml_html_clean
13
+ pandas
14
+ pymongo
15
+ absl-py
16
+ dotenv
17
+ transformers
18
+
19
+
20
+
21
+
22
+
23
+
src/__init__.py ADDED
File without changes
src/configuration/__init__.py ADDED
File without changes
src/configuration/config.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # streamlit session state model keys
2
+
3
+ class SessionStateConfig:
4
+ ZERO_SHOT_CLASSIFIER = "zero_shot_classifier"
5
+ SUMY_SUMMARIZER = "sumy_summarizer"
6
+ GLINER_HANDLER = "gliner_handler"
7
+ QWEN_LLM_HANDLER = "qwen_llm_handler"
src/crawler/CrawlerV2.py ADDED
@@ -0,0 +1,215 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gzip
2
+ import sys
3
+
4
+ import httpx
5
+ from urllib.parse import urljoin
6
+
7
+ from src.crawler.utils.regEx import PATTERNS
8
+ from src.crawler.utils.keywords import KEYWORDS
9
+ from src.crawler.crawler_service import *
10
+ from urllib.robotparser import RobotFileParser
11
+
12
+ URL_KEYWORDS = [
13
+ "veranstaltung", "event", "kalender", "kunst", "kultur",
14
+ "freizeit", "termine",
15
+ "happenings", "ausgehen", "aktivitäten", "aktivitaeten", "programm",
16
+ "wochenendtipps", "party", "festivals", "konzerte", "musik",
17
+ "shows", "theater", "veranstaltungskalender", "ausstellungen", "feste", "spielplan", "veranstaltungsplan"
18
+ ]
19
+
20
+
21
+ class Crawler:
22
+ sys.path.append("..")
23
+ # filter variables
24
+ keywords = KEYWORDS
25
+ url_patterns = PATTERNS
26
+
27
+ def __init__(self, url: str, url_type: str, depth: int):
28
+
29
+ self.visited_urls = set()
30
+ self.excluded_urls = set()
31
+ self.excluded_urls.update(set(get_disallowed_urls(url)))
32
+ self.url_type = url_type
33
+ # set tupel of url and max depth to crawl
34
+ self.queue = [(url, depth)]
35
+ self.domain = urlparse(url).netloc
36
+ self.sitemaps_urls = get_sitemaps(url)
37
+ print(self.url_type)
38
+ print(f"Crawler startet for {url}")
39
+
40
+
41
+ def crawl(self):
42
+ # Loop through the URLs in the queue until it is empty
43
+ if self.sitemaps_urls:
44
+ print("Sitemaps Crawler startet...")
45
+ for url in self.sitemaps_urls:
46
+ if self.include_url(url):
47
+ print("include URL: ", url)
48
+ self.visited_urls.add(url)
49
+ else:
50
+ print("Crawler startet...")
51
+ while self.queue:
52
+ try:
53
+ # get the next URL to crawl
54
+ url_tupels = self.queue.pop(0)
55
+ current_url = url_tupels[0]
56
+ depth = url_tupels[1]
57
+ # access = ask_robots(current_url, "*")
58
+ access = True
59
+ # make request
60
+ if access and depth > 0:
61
+ response = requests.get(current_url)
62
+ if response.status_code >= 400:
63
+ print(f"Skipping {current_url} with status code {response.status_code}")
64
+ continue
65
+ page_content = response.content
66
+
67
+ # Parse the HTML content and extract links to other pages
68
+ soup = BeautifulSoup(page_content, "lxml")
69
+ urls_to_crawl = self.find_urls(soup, current_url)
70
+ urls_to_crawl_tupels = []
71
+ for url in urls_to_crawl:
72
+ if self.include_url(url):
73
+ urls_to_crawl_tupels.append((url, depth - 1))
74
+
75
+ # Add the new URLs to the queue and mark the current URL as visited
76
+ self.queue.extend(urls_to_crawl_tupels)
77
+
78
+
79
+ print(f"Crawled {current_url} and found {len(urls_to_crawl)} new URLs to crawl")
80
+ else:
81
+ print("access denied for ",current_url )
82
+ except Exception as e:
83
+ print("Exception:", e)
84
+
85
+ self.visited_urls.add(current_url)
86
+ if current_url in self.queue:
87
+ self.queue.remove(current_url)
88
+ print("Done. Found ", len(self.visited_urls), " Urls")
89
+ return self.visited_urls
90
+
91
+ def find_urls(self, soup: BeautifulSoup, current_url: str):
92
+ # get all links from page content
93
+ links = soup.find_all("a", href=True)
94
+ urls_to_crawl = set()
95
+ for link in links:
96
+ href = link["href"]
97
+ url = urljoin(current_url, href)
98
+ url = urlparse(url)._replace(query="", fragment="").geturl()
99
+ urls_to_crawl.add(url)
100
+ return urls_to_crawl
101
+
102
+
103
+ def include_url(self,url) -> bool:
104
+
105
+ if urlparse(url).netloc.lower() != self.domain.lower() \
106
+ or url in self.visited_urls \
107
+ or not check_regex(url, self.url_patterns)\
108
+ or url in self.queue\
109
+ or url in self.excluded_urls:
110
+ return False
111
+ else:
112
+ print("Checking ", url)
113
+ # if self.url_type == "city":
114
+ if any(keyword in url for keyword in URL_KEYWORDS):
115
+ print("Found Event URL:", url)
116
+ return True
117
+ else:
118
+ self.excluded_urls.add(url)
119
+ return False
120
+ # else:
121
+ # # if ask_robots(url,"*"):
122
+ # if True:
123
+ # response = requests.get(url)
124
+ # if response.status_code >= 400:
125
+ # self.excluded_urls.add(url)
126
+ # print(f"Skipping {url} with status code {response.status_code}")
127
+ # return False
128
+ # else:
129
+ # page_content = response.content
130
+ # # Parse the HTML content and extract links to other pages
131
+ # soup = BeautifulSoup(page_content, "html.parser")
132
+ # # remove navigation elements
133
+ # for nav in soup.find_all('nav'):
134
+ # nav.decompose()
135
+ #
136
+ # # Step 2: Remove elements with "navigation" or "menu" in the id or class attributes
137
+ #
138
+ # nav_elements= []
139
+ # nav_elements.extend(soup.find_all(id=re.compile(r'.*navigation.*')))
140
+ # nav_elements.extend(soup.find_all(id=re.compile(r'.*menu.*')))
141
+ # nav_elements.extend(soup.find_all(class_=re.compile(r'.*navigation.*')))
142
+ # nav_elements.extend(soup.find_all(class_=re.compile(r'.*menu.*')))
143
+ #
144
+ # print(len(nav_elements))
145
+ # for elem in nav_elements:
146
+ # if elem:
147
+ # elem.decompose()
148
+ #
149
+ # content = get_page_content(soup)
150
+ # print("searching content for keywords...")
151
+ # if check_keywords(content, self.keywords):
152
+ # print("Found Keyword in ", url)
153
+ # return True
154
+ # else:
155
+ # self.excluded_urls.add(url)
156
+ # return False
157
+
158
+
159
+
160
+ def get_sitemaps(url):
161
+ url_parsed = urlparse(url)
162
+ url_robots_txt = url_parsed.scheme + '://' + url_parsed.netloc + '/robots.txt'
163
+ sitemaps = set()
164
+ all_urls = set()
165
+
166
+ robotParse = urllib.robotparser.RobotFileParser()
167
+ robotParse.set_url(url_robots_txt)
168
+ try:
169
+ robotParse.read()
170
+ robot_sitemaps = robotParse.site_maps()
171
+ if robot_sitemaps:
172
+ sitemaps.update(robot_sitemaps)
173
+ else:
174
+ sitemaps.add(url_parsed.scheme + "://" + url_parsed.netloc + "/sitemap.xml")
175
+ sitemaps.add(url_parsed.scheme + "://" + url_parsed.netloc + "/sitemaps.xml")
176
+
177
+
178
+ for sitemap in sitemaps:
179
+ print("Parsing sitemap:", sitemap)
180
+ sitemap_urls = get_urls_from_sitemap(sitemap, set())
181
+ all_urls.update(sitemap_urls)
182
+
183
+ print("Total urls collected from sitemaps: ", len(all_urls))
184
+ except Exception as e:
185
+ print("Exception while parsing sitemap from ", url, ":", e)
186
+ return all_urls
187
+
188
+ def get_urls_from_sitemap(sitemap, urls):
189
+ print("Getting URLs from sitemap:", sitemap)
190
+ try:
191
+ response = httpx.get(sitemap)
192
+ if response.status_code == httpx.codes.OK:
193
+ # Prüfen, ob die Sitemap eine gezippte Datei ist
194
+ content_type = response.headers.get('Content-Type', '')
195
+ if 'gzip' in content_type or sitemap.endswith('.gz'):
196
+ # Falls gezippt, entpacken und lesen
197
+ decompressed_content = gzip.decompress(response.content)
198
+ soup = BeautifulSoup(decompressed_content, 'lxml')
199
+ else:
200
+ # Falls nicht gezippt, direkt parsen
201
+ soup = BeautifulSoup(response.content, 'lxml')
202
+
203
+ # URLs aus der Sitemap extrahieren
204
+ locs = soup.find_all("loc")
205
+ for loc in locs:
206
+ url = loc.get_text()
207
+ if "sitemap" in url:
208
+ # Rekursiv aufrufen, falls es eine Unter-Sitemap ist
209
+ urls.update(get_urls_from_sitemap(url, urls))
210
+ else:
211
+ urls.add(url)
212
+
213
+ except Exception as e:
214
+ print("Exception while resolving sitemap:", sitemap, "-", e)
215
+ return urls
src/crawler/__init__.py ADDED
File without changes
src/crawler/crawler_service.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import urllib
2
+ import re
3
+ import copy
4
+ from bs4 import BeautifulSoup
5
+ from urllib.parse import urlparse
6
+ from urllib.robotparser import RobotFileParser
7
+ from pathlib import Path
8
+ import requests
9
+
10
+
11
+ root_path = Path.cwd()
12
+ html_filter = ['header', 'footer', 'svg', 'img', 'nav', 'script']
13
+
14
+ # check if crawler is allowed to crawl url
15
+ def ask_robots(url: str, useragent="*") -> bool:
16
+ try:
17
+ url_parsed = urlparse(url)
18
+ url_robots_txt = url_parsed.scheme + '://' + url_parsed.netloc + '/robots.txt'
19
+ print("robots.txt: ", url_robots_txt)
20
+ robotParse = urllib.robotparser.RobotFileParser()
21
+ robotParse.set_url(url_robots_txt)
22
+ robotParse.read()
23
+
24
+ print("Ask access to ", url)
25
+ return robotParse.can_fetch('*', url)
26
+ except Exception as e:
27
+ print("Ask Robots :", e)
28
+
29
+
30
+ def get_disallowed_urls(url, user_agent="*"):
31
+ """
32
+ Gibt alle disallowed URLs für den angegebenen User-Agent aus der robots.txt zurück.
33
+
34
+ :param robots_url: Die URL zur robots.txt-Datei (z. B. "https://example.com/robots.txt").
35
+ :param user_agent: Der User-Agent, für den die Regeln geprüft werden (Standard: "*").
36
+ :return: Eine Liste der disallowed URLs.
37
+ """
38
+ # robots.txt Parser initialisieren
39
+ url_parsed = urlparse(url)
40
+ url_robots_txt = url_parsed.scheme + '://' + url_parsed.netloc + '/robots.txt'
41
+ rp = urllib.robotparser.RobotFileParser()
42
+ rp.set_url(url_robots_txt)
43
+ rp.read()
44
+
45
+ # Liste der disallowed Pfade initialisieren
46
+ disallowed_paths = []
47
+
48
+ # robots.txt-Datei als Text herunterladen
49
+ response = requests.get(url_robots_txt)
50
+ if response.status_code == 200:
51
+ # Parsen der robots.txt
52
+ lines = response.text.splitlines()
53
+ current_user_agent = None
54
+ for line in lines:
55
+ # Leerzeichen und Kommentare ignorieren
56
+ line = line.strip()
57
+ if not line or line.startswith("#"):
58
+ continue
59
+
60
+ # User-Agent-Zeilen erkennen
61
+ if line.lower().startswith("user-agent"):
62
+ current_user_agent = line.split(":")[1].strip()
63
+
64
+ # Disallow-Regeln erkennen
65
+ elif line.lower().startswith("disallow") and current_user_agent == user_agent:
66
+ disallow_path = line.split(":")[1].strip()
67
+ if disallow_path:
68
+ disallowed_paths.append(disallow_path)
69
+
70
+ # Basis-URL extrahieren
71
+ base_url = url_robots_txt.rsplit("/", 1)[0]
72
+
73
+ # Vollständige URLs zurückgeben
74
+ disallowed_urls = [base_url + path for path in disallowed_paths]
75
+ return disallowed_urls
76
+
77
+
78
+ # check if the url matches the regEx pattern, exclude it from crawler if it does
79
+ def check_regex(url: str, url_patterns: dict) -> bool:
80
+ for pattern in url_patterns:
81
+ if pattern.match(url):
82
+ return False
83
+ return True
84
+
85
+
86
+ # exclude url if website contains no keyword
87
+ def check_keywords(content: str, keywords: dict) -> bool:
88
+ for word in keywords:
89
+ if re.search(word, content, flags=re.IGNORECASE):
90
+ return True
91
+ return False
92
+
93
+ # get only the content of the page without html tags
94
+ def get_page_content(soup: BeautifulSoup):
95
+ soup_temp = copy.copy(soup)
96
+ body = soup_temp.find('body')
97
+ if body is None:
98
+ return
99
+ for tag in html_filter:
100
+ for el in body.find_all(tag):
101
+ el.decompose()
102
+ return prettify_content(body.text)
103
+
104
+
105
+ def prettify_content(content: str):
106
+ return re.sub(r'\n\s*\n', '\n', content)
src/crawler/maps_api.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dataclasses import fields
2
+ import streamlit as st
3
+ from google.maps import places_v1
4
+ import os
5
+ from dotenv import load_dotenv
6
+
7
+
8
+ def get_maps_results(query, location):
9
+ # Create a client
10
+ load_dotenv()
11
+ client = places_v1.PlacesClient(client_options={"api_key": os.getenv("GOOGLE_MAPS_API_KEY")})
12
+
13
+ # Initialize request argument(s)
14
+ request = places_v1.SearchTextRequest(
15
+ text_query=f"{query} in {location}",
16
+ included_type=query
17
+ )
18
+
19
+ fieldMask = "places.displayName,places.websiteUri,places.formattedAddress,places.types"
20
+ # Make the request
21
+ response = client.search_text(request=request, metadata=[("x-goog-fieldmask",fieldMask)])
22
+ return response.places
23
+
src/crawler/serp_maps.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from src.configuration.config import SERP_API_KEY
2
+ from serpapi import GoogleSearch
3
+
4
+ # maps type ids
5
+ relevant_locations = [
6
+ "art_gallery",
7
+ "auditorium",
8
+ "museum",
9
+ "performing_arts_theater",
10
+ "amphitheatre",
11
+ "amphitheatre",
12
+ "amusement_center",
13
+ "amusement_park",
14
+ "banquet_hall",
15
+ "childrens_camp",
16
+ "comedy_club",
17
+ "community_center",
18
+ "concert_hall",
19
+ "convention_center",
20
+ "cultural_center",
21
+ "dance_hall",
22
+ "event_venue",
23
+ "karaoke",
24
+ "night_club",
25
+ "opera_house",
26
+ "philharmonic_hall",
27
+ "planetarium",
28
+ "library",
29
+ "church",
30
+ "hindu_temple",
31
+ "mosque",
32
+ "synagogue"
33
+ ]
34
+
35
+
36
+ params = {
37
+ "engine": "google_maps",
38
+ "q": "",
39
+ "type": "search",
40
+ "api_key": SERP_API_KEY ,
41
+ # "ll": "@49.4540304,11.101698,14z" # coordinates for Nuremberg with 15 zoom in
42
+ }
43
+
44
+
45
+ def get_maps_results(search_query ):
46
+ results = []
47
+
48
+ params["q"] = search_query
49
+ search = GoogleSearch(params)
50
+ search_dict = search.get_dict()
51
+ if "local_results" not in search_dict:
52
+ return results
53
+ local_results = search_dict["local_results"]
54
+
55
+ for location in local_results:
56
+ if "website" in location and location["website"] not in results:
57
+
58
+ results.append(location["website"])
59
+ return results
60
+
61
+
62
+ # print(get_maps_results("Konzerte Nuernberg"))
src/crawler/serp_search.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from serpapi import GoogleSearch
2
+ from urllib.parse import urlparse
3
+ from src.configuration.config import SERP_API_KEY
4
+
5
+ params = {
6
+ "q": "",
7
+ "location": "Nuremberg, Bavaria, Germany",
8
+ "hl": "en",
9
+ "gl": "us",
10
+ "google_domain": "google.com",
11
+ "api_key": SERP_API_KEY
12
+ }
13
+
14
+ def get_search_results(keywords):
15
+ results = []
16
+ for keyword in keywords:
17
+ params["q"] = keyword + " Veranstaltungen"
18
+ search = GoogleSearch(params)
19
+ organic_results = search.get_dict().get("organic_results")
20
+ if organic_results is not None:
21
+ for result in organic_results:
22
+ append_url = True
23
+ base_url = urlparse(result["link"]).netloc
24
+ for r in results:
25
+ if base_url in r.url:
26
+ append_url = False
27
+ if append_url:
28
+ results.append( result["link"])
29
+ return results
src/crawler/utils/keywords.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ KEYWORDS = [
2
+ "Art Show",
3
+ "Aufführung",
4
+ "Ausstellung",
5
+ "Award Ceremony",
6
+ "Beginn",
7
+ "Charity",
8
+ "Club"
9
+ "Clubnight",
10
+ "Concert",
11
+ "Conference",
12
+ "Convention",
13
+ "Einlass",
14
+ "Eintritt",
15
+ "Exhibition",
16
+ "Festival",
17
+ "Fundraiser",
18
+ "Gala",
19
+ "Konferenz",
20
+ "Konzert",
21
+ "Kunstausstellung",
22
+ "Launch",
23
+ "Lecture",
24
+ "Meeting",
25
+ "Messe",
26
+ "Networking",
27
+ "Netzwerken",
28
+ "Party",
29
+ "Performance",
30
+ "Präsentation",
31
+ "Preisverleihung",
32
+ "Presentation",
33
+ "Seminar",
34
+ "Spendenaktion",
35
+ "Symposium",
36
+ "Tagung",
37
+ "Trade Show",
38
+ "Treffen",
39
+ "Veranstaltung",
40
+ "Vortrag",
41
+ "Webinar",
42
+ "Wohltätigkeitsveranstaltung",
43
+ "Workshop",
44
+ "Event",
45
+ ]
src/crawler/utils/maps_types.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MAPS_TYPES = [
2
+ "art_gallery",
3
+ "auditorium",
4
+ "museum",
5
+ "performing_arts_theater",
6
+ "amphitheatre",
7
+ "amphitheatre",
8
+ "childrens_camp",
9
+ "comedy_club",
10
+ "community_center",
11
+ "concert_hall",
12
+ "convention_center",
13
+ "cultural_center",
14
+ "dance_hall",
15
+ "karaoke",
16
+ "night_club",
17
+ "opera_house",
18
+ "philharmonic_hall",
19
+ "planetarium",
20
+ "library",
21
+ "church",
22
+ "hindu_temple",
23
+ "mosque",
24
+ "synagogue"
25
+ ]
src/crawler/utils/regEx.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+
3
+ # define patterns to filter urls that should not be crawled
4
+
5
+ PATTERNS = [
6
+ re.compile(r'.*about us*.*', re.IGNORECASE),
7
+ re.compile(r'.*about*.*us.*', re.IGNORECASE),
8
+ re.compile(r'.*a(b|n)meld(en|ung)*.*', re.IGNORECASE),
9
+ re.compile(r'.*about.*', re.IGNORECASE),
10
+ re.compile(r'.*agb*.*', re.IGNORECASE),
11
+ re.compile(r'.*archiv*.*', re.IGNORECASE),
12
+ re.compile(r'.*aussteller*.*', re.IGNORECASE),
13
+ re.compile(r'.*auszeichnung*.*', re.IGNORECASE),
14
+ re.compile(r'.*barrierefrei*.*', re.IGNORECASE),
15
+ re.compile(r'.*bestellverfolgung*.*', re.IGNORECASE),
16
+ re.compile(r'.*bezahlung*.*',re.IGNORECASE),
17
+ re.compile(r'.*bilder*.*', re.IGNORECASE),
18
+ re.compile(r'.*cart*.*', re.IGNORECASE),
19
+ re.compile(r'.*checkout*.*', re.IGNORECASE),
20
+ re.compile(r'.*contact*.*', re.IGNORECASE),
21
+ re.compile(r'.*credit card*.*', re.IGNORECASE),
22
+ re.compile(r'^https?://(?:www\.)?.*\.(jpg|png|pdf)$'),
23
+ re.compile(r'.*datenschutz*.*', re.IGNORECASE),
24
+ re.compile(r'.*debit*.*', re.IGNORECASE),
25
+ re.compile(r'.*download*.*', re.IGNORECASE),
26
+ re.compile(r'.*dsgvo.*', re.IGNORECASE),
27
+ re.compile(r'.*faq*.*', re.IGNORECASE),
28
+ re.compile(r'.*firmen(feiern|veranstaltungen).*', re.IGNORECASE),
29
+ re.compile(r'.*f(oe|ö)rder.*', re.IGNORECASE),
30
+ re.compile(r'.*for-rent*.*', re.IGNORECASE),
31
+ re.compile(r'.*fotos*.*', re.IGNORECASE),
32
+ re.compile(r'.*g(ae|ä)steliste*.*', re.IGNORECASE),
33
+ re.compile(r'.*galerie*.*', re.IGNORECASE),
34
+ re.compile(r'.*gallery*.*', re.IGNORECASE),
35
+ re.compile(r'.*geschaeftsbedingungen*.*', re.IGNORECASE),
36
+ re.compile(r'.*geschäftsbedingungen*.*', re.IGNORECASE),
37
+ re.compile(r'.*giropay*.*', re.IGNORECASE),
38
+ re.compile(r'.*guestlist*.*', re.IGNORECASE),
39
+ re.compile(r'.*hinweise*.*', re.IGNORECASE),
40
+ re.compile(r'.*impressum*.*', re.IGNORECASE),
41
+ re.compile(r'.*info.*', re.IGNORECASE),
42
+ re.compile(r'.*jobs*.*', re.IGNORECASE),
43
+ re.compile(r'.*karriere*.*', re.IGNORECASE),
44
+ re.compile(r'.*kasse*.*', re.IGNORECASE),
45
+ re.compile(r'.*klarna*.*', re.IGNORECASE),
46
+ re.compile(r'.*kontakt*.*', re.IGNORECASE),
47
+ re.compile(r'.*konto*.*', re.IGNORECASE),
48
+ re.compile(r'.*kreditkarte*.*', re.IGNORECASE),
49
+ re.compile(r'.*lastschrift*.*', re.IGNORECASE),
50
+ re.compile(r'.*landschaftsprogramm*.*', re.IGNORECASE),
51
+ re.compile(r'.*lieferung*.*', re.IGNORECASE),
52
+ re.compile(r'.*location.*', re.IGNORECASE),
53
+ re.compile(r'.*login*.*', re.IGNORECASE),
54
+ re.compile(r'.*mein-konto*.*', re.IGNORECASE),
55
+ re.compile(r'.*meine-bestellungen*.*', re.IGNORECASE),
56
+ re.compile(r'.*merch*.*', re.IGNORECASE),
57
+ re.compile(r'.*merkzettel*.*', re.IGNORECASE),
58
+ re.compile(r'.*mieten*.*', re.IGNORECASE),
59
+ re.compile(r'.*account*.*', re.IGNORECASE),
60
+ re.compile(r'.*orders*.*', re.IGNORECASE),
61
+ re.compile(r'.*news*.*', re.IGNORECASE),
62
+ re.compile(r'.*newsletter*.*', re.IGNORECASE),
63
+ re.compile(r'.*pay*.*', re.IGNORECASE),
64
+ re.compile(r'.*payment*.*', re.IGNORECASE),
65
+ re.compile(r'.*paypal*.*', re.IGNORECASE),
66
+ re.compile(r'.*personal*.*', re.IGNORECASE),
67
+ re.compile(r'.*photos*.*', re.IGNORECASE),
68
+ re.compile(r'.*pics*.*', re.IGNORECASE),
69
+ re.compile(r'.*pictures*.*', re.IGNORECASE),
70
+ re.compile(r'.*policy*.*', re.IGNORECASE),
71
+ re.compile(r'.*portfolio*.*', re.IGNORECASE),
72
+ re.compile(r'.*press*.*', re.IGNORECASE),
73
+ re.compile(r'.*pressemeldung*.*', re.IGNORECASE),
74
+ re.compile(r'.*pressemitteilungen*.*', re.IGNORECASE),
75
+ re.compile(r'.*privacy-policy*.*', re.IGNORECASE),
76
+ re.compile(r'.*produkt*.*', re.IGNORECASE),
77
+ re.compile(r'.*rathaus*.*', re.IGNORECASE),
78
+ re.compile(r'.*rechnung*.*', re.IGNORECASE),
79
+ re.compile(r'.*sepa*.*', re.IGNORECASE),
80
+ re.compile(r'.*shop*.*', re.IGNORECASE),
81
+ re.compile(r'.*signup*.*', re.IGNORECASE),
82
+ re.compile(r'.*sofort*.*', re.IGNORECASE),
83
+ re.compile(r'.*support*.*', re.IGNORECASE),
84
+ re.compile(r'.*terms-of-use*.*', re.IGNORECASE),
85
+ re.compile(r'.*twitter|facebook|instagram|tiktok*.*', re.IGNORECASE),
86
+ re.compile(r'.*(ue|ü)ber.?uns*.*', re.IGNORECASE),
87
+ re.compile(r'.*unterricht*.*', re.IGNORECASE),
88
+ re.compile(r'.*versand*.*', re.IGNORECASE),
89
+ re.compile(r'.*verbraucherschutz*.*', re.IGNORECASE),
90
+ re.compile(r'.*warenkorb*.*', re.IGNORECASE),
91
+ re.compile(r'.*wegbeschreibung*.*', re.IGNORECASE),
92
+ re.compile(r'.*widerrufsbelehrung*.*', re.IGNORECASE),
93
+ re.compile(r'.*wishlist*.*', re.IGNORECASE),
94
+ re.compile(r'.*zahlung*.*', re.IGNORECASE),
95
+ re.compile(r'.*.jpeg', re.IGNORECASE),
96
+ ]
97
+
src/nlp/__init__.py ADDED
File without changes
src/nlp/config.cfg ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [nlp]
2
+ lang = "en"
3
+ pipeline = ["llm"]
4
+
5
+ [components]
6
+
7
+ [components.llm]
8
+ factory = "llm"
9
+
10
+ [components.llm.task]
11
+ @llm_tasks = "spacy.NER.v3"
12
+ labels = ["PERSON", "ORGANISATION", "LOCATION"]
13
+
14
+ [components.llm.model]
15
+ @llm_models = "spacy.Dolly.v1"
16
+ # For better performance, use dolly-v2-12b instead
17
+ name = "dolly-v2-12b"
src/nlp/data/ner.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"classes":["TITLE","STARTTIME","STARTDATE","ENDDATE","ENDTIME","LOCATION"],"annotations":[["Technik-Salon an der TIB: „FLY ROCKET FLY“\r\n==========================================\r\n\r\nÜber den Aufstieg und Fall des Raketenpioniers Lutz Kayser – ein Film- und Gesprächsabend mit dem Dokumentarfilmer Oliver Schwehm\r\n\r\nEin Start-Up aus dem Schwäbischen, 170 Millionen D-Mark Wagniskapital, ein privates Testgelände im afrikanischen Dschungel – so trat Lutz Kayser in den 1970 er-Jahren an, die Raumfahrt zu revolutionieren.\r\n\r\nWarum das gut hätten klappen können und schließlich doch scheiterte, schildert Oliver Schwehm in seiner bildreichen Dokumentation. Im Rahmen der Jubiläumstour zu Ehren von Lutz Kayser („50 Jahre OTRAG – Oribital Transport und Raketen AG“) machen Film und Regisseur am 05.12.2024 im Technik-Salon Station in der TIB in Hannover. Der Eintritt für die Veranstaltung [„FLY ROCKET FLY“](http://www.technik-salon.de/05.12.2024/fly-rocket-fly.html) ist frei (Spendenbox).\r\n\r\nMehr Informationen zum [Technik-Salon](http://www.technik-salon.de)\r\n\r\n**Wann?** 05.12.2024, 19:00-21:00\r\n\r\n**Wo?** Lesesaal im Marstallgebäude, TIB",{"entities":[[0,42,"TITLE"],[980,990,"STARTDATE"],[992,997,"STARTTIME"],[998,1003,"ENDTIME"],[1014,1047,"LOCATION"]]}]]}
src/nlp/data/ner/texts.json ADDED
The diff for this file is too large to render. See raw diff
 
src/nlp/data/test.txt ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Technik-Salon an der TIB: „FLY ROCKET FLY“
2
+ ==========================================
3
+
4
+ Über den Aufstieg und Fall des Raketenpioniers Lutz Kayser – ein Film- und Gesprächsabend mit dem Dokumentarfilmer Oliver Schwehm
5
+
6
+ Ein Start-Up aus dem Schwäbischen, 170 Millionen D-Mark Wagniskapital, ein privates Testgelände im afrikanischen Dschungel – so trat Lutz Kayser in den 1970 er-Jahren an, die Raumfahrt zu revolutionieren.
7
+
8
+ Warum das gut hätten klappen können und schließlich doch scheiterte, schildert Oliver Schwehm in seiner bildreichen Dokumentation. Im Rahmen der Jubiläumstour zu Ehren von Lutz Kayser („50 Jahre OTRAG – Oribital Transport und Raketen AG“) machen Film und Regisseur am 05.12.2024 im Technik-Salon Station in der TIB in Hannover. Der Eintritt für die Veranstaltung [„FLY ROCKET FLY“](http://www.technik-salon.de/05.12.2024/fly-rocket-fly.html) ist frei (Spendenbox).
9
+
10
+ Mehr Informationen zum [Technik-Salon](http://www.technik-salon.de)
11
+
12
+ **Wann?** 05.12.2024, 19:00-21:00
13
+
14
+ **Wo?** Lesesaal im Marstallgebäude, TIB
src/nlp/dates_txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ Die Reservierungen für das diesjährige Mainzer Weihnachtsdorf werden voraussichtlich am 10.01.2024 EVENT_DATE um 08:00 freigeschaltet.
2
+ Das Interkulturelle Fest wird am 08.09.2025 EVENT_DATE auf dem Domplatz gefeiert.**
3
+ Vom 17.10.2025 - 03.11.2025 findet das Mainzer Oktoberfest zum 18. Mal statt.
4
+ Nächster Termin: 13.09.2025 und 14.09.2025
src/nlp/event.jpg ADDED
src/nlp/experimental/__init__.py ADDED
File without changes
src/nlp/experimental/annotations.json ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ [
3
+ "Im Rahmen der Jubiläumstour zu Ehren von Lutz Kayser (\"50 Jahre OTRAG – Oribital Transport und Raketen AG\") machen Film und Regisseur am 05.12.2024 im Technik-Salon Station in der TIB in Hannover.",
4
+ {
5
+ "entities": [
6
+ [
7
+ 137,
8
+ 147,
9
+ "EVENT_DATE"
10
+ ]
11
+ ]
12
+ }
13
+ ],
14
+ [
15
+ "Wann? 05.12.2024, 19:00-21:00",
16
+ {
17
+ "entities": [
18
+ [
19
+ 6,
20
+ 16,
21
+ "EVENT_DATE"
22
+ ]
23
+ ]
24
+ }
25
+ ],
26
+ [
27
+ "Der siebte Workshop Retrodigitalisierung findet am 20.03.2025 und 21.03.2025 bei ZB MED – Informationszentrum Lebenswissenschaften in Köln statt.",
28
+ {
29
+ "entities": [
30
+ [
31
+ 51,
32
+ 61,
33
+ "EVENT_DATE"
34
+ ],
35
+ [
36
+ 66,
37
+ 76,
38
+ "EVENT_DATE"
39
+ ]
40
+ ]
41
+ }
42
+ ],
43
+ [
44
+ "Wann? 20.03.2025 - 21.03.2025",
45
+ {
46
+ "entities": [
47
+ [
48
+ 6,
49
+ 29,
50
+ "EVENT_DATE_RANGE"
51
+ ]
52
+ ]
53
+ }
54
+ ],
55
+ [
56
+ "Die 18. ACM International Conference on Web Search and Data Mining (WSDM 2025) wird vom 10.03.2025 - 14.03.2025 in Hannover stattfinden.",
57
+ {
58
+ "entities": [
59
+ [
60
+ 88,
61
+ 111,
62
+ "EVENT_DATE_RANGE"
63
+ ]
64
+ ]
65
+ }
66
+ ],
67
+ [
68
+ "So. 08.12.2024 12:15 - 13:15 CET",
69
+ {
70
+ "entities": [
71
+ [
72
+ 4,
73
+ 14,
74
+ "EVENT_DATE"
75
+ ]
76
+ ]
77
+ }
78
+ ],
79
+ [
80
+ "24.12.2025 um 16:00",
81
+ {
82
+ "entities": [
83
+ [
84
+ 0,
85
+ 10,
86
+ "EVENT_DATE"
87
+ ]
88
+ ]
89
+ }
90
+ ],
91
+ [
92
+ "07.01.2025",
93
+ {
94
+ "entities": [
95
+ [
96
+ 0,
97
+ 10,
98
+ "EVENT_DATE"
99
+ ]
100
+ ]
101
+ }
102
+ ],
103
+ [
104
+ "Am 01.07.2025",
105
+ {
106
+ "entities": [
107
+ [
108
+ 3,
109
+ 13,
110
+ "EVENT_DATE"
111
+ ]
112
+ ]
113
+ }
114
+ ],
115
+ [
116
+ "Wann? 07.11.2024 - 09.03.2025",
117
+ {
118
+ "entities": [
119
+ [
120
+ 6,
121
+ 29,
122
+ "EVENT_DATE_RANGE"
123
+ ]
124
+ ]
125
+ }
126
+ ],
127
+ [
128
+ "01.11.2024 - 09.03.2025",
129
+ {
130
+ "entities": [
131
+ [
132
+ 0,
133
+ 23,
134
+ "EVENT_DATE_RANGE"
135
+ ]
136
+ ]
137
+ }
138
+ ],
139
+ [
140
+ "01.09.2025 - 26.12.2024",
141
+ {
142
+ "entities": [
143
+ [
144
+ 0,
145
+ 23,
146
+ "EVENT_DATE_RANGE"
147
+ ]
148
+ ]
149
+ }
150
+ ],
151
+ [
152
+ "Premiere am 01.12.2024",
153
+ {
154
+ "entities": [
155
+ [
156
+ 12,
157
+ 22,
158
+ "EVENT_DATE"
159
+ ]
160
+ ]
161
+ }
162
+ ],
163
+ [
164
+ "01.11.2025 ab 16:00",
165
+ {
166
+ "entities": [
167
+ [
168
+ 0,
169
+ 10,
170
+ "EVENT_DATE"
171
+ ]
172
+ ]
173
+ }
174
+ ],
175
+ [
176
+ "15.01.2025 ab 18:00",
177
+ {
178
+ "entities": [
179
+ [
180
+ 0,
181
+ 10,
182
+ "EVENT_DATE"
183
+ ]
184
+ ]
185
+ }
186
+ ],
187
+ [
188
+ "01.12.2025 ab 14:00-15:00",
189
+ {
190
+ "entities": [
191
+ [
192
+ 0,
193
+ 10,
194
+ "EVENT_DATE"
195
+ ]
196
+ ]
197
+ }
198
+ ],
199
+ [
200
+ "18.01.2025 ab 11:00-18:00",
201
+ {
202
+ "entities": [
203
+ [
204
+ 0,
205
+ 10,
206
+ "EVENT_DATE"
207
+ ]
208
+ ]
209
+ }
210
+ ],
211
+ [
212
+ "01.12.2024 – 08.12.2024",
213
+ {
214
+ "entities": [
215
+ [
216
+ 0,
217
+ 23,
218
+ "EVENT_DATE_RANGE"
219
+ ]
220
+ ]
221
+ }
222
+ ],
223
+ [
224
+ "Freitag 16:00-20:00 / Samstag 11:00-20:00 / Sonntag 11:00-18:00",
225
+ {
226
+ "entities": []
227
+ }
228
+ ],
229
+ [
230
+ "So, 15.12.2024 Beginn: 15:00 Einlass: 14:30",
231
+ {
232
+ "entities": [
233
+ [
234
+ 4,
235
+ 14,
236
+ "EVENT_DATE"
237
+ ]
238
+ ]
239
+ }
240
+ ]
241
+ ]
src/nlp/experimental/annotations_v1.json ADDED
@@ -0,0 +1,430 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ [
3
+ "Im Rahmen der Jubiläumstour zu Ehren von Lutz Kayser („50 Jahre OTRAG – Oribital Transport und Raketen AG“) machen Film und Regisseur am 05.12.2024 im Technik-Salon Station in der TIB in Hannover.",
4
+ {
5
+ "entities": [
6
+ [
7
+ 137,
8
+ 147,
9
+ "DATE"
10
+ ]
11
+ ]
12
+ }
13
+ ],
14
+ [
15
+ "Der siebte Workshop Retrodigitalisierung findet am 20.03.2025 und 21.03.2025 bei ZB MED – Informationszentrum Lebenswissenschaften in Köln statt.",
16
+ {
17
+ "entities": [
18
+ [
19
+ 51,
20
+ 61,
21
+ "START_DATE"
22
+ ],
23
+ [
24
+ 66,
25
+ 76,
26
+ "END_DATE"
27
+ ]
28
+ ]
29
+ }
30
+ ],
31
+ [
32
+ "Die 18. ACM International Conference on Web Search and Data Mining (WSDM 2025) wird vom 10.03.2025 - 14.03.2025 in Hannover stattfinden.",
33
+ {
34
+ "entities": [
35
+ [
36
+ 88,
37
+ 98,
38
+ "START_DATE"
39
+ ],
40
+ [
41
+ 101,
42
+ 111,
43
+ "END_DATE"
44
+ ]
45
+ ]
46
+ }
47
+ ],
48
+ [
49
+ "Die Infoveranstaltung für geistliche Mütter und Väter findet am 08.12.2024 im Christlichen Zentrum Darmstadt statt.",
50
+ {
51
+ "entities": [
52
+ [
53
+ 64,
54
+ 74,
55
+ "DATE"
56
+ ]
57
+ ]
58
+ }
59
+ ],
60
+ [
61
+ "Erlebe einen besonderen Film-Gottesdienst am 24.12.2025 um 16:00.",
62
+ {
63
+ "entities": [
64
+ [
65
+ 45,
66
+ 55,
67
+ "DATE"
68
+ ]
69
+ ]
70
+ }
71
+ ],
72
+ [
73
+ "Termin für öffentliche Besichtigung am 07.01.2025.",
74
+ {
75
+ "entities": [
76
+ [
77
+ 39,
78
+ 49,
79
+ "DATE"
80
+ ]
81
+ ]
82
+ }
83
+ ],
84
+ [
85
+ "Cornelia Poletto Palazzo: Die Dinner-Show im Spiegelpalast findet vom 07.11.2024 bis 09.03.2025 statt.",
86
+ {
87
+ "entities": [
88
+ [
89
+ 70,
90
+ 80,
91
+ "START_DATE"
92
+ ],
93
+ [
94
+ 85,
95
+ 95,
96
+ "END_DATE"
97
+ ]
98
+ ]
99
+ }
100
+ ],
101
+ [
102
+ "Die Ausstellung \"Leonardo da Vinci – uomo universale\" läuft vom 01.09.2025 bis zum 26.12.2024 in Hamburg.",
103
+ {
104
+ "entities": [
105
+ [
106
+ 64,
107
+ 74,
108
+ "START_DATE"
109
+ ],
110
+ [
111
+ 83,
112
+ 93,
113
+ "END_DATE"
114
+ ]
115
+ ]
116
+ }
117
+ ],
118
+ [
119
+ "Liedernachmittag – Lieder von R. Schubert, R. Franz, A. Webern, H. Wolf. Vortrag im Museum August Kestner. Monika Abel, Sopran / Kathrin Isabelle Klein, Klavier. Termine 01.11.2025 ab 16:00.",
120
+ {
121
+ "entities": [
122
+ [
123
+ 170,
124
+ 180,
125
+ "START_DATE"
126
+ ]
127
+ ]
128
+ }
129
+ ],
130
+ [
131
+ "Die goldene Stadt – Vortrag im Museum August Kestner mit Dr. Martin Hersch (München). Termine 15.01.2025 ab 18:00.",
132
+ {
133
+ "entities": [
134
+ [
135
+ 94,
136
+ 104,
137
+ "START_DATE"
138
+ ]
139
+ ]
140
+ }
141
+ ],
142
+ [
143
+ "Stadtansichten – Erleben Sie bei einem Besuch der Sonderausstellung Städtetrip. Termine 01.12.2025 ab 14:00-15:00.",
144
+ {
145
+ "entities": [
146
+ [
147
+ 88,
148
+ 98,
149
+ "START_DATE"
150
+ ]
151
+ ]
152
+ }
153
+ ],
154
+ [
155
+ "Finissage der Sonderausstellung Bartmann, Bier und Tafelzier. Steinzeug in der niederländischen Malerei. Mit Kuratorinnenführung. Termine 18.01.2025 ab 11:00-18:00.",
156
+ {
157
+ "entities": [
158
+ [
159
+ 138,
160
+ 148,
161
+ "START_DATE"
162
+ ]
163
+ ]
164
+ }
165
+ ],
166
+ [
167
+ "Veranstaltungsinformationen 01.12.2024 – 08.12.2024. Nikolausmarkt im Kulturhof, Kulturzentrum Bottrop.",
168
+ {
169
+ "entities": [
170
+ [
171
+ 28,
172
+ 38,
173
+ "START_DATE"
174
+ ],
175
+ [
176
+ 41,
177
+ 51,
178
+ "END_DATE"
179
+ ]
180
+ ]
181
+ }
182
+ ],
183
+ [
184
+ "Verleihung Erlanger Theaterpreis. So. 8.12. Keine Anmeldung nötig. Theater in der Garage 19:00. Eintritt frei.",
185
+ {
186
+ "entities": []
187
+ }
188
+ ],
189
+ [
190
+ "SILVESTER 2024 IM FOODKLUB – Feiern Sie mit uns ins neue Jahr mit einem exquisiten levantinischen Festbuffet! 48€ pro Person. Datum: 2024.",
191
+ {
192
+ "entities": []
193
+ }
194
+ ],
195
+ [
196
+ "PIANOKLÄNGE & Herzgeschichten – Stiftung Gemeinsam für Halle. Termine 15.12.2024. Beginn 15:00, Einlass 14:30.",
197
+ {
198
+ "entities": [
199
+ [
200
+ 70,
201
+ 80,
202
+ "START_DATE"
203
+ ]
204
+ ]
205
+ }
206
+ ],
207
+ [
208
+ "Zwischendrin – Beginn 17:00, Ende 19:00. Veranstaltungsort: Religionspädagogisches Institut im Heinrich-Fries-Haus. Termine 2024-25.",
209
+ {
210
+ "entities": []
211
+ }
212
+ ],
213
+ [
214
+ "vom 02.12. – 19.12.2024, Dauer ca. 30 Minuten",
215
+ {
216
+ "entities": [
217
+ [
218
+ 13,
219
+ 23,
220
+ "END_DATE"
221
+ ]
222
+ ]
223
+ }
224
+ ],
225
+ [
226
+ "01.01.2025*Donnerstag* 15.00 - 17.00 *Uhr*",
227
+ {
228
+ "entities": [
229
+ [
230
+ 0,
231
+ 10,
232
+ "DATE"
233
+ ]
234
+ ]
235
+ }
236
+ ],
237
+ [
238
+ "Vom 30.11.2024 - 06.07.2025 präsentieren wir in unserer Sonderausstellung das Werk des Berliner Künstlers Alexej Tchernyi.",
239
+ {
240
+ "entities": [
241
+ [
242
+ 4,
243
+ 14,
244
+ "START_DATE"
245
+ ],
246
+ [
247
+ 17,
248
+ 27,
249
+ "END_DATE"
250
+ ]
251
+ ]
252
+ }
253
+ ],
254
+ [
255
+ "01.04.2025 *Montag* 15.00 - 16.00 *Uhr*",
256
+ {
257
+ "entities": [
258
+ [
259
+ 0,
260
+ 10,
261
+ "DATE"
262
+ ]
263
+ ]
264
+ }
265
+ ],
266
+ [
267
+ "16.12.2024 **Beginn** 20:00-22:30",
268
+ {
269
+ "entities": [
270
+ [
271
+ 0,
272
+ 10,
273
+ "DATE"
274
+ ]
275
+ ]
276
+ }
277
+ ],
278
+ [
279
+ "29.12.2024 **Beginn** 15:00-17:00",
280
+ {
281
+ "entities": [
282
+ [
283
+ 0,
284
+ 10,
285
+ "DATE"
286
+ ]
287
+ ]
288
+ }
289
+ ],
290
+ [
291
+ "Die Höhner Weihnacht findet am Freitag, 20.12.2024, im Eurogress Aachen statt.",
292
+ {
293
+ "entities": [
294
+ [
295
+ 40,
296
+ 50,
297
+ "DATE"
298
+ ]
299
+ ]
300
+ }
301
+ ],
302
+ [
303
+ "Feiere Weihnachten mit uns am 24.12.2024 um 16:00 in der Festhalle Durlach.",
304
+ {
305
+ "entities": [
306
+ [
307
+ 30,
308
+ 40,
309
+ "DATE"
310
+ ]
311
+ ]
312
+ }
313
+ ],
314
+ [
315
+ "Der Alpha Kurs startet am 13.01.2025 und geht 12 Wochen lang.",
316
+ {
317
+ "entities": [
318
+ [
319
+ 26,
320
+ 36,
321
+ "DATE"
322
+ ]
323
+ ]
324
+ }
325
+ ],
326
+ [
327
+ "Die Echo Night findet am 05.04.2025 statt.",
328
+ {
329
+ "entities": [
330
+ [
331
+ 25,
332
+ 35,
333
+ "DATE"
334
+ ]
335
+ ]
336
+ }
337
+ ],
338
+ [
339
+ "Das Nachbarschaftsfest findet am 06.07.2025 und 07.07.2025 statt.",
340
+ {
341
+ "entities": [
342
+ [
343
+ 33,
344
+ 43,
345
+ "START_DATE"
346
+ ],
347
+ [
348
+ 48,
349
+ 58,
350
+ "END_DATE"
351
+ ]
352
+ ]
353
+ }
354
+ ],
355
+ [
356
+ "Die Einführung des neuen Leiters der Jugendkirche findet am 01.12.2024 um 18:00 statt.",
357
+ {
358
+ "entities": [
359
+ [
360
+ 60,
361
+ 70,
362
+ "DATE"
363
+ ]
364
+ ]
365
+ }
366
+ ],
367
+ [
368
+ "Die Christuskapelle in Grötzingen ist vom 18.12.2023 bis 02.12.2024 täglich von 17:00-19:00 geöffnet.",
369
+ {
370
+ "entities": [
371
+ [
372
+ 42,
373
+ 52,
374
+ "START_DATE"
375
+ ],
376
+ [
377
+ 57,
378
+ 67,
379
+ "END_DATE"
380
+ ]
381
+ ]
382
+ }
383
+ ],
384
+ [
385
+ "Der Adventsmarkt in der Kapelle findet am 30.11.2024 von 14:00-19:00 statt.",
386
+ {
387
+ "entities": [
388
+ [
389
+ 42,
390
+ 52,
391
+ "DATE"
392
+ ]
393
+ ]
394
+ }
395
+ ],
396
+ [
397
+ "Die 18. Koblenzer Literaturtage finden vom 22.03.2025 bis 04.05.2025 statt.",
398
+ {
399
+ "entities": [
400
+ [
401
+ 43,
402
+ 53,
403
+ "START_DATE"
404
+ ],
405
+ [
406
+ 58,
407
+ 68,
408
+ "END_DATE"
409
+ ]
410
+ ]
411
+ }
412
+ ],
413
+ [
414
+ "Die Kunstausstellung \"Der erweiterte Raum\" ist vom 15.11.2024 bis 01.05.2025 zu sehen.",
415
+ {
416
+ "entities": [
417
+ [
418
+ 51,
419
+ 61,
420
+ "START_DATE"
421
+ ],
422
+ [
423
+ 66,
424
+ 76,
425
+ "END_DATE"
426
+ ]
427
+ ]
428
+ }
429
+ ]
430
+ ]
src/nlp/experimental/data/img.png ADDED
src/nlp/experimental/data/test.md ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Infoveranstaltung für geistliche Mütter und Väter
2
+ =================================================
3
+
4
+ **Als Kirche wollen wir uns in die junge Generation investieren und sie fördern. Dazu gebraucht Gott reife geistliche Mütter und Väter.**
5
+
6
+ Datum und Uhrzeit
7
+ -----------------
8
+
9
+ So. 08.12.2024 12:15 - 13:15 CET
10
+
11
+ Veranstaltungsort
12
+ -----------------
13
+
14
+ Christliches Zentrum Darmstadt
15
+
16
+ Röntgenstraße 18 64291 Darmstadt
17
+
18
+ Karte anzeigen
19
+
20
+ Zu diesem Event
21
+ ---------------
22
+
23
+ **Infoveranstaltung für geistliche Mütter und Väter**
24
+
25
+ Als Kirche wollen wir uns in die junge Generation investieren und sie fördern. Dazu gebraucht Gott reife geistliche Mütter und Väter. Wenn du im altern von über 55 Jahren bist und einen Unterschied im Leben einer jungen Person machen möchtest, bist du herzlich zu dieser Infoveranstaltung eingeladen.
26
+
27
+ Wir wollen uns gemeinsam austauschen was unsere ältere Generation auf ihrer Reise mit Gott benötigt und welches Erbe Gott ihnen gegeben hat. Zudem sprechen wir über das kommende Jahr und welche Schritte wir gehen dürfen, damit die junge Generation fest in Jesus verwurzelt ist.
28
+
29
+ Wir starten mit einem kleinen Mittagessen und laden dich herzlich ein.
30
+
31
+ Veranstaltet von
32
+ ----------------
33
+
34
+ Christliches Zentrum Darmstadt
src/nlp/experimental/gliner/ner_fine_tuning.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from label_studio_sdk import Client
2
+
3
+ LABEL_STUDIO_URL = 'http://localhost:8080'
4
+ API_KEY = 'aad38f54021d443b17395123304a7c01001b55af'
5
+ ls = Client(url=LABEL_STUDIO_URL, api_key=API_KEY)
6
+ print(ls.check_connection())
7
+
8
+ # Load and preprocess sample data
9
+ from datasets import load_dataset
10
+ from tqdm import tqdm
11
+
12
+ # We don't need a ton of data, so we'll only look at the training set for now
13
+ dataset = load_dataset("MultiCoNER/multiconer_v2", "English (EN)")["train"]
14
+ medical_labels = ["Medication/Vaccine", "MedicalProcedure", "AnatomicalStructure", "Symptom", "Disease"]
15
+
16
+ # Filter so we only look at samples with medical tags
17
+ medical_dataset = []
18
+ for item in tqdm(dataset):
19
+ has_medical = any(any(label in tag for label in medical_labels) for tag in item["ner_tags"])
20
+ if has_medical:
21
+ # We want the text as a full text and not a list of tokens, so we create that as another key value pair in the item dictionary
22
+ item["text"] = " ".join(item["tokens"])
23
+ medical_dataset.append(item)
24
+
25
+ project = ls.start_project(
26
+ title='Medical NER with GLiNER',
27
+ label_config='''
28
+ <View>
29
+ <Labels name="label" toName="text">
30
+ <Label value="Medication/Vaccine" background="red"/>
31
+ <Label value="MedicalProcedure" background="blue"/>
32
+ <Label value="AnatomicalStructure" background="orange"/>
33
+ <Label value="Symptom" background="green"/>
34
+ <Label value="Disease" background="purple"/>
35
+ </Labels>
36
+
37
+ <Text name="text" value="$text"/>
38
+ </View>
39
+ '''
40
+ )
41
+
42
+ project.import_tasks(medical_dataset)
src/nlp/experimental/gliner/open_information_extraction.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from gliner import GLiNER
2
+ from gliner.multitask import GLiNEROpenExtractor
3
+
4
+ model_id = 'urchade/gliner_multi-v2.1'
5
+ model = GLiNER.from_pretrained(model_id)
6
+ extractor = GLiNEROpenExtractor(model=model, prompt="Extrahiere den Veranstaltungstitel")
7
+
8
+ text = """Technik-Salon an der TIB: „FLY ROCKET FLY“
9
+ Über den Aufstieg und Fall des Raketenpioniers Lutz Kayser – ein Film- und Gesprächsabend mit dem Dokumentarfilmer Oliver Schwehm
10
+
11
+ Ein Start-Up aus dem Schwäbischen, 170 Millionen D-Mark Wagniskapital, ein privates Testgelände im afrikanischen Dschungel – so trat Lutz Kayser in den 1970er-Jahren an, die Raumfahrt zu revolutionieren.
12
+
13
+ Warum das gut hätten klappen können und schließlich doch scheiterte, schildert Oliver Schwehm in seiner bildreichen Dokumentation. Im Rahmen der Jubiläumstour zu Ehren von Lutz Kayser („50 Jahre OTRAG – Oribital Transport und Raketen AG“) machen Film und Regisseur am 5. Dezember 2024 im Technik-Salon Station in der TIB in Hannover. Der Eintritt für die Veranstaltung „FLY ROCKET FLY“ ist frei (Spendenbox).
14
+
15
+ Mehr Informationen zum Technik-Salon
16
+
17
+ Wann? 5. Dezember 2024, 19.00-21.00 Uhr
18
+
19
+ Wo? Lesesaal im Marstallgebäude, TIB"""
20
+ labels = ['title']
21
+ predictions = extractor(text, labels=labels)
22
+ print(predictions)
src/nlp/experimental/gliner/summarization.py ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+
3
+ from gliner import GLiNER
4
+ from gliner.multitask import GLiNERSummarizer
5
+
6
+ texts = [
7
+ """Cornelia Poletto Palazzo: Die Dinner-Show im Spiegelpalast
8
+ Wann? 7. November 2024 bis 9. März 2025
9
+ Wo? Spiegelpalast in Hamburg-Altona, Waidmannstraße 26
10
+
11
+ Tickets sind hier erhältlich: Tickets buchen*
12
+
13
+ Termine
14
+ November 2024 bis 9. März 2025
15
+ Erreichbarkeit
16
+ Anreise mit den öffentlichen Verkehrsmitteln:
17
+ Über die Bushaltestellen Langenfelderstraße und Diebsteich Ostseite erreichen Sie den Spiegelpalast in wenigen Gehminuten. Bitte nutzen Sie bevorzugt den ÖPNV zur Anreise.
18
+
19
+ Anreise mit dem PKW:
20
+ Eine begrenzte Anzahl an Parkplätzen steht Ihnen in unmittelbarer Nähe zum Spiegelpalast zur Verfügung. Folgen Sie der Beschilderung! Die Anreise mit dem ÖPNV wird empfohlen.""",
21
+
22
+ """Technik-Salon an der TIB: „FLY ROCKET FLY“
23
+ Über den Aufstieg und Fall des Raketenpioniers Lutz Kayser – ein Film- und Gesprächsabend mit dem Dokumentarfilmer Oliver Schwehm
24
+
25
+ Ein Start-Up aus dem Schwäbischen, 170 Millionen D-Mark Wagniskapital, ein privates Testgelände im afrikanischen Dschungel – so trat Lutz Kayser in den 1970er-Jahren an, die Raumfahrt zu revolutionieren.
26
+
27
+ Warum das gut hätten klappen können und schließlich doch scheiterte, schildert Oliver Schwehm in seiner bildreichen Dokumentation. Im Rahmen der Jubiläumstour zu Ehren von Lutz Kayser („50 Jahre OTRAG – Oribital Transport und Raketen AG“) machen Film und Regisseur am 5. Dezember 2024 im Technik-Salon Station in der TIB in Hannover. Der Eintritt für die Veranstaltung „FLY ROCKET FLY“ ist frei (Spendenbox).
28
+
29
+ Mehr Informationen zum Technik-Salon""",
30
+
31
+ """Workshop Retrodigitalisierung ================================
32
+ Thema: Digitalisierte Sammlungen präsentieren – Konzeptionierung, Darstellung und Vermittlung
33
+
34
+ Der siebte Workshop Retrodigitalisierung findet am 20. und 21. März 2025 bei ZB MED – Informationszentrum Lebenswissenschaften in Köln statt. Er richtet sich an Praktiker:innen, die sich in Bibliotheken mit der Retrodigitalisierung befassen. Wie in den Vorjahren bietet der Workshop ein breites Spektrum an interessanten Vorträgen zur Praxis der Retrodigitalisierung. Dafür brauchen wir Sie und Ihre Einreichungen!
35
+
36
+ Im Fokus des nächsten Workshops steht die zeitgemäße Präsentation digitalisierter Sammlungen. Das Programm widmet sich insbesondere den Themen Konzeptionierung, Darstellung und Vermittlung von digitalisierten Sammlungen und Beständen über die Präsentationsplattformen der Einrichtungen rund um die Nutzung von Digitalisaten.
37
+
38
+ Der Call for Presentations läuft noch bis zum 18. Oktober 2024. Wir freuen uns auf Ihren Beitrag!
39
+
40
+ Der Workshop Retrodigitalisierung wird gemeinsam von den drei deutschen Zentralen Fachbibliotheken TIB – Leibniz-Informationszentrum Technik und Naturwissenschaften, ZB MED – Informationszentrum Lebenswissenschaften und ZBW – Leibniz-Informationszentrum Wirtschaft sowie der Staatsbibliothek zu Berlin – Preußischer Kulturbesitz durchgeführt.
41
+
42
+ Wann? 20. März bis 21. März 2025
43
+
44
+ Wo? ZB MED in Köln""",
45
+
46
+ """„ACM WSDM 2025“: renommierte Konferenz zu Websuche und Data Mining in Hannover
47
+ Wissenschaftlicher Austausch und technologische Innovation im Fokus der 18. ACM International Conference on Web Search and Data Mining
48
+
49
+ Die 18. ACM International Conference on Web Search and Data Mining (WSDM 2025) wird vom 10.03.2025 - 14.03.2025 in Hannover stattfinden. Die WSDM zählt zu den führenden Konferenzen in den Bereichen Websuche, Data Mining, Maschinelles Lernen und Künstliche Intelligenz. Sie bietet eine Plattform, auf der weltweit führende Wissenschaftler:innen, Fachleute und Praktiker:innen ihre neuesten Forschungsergebnisse präsentieren und zukunftsweisende Ideen austauschen können.
50
+
51
+ Die Konferenz wird sich auf ein breites Spektrum aktueller Themen konzentrieren, darunter:
52
+
53
+ Web of Things, ortsunabhängige und mobile Datenverarbeitung (Ubiquitous and Mobile Computing)
54
+ Datenschutz, Fairness, Interpretierbarkeit
55
+ Soziale Netzwerke
56
+ Intelligente Assistenten
57
+ Crowdsourcing und menschliche Datenverarbeitung
58
+ Zur Konferenz-Website""",
59
+
60
+ """Infoveranstaltung für geistliche Mütter und Väter
61
+ Als Kirche wollen wir uns in die junge Generation investieren und sie fördern. Dazu gebraucht Gott reife geistliche Mütter und Väter.
62
+
63
+ Von Christliches Zentrum DarmstadtFolgenFolgen
64
+
65
+ Datum und Uhrzeit
66
+ So. 8. Dez. 2024 12:15 - 13:15 CET
67
+
68
+ Veranstaltungsort
69
+ Christliches Zentrum Darmstadt
70
+
71
+ Röntgenstraße 18 64291 DarmstadtKarte anzeigen
72
+
73
+ Zu diesem Event
74
+ ** Infoveranstaltung für geistliche Mütter und Väter **
75
+
76
+ Als Kirche wollen wir uns in die junge Generation investieren und sie fördern. Dazu gebraucht Gott reife geistliche Mütter und Väter. Wenn du im altern von über 55 Jahren bist und einen Unterschied im Leben einer jungen Person machen möchtest, bist du herzlich zu dieser Infoveranstaltung eingeladen.
77
+
78
+ Wir wollen uns gemeinsam austauschen was unsere ältere Generation auf ihrer Reise mit Gott ben��tigt und welches Erbe Gott ihnen gegeben hat. Zudem sprechen wir über das kommende Jahr und welche Schritte wir gehen dürfen, damit die junge Generation fest in Jesus verwurzelt ist.
79
+
80
+ Wir starten mit einem kleinen Mittagessen und laden dich herzlich ein.
81
+
82
+ Veranstaltet von
83
+ Christliches Zentrum Darmstadt""",
84
+
85
+ """MJ – Das Michael Jackson Musical
86
+ Das Erfolgsmusical über den Ausnahmekünstler Michael Jackson ist seit Dezember 2024 in Hamburg zu sehen! Tickets und Hotel können Sie hier bequem online buchen.
87
+
88
+ Tickets buchen
89
+ Erleben Sie das mehrfach ausgezeichnete Musical MJ – Das Michael Jackson Musical.
90
+
91
+ Tickets buchen ab 63,99€*
92
+
93
+ Tickets und Hotel buchen
94
+ Sie möchten Ihren Musical-Besuch mit einer Reise nach Hamburg verbinden? Hier können Sie Ihre Tickets und Hotelübernachtung im Paket buchen:
95
+
96
+ Reisepaket: MJ – Das Michael Jackson Musical ab 134,60€*
97
+
98
+ Alle Vorteile auf einen Blick:
99
+
100
+ Übernachtung im ausgewählten Hotel inkl. Frühstück, Zusatznächte buchbar
101
+ Musical-Ticket in der gewählten Preiskategorie
102
+ Hamburg Card (3 Tage) – Ihr Entdeckerticket für freie Fahrt mit Bus und Bahn im Wert von 31,90€
103
+ Termine für MJ – Das Michael Jackson Musical
104
+ Anfahrt
105
+ MJ – Das Michael Jackson Musical | Stage Theater an der Elbe
106
+
107
+ Norderelbstraße 8, 20457 Hamburg
108
+
109
+ Kontakt speichern
110
+
111
+ Adresse auf Karte anzeigen
112
+
113
+ [Anfahrt](https://geofox.hvv.de/jsf/home.seam?clear=true&language=de&destination=Norderelbstra%C3%9Fe 8, 20457 Hamburg)
114
+
115
+ Termine
116
+ Premiere am 1. Dezember 2024""",
117
+
118
+ """Liedernachmittag
119
+ Lieder von R. Schubert, R. Franz, A. Webern, H. Wolf
120
+
121
+ Stuhlreihen im Museum August Kestner
122
+ © MAK/LHH
123
+
124
+ Vortrag im Museum August Kestner
125
+
126
+ Monika Abel, Sopran / Kathrin Isabelle Klein, Klavier
127
+
128
+ Termine
129
+
130
+ 11.01.2025 ab 16:00 Uhr
131
+
132
+ Ort
133
+
134
+ Museum August Kestner
135
+ Platz der Menschenrechte 3
136
+ 30159 Hannover
137
+
138
+ Konzertkarten: [email protected] und Tageskasse ab 15.00 Uhr im Museum
139
+
140
+ Unter der Schirmherrschaft von Kammersängerin Helen Donath.
141
+
142
+ Bis zu viermal im Jahr laden wir zu einem Liedernachmittag im Museum ein.
143
+
144
+ In Zusammenarbeit mit: Lohmann-Stiftung für Liedgesang e.V., Hannover; Freundes- und Förderkreis des Museum August Kestner „Antike & Gegenwart e.V.“""",
145
+
146
+ """Die goldene Stadt
147
+ Vortrag im Museum August Kestner
148
+
149
+ Mit Dr. Martin Hersch (München)
150
+
151
+ In Kooperation mit dem Freundeskreis Antike und Gegenwart
152
+
153
+ Termine
154
+ 15.01.2025 ab 18:00 Uhr
155
+
156
+ Ort
157
+ Museum August Kestner
158
+ Platz der Menschenrechte 3
159
+ 30159 Hannover
160
+
161
+ Eintritt
162
+ 5,00 €
163
+
164
+ ermäßigter Eintritt
165
+
166
+ 4,00 €""",
167
+
168
+ """Stadtansichten
169
+ Erleben Sie bei einem Besuch der Sonderausstellung Städtetrip vielfältige und spannende Lyrik und Prosa verschiedener Autor*innen, nicht nur zum Thema Reisen. Ausgewählt und vorgetragen von der Literarischen Komponistin und Rezitatorin Marie Dettmer.
170
+
171
+ Termine
172
+
173
+ 12.01.2025 ab 14:00 bis 15:00 Uhr
174
+
175
+ Ort
176
+
177
+ Landeshauptstadt Hannover
178
+
179
+ Trammplatz 2
180
+
181
+ 30159 Hannover"""
182
+ ]
183
+
184
+ model_id = 'urchade/gliner_multi-v2.1'
185
+ model = GLiNER.from_pretrained(model_id)
186
+ summarizer = GLiNERSummarizer(model=model)
187
+
188
+
189
+ for text in texts :
190
+ summary = summarizer(text, threshold=0.001)
191
+ print("ORIGINAL")
192
+ print(text)
193
+ print("SUMMARY")
194
+ print(summary)
src/nlp/experimental/keyword_extraction.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import spacy
2
+ from collections import Counter
3
+ from string import punctuation
4
+ nlp = spacy.load("de_core_news_lg")
5
+ def get_hotwords(text):
6
+ result = []
7
+ pos_tag = ['PROPN', 'ADJ', 'NOUN']
8
+ doc = nlp(text.lower())
9
+ for token in doc:
10
+ if(token.text in nlp.Defaults.stop_words or token.text in punctuation):
11
+ continue
12
+ if(token.pos_ in pos_tag):
13
+ result.append(token.text)
14
+ return result
15
+ new_text = """
16
+ Technik-Salon an der TIB: „FLY ROCKET FLY“
17
+ Über den Aufstieg und Fall des Raketenpioniers Lutz Kayser – ein Film- und Gesprächsabend mit dem Dokumentarfilmer Oliver Schwehm
18
+
19
+ Ein Start-Up aus dem Schwäbischen, 170 Millionen D-Mark Wagniskapital, ein privates Testgelände im afrikanischen Dschungel – so trat Lutz Kayser in den 1970 er-Jahren an, die Raumfahrt zu revolutionieren.
20
+
21
+ Warum das gut hätten klappen können und schließlich doch scheiterte, schildert Oliver Schwehm in seiner bildreichen Dokumentation. Im Rahmen der Jubiläumstour zu Ehren von Lutz Kayser („50 Jahre OTRAG – Oribital Transport und Raketen AG“) machen Film und Regisseur am 05.12.2024 im Technik-Salon Station in der TIB in Hannover. Der Eintritt für die Veranstaltung „FLY ROCKET FLY“ ist frei (Spendenbox).
22
+
23
+ Mehr Informationen zum Technik-Salon
24
+
25
+ Wann? 05.12.2024, 19:00-21:00
26
+
27
+ Wo? Lesesaal im Marstallgebäude, TIB
28
+ """
29
+ output = set(get_hotwords(new_text))
30
+ print(output)
31
+ most_common_list = Counter(output).most_common(10)
32
+ for item in most_common_list:
33
+ print(item[0])
src/nlp/experimental/layout_parser.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import layoutparser as lp
2
+ import cv2
3
+
4
+
5
+ image = cv2.imread("data/img.png")
6
+ image = image[..., ::-1]
7
+ # Convert the image from BGR (cv2 default loading style)
8
+ # to RGB
9
+
10
+
11
+
12
+ model = lp.Detectron2LayoutModel('lp://PubLayNet/faster_rcnn_R_50_FPN_3x/config',
13
+ extra_config=["MODEL.ROI_HEADS.SCORE_THRESH_TEST", 0.8],
14
+ label_map={0: "Text", 1: "Title", 2: "List", 3:"Table", 4:"Figure"})
15
+ # Load the deep layout model from the layoutparser API
16
+ # For all the supported model, please check the Model
17
+ # Zoo Page: https://layout-parser.readthedocs.io/en/latest/notes/modelzoo.html
18
+
19
+ layout = model.detect(image)
20
+ # Detect the layout of the input image
21
+
22
+ lp.draw_box(image, layout, box_width=3)
23
+ # Show the detected layout of the input image
src/nlp/experimental/llm/__init__.py ADDED
File without changes
src/nlp/experimental/llm/inference_api_test.py ADDED
@@ -0,0 +1,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from huggingface_hub import InferenceClient
2
+ import json
3
+ from src.configuration.config import INFERENCE_API_KEY
4
+ texts = [
5
+ """Cornelia Poletto Palazzo: Die Dinner-Show im Spiegelpalast
6
+ Wann? 7. November 2024 bis 9. März 2025
7
+ Wo? Spiegelpalast in Hamburg-Altona, Waidmannstraße 26
8
+
9
+ Tickets sind hier erhältlich: Tickets buchen*
10
+
11
+ Termine
12
+ November 2024 bis 9. März 2025
13
+ Erreichbarkeit
14
+ Anreise mit den öffentlichen Verkehrsmitteln:
15
+ Über die Bushaltestellen Langenfelderstraße und Diebsteich Ostseite erreichen Sie den Spiegelpalast in wenigen Gehminuten. Bitte nutzen Sie bevorzugt den ÖPNV zur Anreise.
16
+
17
+ Anreise mit dem PKW:
18
+ Eine begrenzte Anzahl an Parkplätzen steht Ihnen in unmittelbarer Nähe zum Spiegelpalast zur Verfügung. Folgen Sie der Beschilderung! Die Anreise mit dem ÖPNV wird empfohlen.""",
19
+
20
+ """Technik-Salon an der TIB: „FLY ROCKET FLY“
21
+ Über den Aufstieg und Fall des Raketenpioniers Lutz Kayser – ein Film- und Gesprächsabend mit dem Dokumentarfilmer Oliver Schwehm
22
+
23
+ Ein Start-Up aus dem Schwäbischen, 170 Millionen D-Mark Wagniskapital, ein privates Testgelände im afrikanischen Dschungel – so trat Lutz Kayser in den 1970er-Jahren an, die Raumfahrt zu revolutionieren.
24
+
25
+ Warum das gut hätten klappen können und schließlich doch scheiterte, schildert Oliver Schwehm in seiner bildreichen Dokumentation. Im Rahmen der Jubiläumstour zu Ehren von Lutz Kayser („50 Jahre OTRAG – Oribital Transport und Raketen AG“) machen Film und Regisseur am 5. Dezember 2024 im Technik-Salon Station in der TIB in Hannover. Der Eintritt für die Veranstaltung „FLY ROCKET FLY“ ist frei (Spendenbox).
26
+
27
+ Mehr Informationen zum Technik-Salon""",
28
+
29
+ """Workshop Retrodigitalisierung ================================
30
+ Thema: Digitalisierte Sammlungen präsentieren – Konzeptionierung, Darstellung und Vermittlung
31
+
32
+ Der siebte Workshop Retrodigitalisierung findet am 20. und 21. März 2025 bei ZB MED – Informationszentrum Lebenswissenschaften in Köln statt. Er richtet sich an Praktiker:innen, die sich in Bibliotheken mit der Retrodigitalisierung befassen. Wie in den Vorjahren bietet der Workshop ein breites Spektrum an interessanten Vorträgen zur Praxis der Retrodigitalisierung. Dafür brauchen wir Sie und Ihre Einreichungen!
33
+
34
+ Im Fokus des nächsten Workshops steht die zeitgemäße Präsentation digitalisierter Sammlungen. Das Programm widmet sich insbesondere den Themen Konzeptionierung, Darstellung und Vermittlung von digitalisierten Sammlungen und Beständen über die Präsentationsplattformen der Einrichtungen rund um die Nutzung von Digitalisaten.
35
+
36
+ Der Call for Presentations läuft noch bis zum 18. Oktober 2024. Wir freuen uns auf Ihren Beitrag!
37
+
38
+ Der Workshop Retrodigitalisierung wird gemeinsam von den drei deutschen Zentralen Fachbibliotheken TIB – Leibniz-Informationszentrum Technik und Naturwissenschaften, ZB MED – Informationszentrum Lebenswissenschaften und ZBW – Leibniz-Informationszentrum Wirtschaft sowie der Staatsbibliothek zu Berlin – Preußischer Kulturbesitz durchgeführt.
39
+
40
+ Wann? 20. März bis 21. März 2025
41
+
42
+ Wo? ZB MED in Köln""",
43
+
44
+ """„ACM WSDM 2025“: renommierte Konferenz zu Websuche und Data Mining in Hannover
45
+ Wissenschaftlicher Austausch und technologische Innovation im Fokus der 18. ACM International Conference on Web Search and Data Mining
46
+
47
+ Die 18. ACM International Conference on Web Search and Data Mining (WSDM 2025) wird vom 10.03.2025 - 14.03.2025 in Hannover stattfinden. Die WSDM zählt zu den führenden Konferenzen in den Bereichen Websuche, Data Mining, Maschinelles Lernen und Künstliche Intelligenz. Sie bietet eine Plattform, auf der weltweit führende Wissenschaftler:innen, Fachleute und Praktiker:innen ihre neuesten Forschungsergebnisse präsentieren und zukunftsweisende Ideen austauschen können.
48
+
49
+ Die Konferenz wird sich auf ein breites Spektrum aktueller Themen konzentrieren, darunter:
50
+
51
+ Web of Things, ortsunabhängige und mobile Datenverarbeitung (Ubiquitous and Mobile Computing)
52
+ Datenschutz, Fairness, Interpretierbarkeit
53
+ Soziale Netzwerke
54
+ Intelligente Assistenten
55
+ Crowdsourcing und menschliche Datenverarbeitung
56
+ Zur Konferenz-Website""",
57
+
58
+ """Infoveranstaltung für geistliche Mütter und Väter
59
+ Als Kirche wollen wir uns in die junge Generation investieren und sie fördern. Dazu gebraucht Gott reife geistliche Mütter und Väter.
60
+
61
+ Von Christliches Zentrum DarmstadtFolgenFolgen
62
+
63
+ Datum und Uhrzeit
64
+ So. 8. Dez. 2024 12:15 - 13:15 CET
65
+
66
+ Veranstaltungsort
67
+ Christliches Zentrum Darmstadt
68
+
69
+ Röntgenstraße 18 64291 DarmstadtKarte anzeigen
70
+
71
+ Zu diesem Event
72
+ ** Infoveranstaltung für geistliche Mütter und Väter **
73
+
74
+ Als Kirche wollen wir uns in die junge Generation investieren und sie fördern. Dazu gebraucht Gott reife geistliche Mütter und Väter. Wenn du im altern von über 55 Jahren bist und einen Unterschied im Leben einer jungen Person machen möchtest, bist du herzlich zu dieser Infoveranstaltung eingeladen.
75
+
76
+ Wir wollen uns gemeinsam austauschen was unsere ältere Generation auf ihrer Reise mit Gott benötigt und welches Erbe Gott ihnen gegeben hat. Zudem sprechen wir über das kommende Jahr und welche Schritte wir gehen dürfen, damit die junge Generation fest in Jesus verwurzelt ist.
77
+
78
+ Wir starten mit einem kleinen Mittagessen und laden dich herzlich ein.
79
+
80
+ Veranstaltet von
81
+ Christliches Zentrum Darmstadt""",
82
+
83
+ """MJ – Das Michael Jackson Musical
84
+ Das Erfolgsmusical über den Ausnahmekünstler Michael Jackson ist seit Dezember 2024 in Hamburg zu sehen! Tickets und Hotel können Sie hier bequem online buchen.
85
+
86
+ Tickets buchen
87
+ Erleben Sie das mehrfach ausgezeichnete Musical MJ – Das Michael Jackson Musical.
88
+
89
+ Tickets buchen ab 63,99€*
90
+
91
+ Tickets und Hotel buchen
92
+ Sie möchten Ihren Musical-Besuch mit einer Reise nach Hamburg verbinden? Hier können Sie Ihre Tickets und Hotelübernachtung im Paket buchen:
93
+
94
+ Reisepaket: MJ – Das Michael Jackson Musical ab 134,60€*
95
+
96
+ Alle Vorteile auf einen Blick:
97
+
98
+ Übernachtung im ausgewählten Hotel inkl. Frühstück, Zusatznächte buchbar
99
+ Musical-Ticket in der gewählten Preiskategorie
100
+ Hamburg Card (3 Tage) – Ihr Entdeckerticket für freie Fahrt mit Bus und Bahn im Wert von 31,90€
101
+ Termine für MJ – Das Michael Jackson Musical
102
+ Anfahrt
103
+ MJ – Das Michael Jackson Musical | Stage Theater an der Elbe
104
+
105
+ Norderelbstraße 8, 20457 Hamburg
106
+
107
+ Kontakt speichern
108
+
109
+ Adresse auf Karte anzeigen
110
+
111
+ [Anfahrt](https://geofox.hvv.de/jsf/home.seam?clear=true&language=de&destination=Norderelbstra%C3%9Fe 8, 20457 Hamburg)
112
+
113
+ Termine
114
+ Premiere am 1. Dezember 2024""",
115
+
116
+ """Liedernachmittag
117
+ Lieder von R. Schubert, R. Franz, A. Webern, H. Wolf
118
+
119
+ Stuhlreihen im Museum August Kestner
120
+ © MAK/LHH
121
+
122
+ Vortrag im Museum August Kestner
123
+
124
+ Monika Abel, Sopran / Kathrin Isabelle Klein, Klavier
125
+
126
+ Termine
127
+
128
+ 11.01.2025 ab 16:00 Uhr
129
+
130
+ Ort
131
+
132
+ Museum August Kestner
133
+ Platz der Menschenrechte 3
134
+ 30159 Hannover
135
+
136
+ Konzertkarten: [email protected] und Tageskasse ab 15.00 Uhr im Museum
137
+
138
+ Unter der Schirmherrschaft von Kammersängerin Helen Donath.
139
+
140
+ Bis zu viermal im Jahr laden wir zu einem Liedernachmittag im Museum ein.
141
+
142
+ In Zusammenarbeit mit: Lohmann-Stiftung für Liedgesang e.V., Hannover; Freundes- und Förderkreis des Museum August Kestner „Antike & Gegenwart e.V.“""",
143
+
144
+ """Die goldene Stadt
145
+ Vortrag im Museum August Kestner
146
+
147
+ Mit Dr. Martin Hersch (München)
148
+
149
+ In Kooperation mit dem Freundeskreis Antike und Gegenwart
150
+
151
+ Termine
152
+ 15.01.2025 ab 18:00 Uhr
153
+
154
+ Ort
155
+ Museum August Kestner
156
+ Platz der Menschenrechte 3
157
+ 30159 Hannover
158
+
159
+ Eintritt
160
+ 5,00 €
161
+
162
+ ermäßigter Eintritt
163
+
164
+ 4,00 €""",
165
+
166
+ """Stadtansichten
167
+ Erleben Sie bei einem Besuch der Sonderausstellung Städtetrip vielfältige und spannende Lyrik und Prosa verschiedener Autor*innen, nicht nur zum Thema Reisen. Ausgewählt und vorgetragen von der Literarischen Komponistin und Rezitatorin Marie Dettmer.
168
+
169
+ Termine
170
+
171
+ 12.01.2025 ab 14:00 bis 15:00 Uhr
172
+
173
+ Ort
174
+
175
+ Landeshauptstadt Hannover
176
+
177
+ Trammplatz 2
178
+
179
+ 30159 Hannover"""
180
+ ]
181
+
182
+ client = InferenceClient(
183
+ "Qwen/Qwen2.5-Coder-32B-Instruct",
184
+ token=INFERENCE_API_KEY,
185
+ )
186
+
187
+ for text in texts:
188
+ messages = [{
189
+ "role": "user",
190
+ "content": """"Du bist ein NER-Model. Gebe die Veranstaltungsinformationen Titel, Startdatum, Enddatum, Startzeit,
191
+ Endzeit, Einlasszeit, LocationName, Straße, Hausnummer, Postleitzahl, Stadt, Preis usw. aus dem text in JSON Format zurück.
192
+ Es sollen keine Markdown Elemente enthalten sein, nur das JSON Objekt als string.
193
+ Gebe Nur das JSON als Antwort zurück. JSON_Schema:
194
+ {
195
+ "title": string,
196
+ "organizer": string,
197
+ "startDate": string,
198
+ "endDate": string,
199
+ "startTime": string,
200
+ "endTime": string,
201
+ "admittanceTime": string,
202
+ "locationName": string,
203
+ "street": string,
204
+ "houseNumber": string,
205
+ "postalCode": string,
206
+ "city": string,
207
+ "price": Array<float> | null,
208
+ "currency": string,
209
+ "priceFree": bool,
210
+ "ticketsRequired": bool.
211
+ "categories": Array<string> | null,
212
+ "eventDescription": string,
213
+ "accesibilityInformation": string,
214
+ "keywords": Array<string> | null,
215
+ }
216
+ Text: """ + text}]
217
+
218
+ response = client.chat_completion(messages, max_tokens=1000)
219
+ print(text)
220
+ print()
221
+ event = json.loads(response.choices[0].message.content)
222
+ print(f"TITEL: {event["title"]} - HOST: {event['organizer']}")
223
+ print(f"KATEGORIEN: {event["categories"]}")
224
+ print(f"KEYWORDS: {event['keywords']}")
225
+ print(f"BESCHREIBUNG:\n {event["eventDescription"]}")
226
+ print("_________________________________________________________________________________________________________________________")
227
+
src/nlp/experimental/llm/llm_image_document_question_answering.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ from huggingface_hub import InferenceClient
2
+ from src.configuration.config import INFERENCE_API_KEY
3
+ client = InferenceClient(
4
+ "vikhyatk/moondream2",
5
+ token=INFERENCE_API_KEY,)
6
+ response = client.image_to_text(
7
+ image="event.jpg")
8
+ print(response)
src/nlp/experimental/llm/llm_ner.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers import AutoModelForCausalLM, AutoTokenizer
2
+
3
+ model_name = "Qwen/Qwen2.5-Coder-32B-Instruct"
4
+
5
+ model = AutoModelForCausalLM.from_pretrained(
6
+ model_name,
7
+ torch_dtype="auto",
8
+ device_map="auto"
9
+ )
10
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
11
+
12
+ prompt = "write a quick sort algorithm."
13
+ messages = [
14
+ {"role": "system", "content": "You are Qwen, created by Alibaba Cloud. You are a helpful assistant."},
15
+ {"role": "user", "content": prompt}
16
+ ]
17
+ text = tokenizer.apply_chat_template(
18
+ messages,
19
+ tokenize=False,
20
+ add_generation_prompt=True
21
+ )
22
+ model_inputs = tokenizer([text], return_tensors="pt").to(model.device)
23
+
24
+ generated_ids = model.generate(
25
+ **model_inputs,
26
+ max_new_tokens=512
27
+ )
28
+ generated_ids = [
29
+ output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
30
+ ]
31
+
32
+ response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
src/nlp/experimental/ner/__init__.py ADDED
File without changes
src/nlp/experimental/ner/create_spacy_annotations.py ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+
3
+ import spacy
4
+
5
+
6
+ def convert_to_spacy_annotations(data):
7
+ spacy_annotations = []
8
+
9
+ for item in data:
10
+ text = item["text"]
11
+ entities = []
12
+
13
+ for entity in item["entities"]:
14
+ start_idx = text.find(entity["data"])
15
+ if start_idx != -1:
16
+ end_idx = start_idx + len(entity["data"])
17
+ entities.append((start_idx, end_idx, entity["label"]))
18
+
19
+ spacy_annotations.append((text, {"entities": entities}))
20
+
21
+ return spacy_annotations
22
+
23
+
24
+ # Beispiel JSON-Daten
25
+ data = [
26
+ {
27
+ "text": "Im Rahmen der Jubiläumstour zu Ehren von Lutz Kayser (\"50 Jahre OTRAG – Oribital Transport und Raketen AG\") machen Film und Regisseur am 05.12.2024 im Technik-Salon Station in der TIB in Hannover.",
28
+ "entities": [{ "data": "05.12.2024", "label": "EVENT_DATE" }]
29
+ },
30
+ {
31
+ "text": "Wann? 05.12.2024, 19:00-21:00",
32
+ "entities": [{ "data": "05.12.2024", "label": "EVENT_DATE" }]
33
+ },
34
+ {
35
+ "text": "Der siebte Workshop Retrodigitalisierung findet am 20.03.2025 und 21.03.2025 bei ZB MED – Informationszentrum Lebenswissenschaften in Köln statt.",
36
+ "entities": [
37
+ { "data": "20.03.2025", "label": "EVENT_DATE" },
38
+ { "data": "21.03.2025", "label": "EVENT_DATE" }
39
+ ]
40
+ },
41
+ {
42
+ "text": "Wann? 20.03.2025 - 21.03.2025",
43
+ "entities": [{ "data": "20.03.2025 - 21.03.2025", "label": "EVENT_DATE_RANGE" }]
44
+ },
45
+ {
46
+ "text": "Die 18. ACM International Conference on Web Search and Data Mining (WSDM 2025) wird vom 10.03.2025 - 14.03.2025 in Hannover stattfinden.",
47
+ "entities": [{ "data": "10.03.2025 - 14.03.2025", "label": "EVENT_DATE_RANGE" }]
48
+ },
49
+ {
50
+ "text": "So. 08.12.2024 12:15 - 13:15 CET",
51
+ "entities": [{ "data": "08.12.2024", "label": "EVENT_DATE" }]
52
+ },
53
+ {
54
+ "text": "24.12.2025 um 16:00",
55
+ "entities": [{ "data": "24.12.2025", "label": "EVENT_DATE" }]
56
+ },
57
+ {
58
+ "text": "07.01.2025",
59
+ "entities": [{ "data": "07.01.2025", "label": "EVENT_DATE" }]
60
+ },
61
+ {
62
+ "text": "Am 01.07.2025",
63
+ "entities": [{ "data": "01.07.2025", "label": "EVENT_DATE" }]
64
+ },
65
+ {
66
+ "text": "Wann? 07.11.2024 - 09.03.2025",
67
+ "entities": [{ "data": "07.11.2024 - 09.03.2025", "label": "EVENT_DATE_RANGE" }]
68
+ },
69
+ {
70
+ "text": "01.11.2024 - 09.03.2025",
71
+ "entities": [{ "data": "01.11.2024 - 09.03.2025", "label": "EVENT_DATE_RANGE" }]
72
+ },
73
+ {
74
+ "text": "01.09.2025 - 26.12.2024",
75
+ "entities": [{ "data": "01.09.2025 - 26.12.2024", "label": "EVENT_DATE_RANGE" }]
76
+ },
77
+ {
78
+ "text": "Premiere am 01.12.2024",
79
+ "entities": [
80
+ {
81
+ "data": "01.12.2024",
82
+ "label": "EVENT_DATE"
83
+ }
84
+ ]
85
+ },
86
+ {
87
+ "text": "01.11.2025 ab 16:00",
88
+ "entities": [
89
+ {
90
+ "data": "01.11.2025",
91
+ "label": "EVENT_DATE"
92
+ }
93
+ ]
94
+ },
95
+ {
96
+ "text": "15.01.2025 ab 18:00",
97
+ "entities": [
98
+ {
99
+ "data": "15.01.2025",
100
+ "label": "EVENT_DATE"
101
+ }
102
+ ]
103
+ },
104
+ {
105
+ "text": "01.12.2025 ab 14:00-15:00",
106
+ "entities": [
107
+ {
108
+ "data": "01.12.2025",
109
+ "label": "EVENT_DATE"
110
+ }
111
+ ]
112
+ },
113
+ {
114
+ "text": "18.01.2025 ab 11:00-18:00",
115
+ "entities": [
116
+ {
117
+ "data": "18.01.2025",
118
+ "label": "EVENT_DATE"
119
+ }
120
+ ]
121
+ },
122
+ {
123
+ "text": "01.12.2024 – 08.12.2024",
124
+ "entities": [
125
+ {
126
+ "data": "01.12.2024 – 08.12.2024",
127
+ "label": "EVENT_DATE_RANGE"
128
+ }
129
+ ]
130
+ },
131
+ {
132
+ "text": "Freitag 16:00-20:00 / Samstag 11:00-20:00 / Sonntag 11:00-18:00",
133
+ "entities": []
134
+ },
135
+ {
136
+ "text": "So, 15.12.2024 Beginn: 15:00 Einlass: 14:30",
137
+ "entities": [
138
+ {
139
+ "data": "15.12.2024",
140
+ "label": "EVENT_DATE"
141
+ }
142
+ ]
143
+ }
144
+ ]
145
+
146
+
147
+
148
+
149
+
150
+
151
+ # Umwandlung in spaCy-Format
152
+ annotations = convert_to_spacy_annotations(data)
153
+ # JSON speichern
154
+ with open("../annotations.json", "w", encoding="utf-8") as f:
155
+ json.dump(annotations, f, ensure_ascii=False, indent=4)
156
+
157
+ # Ausgabe prüfen
158
+ print(json.dumps(annotations, ensure_ascii=False, indent=4))
src/nlp/experimental/ner/few_shot_ner.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import spacy
2
+ from spacy import displacy
3
+ from spacy.tokenizer import Tokenizer
4
+ from spacy.util import compile_prefix_regex, compile_suffix_regex, compile_infix_regex
5
+
6
+ # Beispieltext mit Datum und Veranstaltung
7
+ text = """Das Event findet am 01.02.2022 statt. Es wird um 19:00 Uhr beginnen."""
8
+
9
+ # Lade ein vortrainiertes deutsches Modell (enthält Dependency Parser!)
10
+ nlp = spacy.load("de_core_news_md")
11
+
12
+ # Punkt als Infix definieren (damit 01.02.2022 getrennt wird)
13
+ infixes = list(nlp.Defaults.infixes) + [r"(?<=\d)\.(?=\d)"] # Punkt zwischen Zahlen trennen
14
+ infix_re = compile_infix_regex(infixes)
15
+
16
+ # Tokenizer mit neuer Infix-Regel setzen
17
+ nlp.tokenizer = Tokenizer(nlp.vocab, infix_finditer=infix_re.finditer)
18
+
19
+ # Entity Ruler für Datumsangaben hinzufügen
20
+ ruler = nlp.add_pipe("entity_ruler", before="ner")
21
+
22
+ patterns = [
23
+ {
24
+ "label": "DATE",
25
+ "pattern": [
26
+ {"SHAPE": "dd"}, {"ORTH": "."}, {"SHAPE": "dd"}, {"ORTH": "."}, {"SHAPE": "dddd"}
27
+ ]
28
+ }
29
+ ]
30
+ ruler.add_patterns(patterns)
31
+
32
+ # Verarbeite den Text
33
+ doc = nlp(text)
34
+
35
+ # Tokenisierung prüfen
36
+ print("Tokens:", [token.text for token in doc])
37
+
38
+ # Extrahiere erkannte Entitäten
39
+ for ent in doc.ents:
40
+ print(ent.text, ent.label_ , ent.start_char, ent.end_char)
41
+
42
+ # # Überprüfung der Abhängigkeiten zwischen Datum und Veranstaltung
43
+ # for token in doc:
44
+ # if token.ent_type_ == "DATE":
45
+ # # Überprüfe, ob das Datum mit einem Ereigniswort verbunden ist
46
+ # # Hier suchen wir nach Wörtern wie "findet", "statt", "Event" etc.
47
+ # for child in token.head.children:
48
+ # if child.dep_ in ["ROOT", "prep", "attr", "dobj"] and child.lemma_ in ["findet", "statt", "Veranstaltung", "Event"]:
49
+ # print(f"Das Datum {token.text} bezieht sich auf eine Veranstaltung.")
50
+ # break
51
+
52
+ displacy.serve(doc, style="dep")
src/nlp/experimental/ner/nu_ner.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from gliner import GLiNER
2
+
3
+ def merge_entities(entities):
4
+ if not entities:
5
+ return []
6
+ merged = []
7
+ current = entities[0]
8
+ for next_entity in entities[1:]:
9
+ if next_entity['label'] == current['label'] and (next_entity['start'] == current['end'] + 1 or next_entity['start'] == current['end']):
10
+ current['text'] = text[current['start']: next_entity['end']].strip()
11
+ current['end'] = next_entity['end']
12
+ else:
13
+ merged.append(current)
14
+ current = next_entity
15
+ # Append the last entity
16
+ merged.append(current)
17
+ return merged
18
+
19
+
20
+ model = GLiNER.from_pretrained("numind/NuNerZero")
21
+
22
+ # NuZero requires labels to be lower-cased!
23
+ labels = ["organization", "initiative", "project"]
24
+ labels = [l.lower() for l in labels]
25
+
26
+ text = "At the annual technology summit, the keynote address was delivered by a senior member of the Association for Computing Machinery Special Interest Group on Algorithms and Computation Theory, which recently launched an expansive initiative titled 'Quantum Computing and Algorithmic Innovations: Shaping the Future of Technology'. This initiative explores the implications of quantum mechanics on next-generation computing and algorithm design and is part of a broader effort that includes the 'Global Computational Science Advancement Project'. The latter focuses on enhancing computational methodologies across scientific disciplines, aiming to set new benchmarks in computational efficiency and accuracy."
27
+
28
+ entities = model.predict_entities(text, labels)
29
+
30
+ entities = merge_entities(entities)
31
+
32
+ for entity in entities:
33
+ print(entity["text"], "=>", entity["label"])
src/nlp/experimental/ner/spacy_ner.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import random
3
+
4
+ import spacy
5
+ from spacy.training.example import Example
6
+
7
+ # Lade das deutsche Basismodell
8
+ nlp = spacy.load("de_core_news_sm")
9
+
10
+ with open('../annotations.json', encoding='utf-8') as f:
11
+ TRAINING_DATA = json.load(f)
12
+
13
+ # Wenn das Modell keine benutzerdefinierten Entitäten hat, füge die Entitätenerkennung hinzu
14
+ if "ner" not in nlp.pipe_names:
15
+ ner = nlp.create_pipe("ner")
16
+ nlp.add_pipe("ner", last=True)
17
+ else:
18
+ ner = nlp.get_pipe("ner")
19
+
20
+ # Füge die Entitäten hinzu
21
+ ner.add_label("START_DATE")
22
+ ner.add_label("END_DATE")
23
+ ner.add_label("DATE")
24
+ ner.add_label("OTHER")
25
+
26
+ # Trainingsdaten in Beispiele umwandeln
27
+ examples = []
28
+ for text, annotations in TRAINING_DATA:
29
+ doc = nlp.make_doc(text)
30
+ example = Example.from_dict(doc, annotations)
31
+ examples.append(example)
32
+
33
+ # Beginne mit dem Training
34
+ optimizer = nlp.begin_training()
35
+ for epoch in range(30): # Anzahl der Epochen
36
+ print(f"Epoch {epoch + 1}")
37
+ losses = {}
38
+ # Shuffle und trainiere das Modell mit den Beispielen
39
+ random.shuffle(examples)
40
+ # Trainiere mit den Beispielen
41
+ for example in examples:
42
+ nlp.update([example], drop=0.5, losses=losses)
43
+ print(losses)
44
+
45
+ # Speichere das trainierte Modell
46
+ nlp.to_disk("models/date_model")
47
+
src/nlp/experimental/ner/spacy_ner_rule_based.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import spacy
2
+ from spacy.tokenizer import Tokenizer
3
+ from spacy.util import compile_prefix_regex, compile_suffix_regex, compile_infix_regex
4
+ from nltk import Tree
5
+
6
+
7
+ # Beispieltext mit Datum
8
+ text = "Das Event findet am 01.02.2022 statt."
9
+
10
+ # Lade ein leeres deutsches Modell
11
+ nlp = spacy.blank("de")
12
+ nlp.add_pipe('sentencizer')
13
+
14
+ # 1️⃣ Punkt als Suffix & Infix definieren (damit er zwischen Zahlen trennt)
15
+ suffixes = list(nlp.Defaults.suffixes) + [r"\."] # Punkt als Suffix hinzufügen
16
+ infixes = list(nlp.Defaults.infixes) + [r"(?<=\d)\.(?=\d)"] # Punkt zwischen Zahlen trennen
17
+
18
+ # Regex-Objekte kompilieren
19
+ suffix_re = compile_suffix_regex(suffixes)
20
+ infix_re = compile_infix_regex(infixes)
21
+
22
+ # Angepasste Tokenizer-Funktion setzen
23
+ nlp.tokenizer = Tokenizer(nlp.vocab, suffix_search=suffix_re.search, infix_finditer=infix_re.finditer)
24
+ # 2️⃣ Entity Ruler für Datumsangaben hinzufügen
25
+ ruler = nlp.add_pipe("entity_ruler")
26
+
27
+ patterns = [
28
+ {
29
+ "label": "DATE",
30
+ "pattern": [
31
+ {"SHAPE": "dd"}, {"ORTH": "."}, {"SHAPE": "dd"}, {"ORTH": "."}, {"SHAPE": "dddd"}
32
+ ]
33
+ }
34
+ ]
35
+
36
+ ruler.add_patterns(patterns)
37
+
38
+ # 3️⃣ Verarbeite den Text
39
+ doc = nlp(text)
40
+
41
+ # Prüfe Tokenisierung
42
+ print("Tokens:", [token.text for token in doc]) # Punkt soll nun getrennt sein
43
+
44
+ # Extrahiere erkannte Entitäten
45
+ for ent in doc.ents:
46
+ print(ent.text, ent.label_)
47
+
48
+ def to_nltk_tree(node):
49
+ if node.n_lefts + node.n_rights > 0:
50
+ return Tree(node.orth_, [to_nltk_tree(child) for child in node.children])
51
+ else:
52
+ return node.orth_
53
+
54
+
55
+
56
+ for sent in doc.sents:
57
+ print(sent.text)
58
+ print(to_nltk_tree(sent.root))