Last active
December 12, 2024 17:37
-
-
Save RETr4ce/b6bfe3e072a985f5148eec0147430211 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import logging | |
| import re | |
| import asyncio | |
| import string | |
| from typing import TypeVar, TypeIs | |
| from dataclasses import dataclass | |
| from typing import Set, Final | |
| from pathlib import Path | |
| from pymongo import MongoClient | |
| from langchain_ollama import ChatOllama | |
| from langchain_core.prompts import ChatPromptTemplate | |
| from langchain_core.output_parsers import StrOutputParser | |
| from langchain_core.runnables import RunnablePassthrough | |
| T = TypeVar("T", bound=str, default=str) | |
| @dataclass(frozen=True) | |
| class TextAnalysis: | |
| text: str | |
| sentiment: str | |
| summary: str | |
| topic: str | |
| MONGO_URI = "mongodb://localhost:27017" | |
| DATABASE_NAME = "bsky_feed" | |
| COLLECTION_NAME = "posts" | |
| BASE_URL = "http://localhost:11434" | |
| MODEL_NAME = "gemma2" | |
| STOPWORDS_FILE: Final[Path] = Path("./Stopwoorden/NL/STOP.txt") | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format='%(asctime)s - %(levelname)s - %(message)s', | |
| force=True | |
| ) | |
| logger = logging.getLogger(__name__) | |
| logging.getLogger('httpx').setLevel(logging.WARNING) | |
| client = MongoClient(MONGO_URI) | |
| db = client[DATABASE_NAME] | |
| collection = db[COLLECTION_NAME] | |
| llm = ChatOllama( | |
| model=MODEL_NAME, | |
| base_url=BASE_URL | |
| ) | |
| sentiment_prompt = ChatPromptTemplate.from_messages([ | |
| ("system", "Je bent een sentimentanalyse model dat teksten classificeert als positief, negatief of neutraal."), | |
| ("human", "Beoordeel de volgende tekst en geef alleen 'positief', 'negatief' of 'neutraal' als antwoord:\n\n{text}") | |
| ]) | |
| summary_prompt = ChatPromptTemplate.from_messages([ | |
| ("system", """ | |
| Je bent een taalmodel dat teksten samenvat en alleen kernwoorden terug geeft. | |
| Analyseer eerst de tekst op: | |
| 1. Kernwoorden en thema's | |
| 2. Context en onderwerp | |
| 3. Specifieke verwijzingen | |
| Geef daarna de meest passende samenvatting terug in maximaal 10 kernwoorden. | |
| voorbeelden samenvatting: | |
| - "Zalig, Roxette, die hoor ik echt graag." => Roxette, favoriete, muziek, hits | |
| - "Bruidsmodezaak Tarwekamp is niet ‘t enige bedrijf op dit adres. Ook G.A.Zorg -zelfde eigenaresse- | |
| laat zich vanuit hier inhuren. Nou en? Ken bedrijf niet. Iha: 1000den zzp era starten zorgbedrijf, | |
| onbevoegd en zonder diploma’s, ouderen en zieken de dupe, veel declaratiefraude." => Bruidsmodezaak, Tarwekamp, G.A.Zorg, eigenaresse, zorgbedrijf, zzp, fraude, zorgverlening, bedrijven, kwetsbaar | |
| - "Bij archeologisch onderzoek in de Merwedekanaalzone in Utrecht, is vorige week de weg naar het Romeinse | |
| fort op het Domplein ontdekt. Het gaat om een aftakking van de limesweg. Zie het bericht bij de Gemeente Utrecht" => archeologen, utrecht, romeinen, limesweg, domplein, onderzoek, ontdekt | |
| - "De vlucht naar voren, dat wat de PVV altijd al als enige optie zag wanneer ze met de werkelijkheid werden geconfronteerd." => PVV, confrontatie, optie, werkelijkheid | |
| - "'Sluimervriend' verkozen tot 'Het ontbreekwoord van het jaar' 2024 door Radio 1-luisteraars" => sluimervriend, ontbreekwoord, 2024, Radio 1, luisteraars, verkiezing | |
| - "Mag Israël op de terreurlijst aub? Of is die lijst alleen voor moslims? Kotsmisselijk word ik van de | |
| hypocrisie bij sommige politici. #bbb #xenogeert #vvd #cu #cda #sgp #schoof" => israël, moslims, terreurlijst, politiek, hypocriet, bbb, vvd, cu, cda, sgp, schoof | |
| - "Fryslân dreigt een provincie te worden waarin het bestuur een draaideur is voor sentimentgedreven | |
| partijen, terwijl juist continuïteit en visie nodig zijn om te bouwen aan de toekomst." => friesland, bestuur, sentiment, continuïteit, toekomst | |
| - "Besluit reguliere #omgevingsvergunning Eerste Moordrechtse Tiendeweg 5 t/m 13 in Gouda" => omgevingsvergunning, moordrechtse tiendeweg, besluit, vergunning, gouda | |
| - "Kan die stotterende CU farizeeër @donceder.bsky.social niet meer aanhoren. | |
| Hij is geboren voor de hel'lees bijbel' daarom zeg ook tegen tot hem ziens in de hel. Want Jezus accepteert geen WOKE farizeeërs. | |
| #TweedeKamer #nieuwsbv #spraakmakers" => CU, farizeeër, woke, tweedekamer | |
| - "Op 11 december 1916 is Perez Prado geboren, bandleider, zanger, pianist en componist. | |
| Perez werd ook wel de ‘King of the mambo’ genoemd. De single ‘Patricia’ komt uit 1958. | |
| Perez is op 14 september 1989 overleden." => perez prado, muzikant, bandleider, patricia, 1958, overleden | |
| - "NIEUWS. Minister Faber van asiel vindt een kamer met een bed voor een statushouder niet sober genoeg als opvang. | |
| Wat ze dan wil is onduidelijk. Stapelbedden …? Straks @eenvandaag" => faber, asiel, opvang, statushouders, politiek, kamer, bed, eenvandaag | |
| """), | |
| ("human", "Vat de volgende tekst samen in maximaal 10 kernwoorden, gescheiden door komma's, zonder nummering, bijvoeglijke naamwoorden, lidwoorden, bijwoorden of hashtags als antwoord:\n\n{text}\"") | |
| ]) | |
| topic_prompt = ChatPromptTemplate.from_messages([ | |
| ("system", """ | |
| Je bent een onderwerpclassificatiemodel dat teksten classificeert in één van de volgende categorieën: | |
| politiek, sport, technologie, gezondheid, economie, cultuur, entertainment, wetenschap, | |
| buitenland, geweld, milieu, onderwijs, recht, vervoer, religie, kunst, werk, overheid. | |
| Analyseer eerst de tekst op: | |
| 1. Kernwoorden en thema's | |
| 2. Context en onderwerp | |
| 3. Specifieke verwijzingen | |
| Geef daarna alleen de meest passende categorie terug zonder uitleg of extra tekst. | |
| Voorbeelden: | |
| - "De verkiezingen van dit jaar zullen bepalend zijn voor de toekomstige koers van het land." => politiek | |
| - "Het nationale voetbalteam bereikte de finale van het wereldkampioenschap." => sport | |
| - "Nieuwe innovaties in kunstmatige intelligentie veranderen het technologische landschap." => technologie | |
| - "Het internationale congres over klimaatverandering begon vandaag." => milieu | |
| - "Er waren meldingen van geweld tijdens de protesten in de stad." => geweld | |
| """), | |
| ("human", "Classificeer de volgende tekst in één van de genoemde categorieën en geef alleen de categorie als antwoord:\n\n{text}") | |
| ]) | |
| sentiment_chain = sentiment_prompt | llm | StrOutputParser() | |
| summary_chain = summary_prompt | llm | StrOutputParser() | |
| topic_chain = topic_prompt | llm | StrOutputParser() | |
| def is_valid_sentiment(text: str) -> TypeIs[str]: | |
| return text in ['positief', 'negatief', 'neutraal'] | |
| def analyze_sentiment(text: str) -> str: | |
| try: | |
| sentiment = sentiment_chain.invoke({"text": text}).strip().lower() | |
| if is_valid_sentiment(sentiment): | |
| return sentiment | |
| match = re.search(r'\b(positief|negatief|neutraal)\b', sentiment) | |
| return match.group(1).lower() if match else "onbekend" | |
| except Exception as e: | |
| logger.error(f"Error in sentiment analysis: {e}") | |
| return "onbekend" | |
| def create_summary(text: str) -> str: | |
| try: | |
| summary = summary_chain.invoke({"text": text}).strip().lower() | |
| summary_words = summary.split() | |
| return ' '.join(summary_words[:10]) if len(summary_words) > 10 else summary | |
| except Exception as e: | |
| logger.error(f"Error in creating summary: {e}") | |
| return "onbekend" | |
| def analyze_topic(text: str) -> str: | |
| try: | |
| topic = topic_chain.invoke({"text": text}).strip().lower() | |
| valid_topics = {'politiek', 'sport', 'technologie', 'gezondheid', 'economie', | |
| 'cultuur', 'entertainment', 'wetenschap', 'buitenland', 'geweld', | |
| 'milieu', 'onderwijs', 'recht', 'vervoer', 'religie', 'kunst', "werk", "overheid"} | |
| return topic if topic in valid_topics else "overig" | |
| except Exception as e: | |
| logger.error(f"Error in topic analysis: {e}") | |
| return "overig" | |
| def load_stopwords(file_path: str) -> Set[str]: | |
| try: | |
| return {word.strip().lower() for word in file_path.read_text(encoding="utf-8").splitlines()} | |
| except (FileNotFoundError, PermissionError) as e: | |
| raise RuntimeError(f"Failed to load stopwords from {file_path}: {str(e)}") | |
| stop_words: Final[Set[str]] = load_stopwords(STOPWORDS_FILE) | |
| def remove_punctuation_except_comma(text): | |
| punctuation_without_comma = string.punctuation.replace(',', '') | |
| translator = str.maketrans('', '', punctuation_without_comma) | |
| return text.translate(translator) | |
| async def process_documents(): | |
| documents = collection.find() | |
| for doc in documents: | |
| try: | |
| text = doc['commit']['record']['text'] | |
| if len(text.split()) <= 5: | |
| continue | |
| analysis = TextAnalysis( | |
| text=text, | |
| sentiment=analyze_sentiment(text), | |
| summary=[word.strip() for word in remove_punctuation_except_comma(create_summary(text)).split(",") if word.strip().lower() not in stop_words and word.strip()], | |
| topic=analyze_topic(text) | |
| ) | |
| print(f"Text: {analysis.text}\n" | |
| f"Sentiment: {analysis.sentiment}\n" | |
| f"Summary: {analysis.summary}\n" | |
| f"Topic: {analysis.topic}\n") | |
| except KeyError as e: | |
| logger.error(f"Missing field in document: {e}") | |
| except Exception as e: | |
| logger.error(f"Error processing document: {e}") | |
| if __name__ == "__main__": | |
| asyncio.run(process_documents()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment