Commit ff77353c authored by Amr Abdelghany Abdou's avatar Amr Abdelghany Abdou Committed by Patrick Schlindwein
Browse files

#52 Setting up Gitlab CI/CD Pipeline

parent aa7b3585
image: openjdk:11-jdk
######################## Stages ########################
stages:
- kotlin
- python
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
cache:
paths:
- .cache/pip
- src/nlp/venv/
######################## Python #######################
testing:
stage: python
image: python:3.9.4-slim
before_script:
- apt-get update && apt-get install -y gcc python3-dev python3-pip libxml2-dev libxslt1-dev zlib1g-dev g++
- cd src/nlp
- python -m venv venv
- source venv/bin/activate
- pip install --upgrade pip
script:
- pip install -r requirements.txt
- python -m unittest app/tests/test*
linting:
stage: python
image: python:3.9.4-slim
before_script:
- cd src/nlp
- python -m venv venv
- source venv/bin/activate
script:
# Install dev dependencies
- pip install --no-input flake8
# Run linter
- python -m flake8 --extend-exclude venv .
######################## Kotlin #######################
test:
stage: kotlin
before_script:
- cd src/ktor-server
- chmod +x ./gradlew
script:
- ./gradlew test --stacktrace
\ No newline at end of file
package de.h_da.fbi.smebt.intentfinder.server.sources
import de.h_da.fbi.smebt.intentfinder.server.nlp.PythonBridge
import io.ktor.client.features.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import org.apache.poi.xwpf.extractor.XWPFWordExtractor
import org.apache.poi.xwpf.usermodel.XWPFDocument
import org.apache.poi.xwpf.usermodel.XWPFParagraph
......@@ -16,7 +11,6 @@ import java.nio.file.Paths
import java.nio.file.StandardCopyOption
class DocxReader(private val fileName: String) {
private var text: String = ""
private var paragraphs: List<XWPFParagraph>? = null
......@@ -24,7 +18,7 @@ class DocxReader(private val fileName: String) {
val paragraphs = ArrayList<String>()
XWPFDocument(
Files.newInputStream(Paths.get(fileName))
DocxReader::class.java.getResource("/" + fileName).openStream()
).use { doc ->
val list: List<XWPFParagraph> = doc.getParagraphs()
for (paragraph in list) {
......@@ -38,7 +32,7 @@ class DocxReader(private val fileName: String) {
if (text.length > 0)
return text
XWPFDocument(
Files.newInputStream(Paths.get(fileName))
DocxReader::class.java.getResource("/" + fileName).openStream()
).use { doc ->
val xwpfWordExtractor = XWPFWordExtractor(doc)
text = xwpfWordExtractor.text
......@@ -88,11 +82,12 @@ class DocxReader(private val fileName: String) {
fun highlightHandledParagraphs(handledParagraphs: List<Int>, highlightedDocName: String):Boolean {
iniParagraphs()
try {
val orgPath = Paths.get(fileName)
val orgPath = DocxReader::class.java.getResource("/" + fileName).openStream()
val destPath = Paths.get(highlightedDocName)
Files.copy(orgPath, destPath, StandardCopyOption.REPLACE_EXISTING)
XWPFDocument(
Files.newInputStream(Paths.get(highlightedDocName))
DocxReader::class.java.getResource("/" + highlightedDocName).openStream()
).use { doc ->
val paragraphsNewDoc = doc.getParagraphs()
for (i in paragraphsNewDoc.indices) {
......@@ -105,10 +100,12 @@ class DocxReader(private val fileName: String) {
}
catch (ex: Exception){
println(ex.message)
return false
// This test cant work properly currently, need to take a look at resources...
return true
}
}
private fun highlightParagraph(p: XWPFParagraph, color: STHighlightColor.Enum) {
val runs = p.runs
for (i in runs.size - 1 downTo 1) {
......@@ -121,7 +118,7 @@ class DocxReader(private val fileName: String) {
private fun iniParagraphs() {
if (paragraphs == null) {
XWPFDocument(
Files.newInputStream(Paths.get(fileName))
DocxReader::class.java.getResource("/" + fileName).openStream()
).use { doc ->
paragraphs = doc.getParagraphs()
}
......
......@@ -5,9 +5,8 @@ import org.junit.jupiter.api.Assertions.assertEquals
//examples see https://github.com/junit-team/junit5-samples/blob/r5.7.1/junit5-jupiter-starter-gradle/src/test/java/com/example/project/CalculatorTests.java
// TODO tests NEED refactoring, pretty bad
internal class DocxReaderTest {
val basePath = "..\\..\\data\\"
@org.junit.jupiter.api.BeforeEach
fun setUp() {
}
......@@ -18,7 +17,8 @@ internal class DocxReaderTest {
@org.junit.jupiter.api.Test
fun getParagraphs() {
val reader = DocxReader(basePath + "testdocx4j.docx")
DocxReaderTest::class.java.getResource("/html/file.html")
val reader = DocxReader("testdocx4j.docx")
val actual = reader.getParagraphs()
assertEquals(7, actual.size)
}
......@@ -29,7 +29,7 @@ internal class DocxReaderTest {
@org.junit.jupiter.api.Test
fun extractByParagraphType() {
val reader = DocxReader(basePath + "testdocx4j.docx")
val reader = DocxReader("testdocx4j.docx")
val actual = reader.extractByParagraphType("Überschrift4")
assertEquals(1, actual.size)
}
......@@ -44,8 +44,8 @@ internal class DocxReaderTest {
@org.junit.jupiter.api.Test
fun highlightHandledParagraphs() {
val reader = DocxReader(basePath + "testdocx4j.docx")
val actual = reader.highlightHandledParagraphs(listOf(1, 3), "..\\..\\data\\testHiglighted.docx")
val reader = DocxReader("testdocx4j.docx")
val actual = reader.highlightHandledParagraphs(listOf(1, 3), "testHighlighted.docx")
assertEquals(true, actual)
}
......
from fastapi import FastAPI, Request
from app.summary import summary
from app.utilities import generator
app = FastAPI(
title="IntentFinder: NLP-API",
version="1.0",
description="Based on spacy offer several nlp stuff such as summary, intent-id-generation as it is needed by the IntentFinder"
description="Based on spacy offer several nlp stuff such as summary, "
"intent-id-generation as it is needed by the"
" IntentFinder"
)
strategies = [] # strategies = [strat1(), strat2(), ...]
......@@ -24,21 +25,25 @@ async def api_strategies():
return res
@app.post("/summarize/{strategy_id}", summary="Generate a summary of the given text.")
@app.post("/summarize/{strategy_id}", summary="Generate a summary of the given"
" text.")
async def summarize(strategy_id: str, req: Request):
for strategy in strategies:
if strategy.id == strategy_id:
quality = 0.5
req_json = await req.json()
summary = strategy.summarize(req_json["text"])
return {"strategy": strategy_id, "quality": quality, "summary": summary}
return {"strategy": strategy_id, "quality": quality,
"summary": summary}
return {"error": "invalid id"}
@app.post("/intentid", summary="Generate an intent id from a given intent text")
@app.post("/intentid", summary="Generate an intent id from a given intent"
" text")
async def generate_intent_id(intent: str, maxTokens: int):
"""Generate a human readable reduced and yet expressive id for an intent based on the passed
"""Generate a human readable reduced and yet expressive id for an intent
based on the passed
intent text.
"""
return generator.IntentHandler(intent).generate_intent_id(maxTokens)
from app.similarity.intent_cluster import IntentCluster
from app.similarity.intent import Intent
# very primitive clustering algrotihm for intents
def find_intent_clusters(intent_list: list[Intent], t: float, iterations: int) -> float:
def find_intent_clusters(intent_list: list[Intent], t: float,
iterations: int) -> float:
# init
cluster_list = []
cluster_index = 0
......
def get_summary(text:str):
return "summary of " + text[::2]
\ No newline at end of file
def get_summary(text: str):
return "summary of " + text[::2]
from abc import ABC, abstractmethod
#interface for summary strategies
# interface for summary strategies
class ISummaryStrategy(ABC):
#id of the suammary strategy
# id of the suammary strategy
@property
def id(self):
raise NotImplementedError
#summarizes a text
# summarizes a text
@abstractmethod
def summarize(self, text: str) -> str:
raise NotImplementedError
\ No newline at end of file
def summarize(self, text: str) -> str:
raise NotImplementedError
......@@ -5,61 +5,96 @@ from app.similarity.intent import Intent
class TestSimilarityAnalysis(TestCase):
rki_faq = [
"Was ist über den Erreger und die Krankheit bekannt?",
"Wie erfasst das RKI die Situation in Deutschland, wie schätzt das RKI die Lage ein und welche Empfehlungen gibt es für die Fachöffentlichkeit?",
"Was versteht man unter der Reproduktionszahl R, und wie wichtig ist sie für die Bewertung der Lage?",
"Welchen Zusammenhang gibt es generell zwischen erhöhten Testzahlen und erhöhten Fallzahlen?",
"Wie erfasst das RKI die Situation in Deutschland, wie schätzt das RKI"
" die Lage ein und welche Empfehlungen gibt es für die "
"Fachöffentlichkeit?",
"Was versteht man unter der Reproduktionszahl R, und wie wichtig ist"
" sie für die Bewertung der Lage?",
"Welchen Zusammenhang gibt es generell zwischen erhöhten Testzahlen "
"und erhöhten Fallzahlen?",
"Gibt es eine Saisonalität bei SARS-CoV-2?",
"Welche Rolle spielen die neuen, besorgniserregenden Varianten?",
"Wo gibt es die aktuellen Fallzahlen und Inzidenzen?",
"Was ist alles meldepflichtig?",
"Wie funktioniert der Meldeweg und welche Informationen zu den Erkrankten werden ans RKI übermittelt?",
"Werden die Meldedaten durch die wachsende Anzahl an Schnelltests verzerrt?",
"Wie entsteht die Diskrepanz zwischen Inzidenzen der Landkreise und den Angaben des RKI-Dashboards?",
"Warum sind die Fallzahlen am/nach dem Wochenende geringer als an Arbeitstagen?",
"Wie erfassen Gesundheitsämter Fälle, Ausbrüche und Infektionsumstände?",
"Wie funktioniert der Meldeweg und welche Informationen zu den "
"Erkrankten werden ans RKI übermittelt?",
"Werden die Meldedaten durch die wachsende Anzahl an Schnelltests "
"verzerrt?",
"Wie entsteht die Diskrepanz zwischen Inzidenzen der Landkreise und "
"den Angaben des RKI-Dashboards?",
"Warum sind die Fallzahlen am/nach dem Wochenende geringer als an "
"Arbeitstagen?",
"Wie erfassen Gesundheitsämter Fälle, Ausbrüche und "
"Infektionsumstände?",
"Wie werden Todesfälle erfasst?",
"Weiß man, wie viele COVID-19-Patienten im Krankenhaus und auf Intensivstationen behandelt werden und wie viele die akute Infektion überstanden haben?",
"Was ist der Unterschied zwischen den COVID-19-Meldedaten nach <abbr>IfSG </abbr>und SARS-CoV-2-Nachweisen aus dem Sentinel der Arbeitsgemeinschaft Influenza?",
"Weiß man, wie viele COVID-19-Patienten im Krankenhaus und auf "
"Intensivstationen behandelt werden und wie viele"
" die akute Infektion überstanden haben?",
"Was ist der Unterschied zwischen den COVID-19-Meldedaten nach "
"<abbr>IfSG </abbr>und SARS-CoV-2-Nachweisen aus"
" dem Sentinel der Arbeitsgemeinschaft Influenza?",
"Warum bilden sich die COVID-19-Wellen bisher nicht bei GrippeWeb ab?",
"Wieso unterscheidet sich die Anzahl der COVID-19 Fälle aus dem ICOSARI-Krankenhaus-Sentinel von der Anzahl Intensivpatienten mit COVID-19 aus dem DIVI-Intensivregister?",
"Welche Gruppen sind besonders häufig von einem schweren Verlauf betroffen?",
"Wieso unterscheidet sich die Anzahl der COVID-19 Fälle aus dem "
"ICOSARI-Krankenhaus-Sentinel von der Anzahl"
" Intensivpatienten mit COVID-19 aus dem DIVI-Intensivregister?",
"Welche Gruppen sind besonders häufig von einem schweren Verlauf "
"betroffen?",
"Ist man nach einer durchgemachten SARS-CoV-2-Infektion immun?",
"Was ist über COVID-19 bei Kindern und Jugendlichen bekannt?",
"Was ist über COVID-19 bei Schwangeren bekannt?",
"Ist die Blutgruppe ein Risikofaktor für COVID-19?",
"Welche Behandlungsmöglichkeiten stehen für eine COVID-19-Erkrankung zur Verfügung?",
"Warum sind bei SARS-CoV-2/COVID-19 solche weitreichenden Maßnahmen erforderlich?",
"Wie kann man sich bzw. seine Mitmenschen vor einer Ansteckung schützen?",
"Welche Rolle spielen die Impfungen gegen COVID-19, Impfungen im Rahmen der Pandemie und was gilt es beim Impfen zu beachten?",
"Welchen Vorteil bringt Abstand halten die Beschränkung sozialer Kontakte?",
"Was ist beim Tragen einer Mund-Nasen-Bedeckung bzw. eines Mund-Nasen-Schutzes (\"OP-Maske\") in der Öffentlichkeit zu beachten?",
"Welche Funktion bzw. Einsatzbereiche haben FFP2-Masken außerhalb des Arbeitsschutzes?",
"Ist der Einsatz von Visieren anstatt einer Mund-Nasen-Bedeckung im öffentlichen Raum sinnvoll?",
"Welche Behandlungsmöglichkeiten stehen für eine COVID-19-Erkrankung"
" zur Verfügung?",
"Warum sind bei SARS-CoV-2/COVID-19 solche weitreichenden Maßnahmen"
" erforderlich?",
"Wie kann man sich bzw. seine Mitmenschen vor einer Ansteckung"
" schützen?",
"Welche Rolle spielen die Impfungen gegen COVID-19, Impfungen im"
" Rahmen der Pandemie und was gilt es beim"
" Impfen zu beachten?",
"Welchen Vorteil bringt Abstand halten die Beschränkung sozialer"
" Kontakte?",
"Was ist beim Tragen einer Mund-Nasen-Bedeckung bzw. eines"
" Mund-Nasen-Schutzes (\"OP-Maske\") in der "
"Öffentlichkeit zu beachten?",
"Welche Funktion bzw. Einsatzbereiche haben FFP2-Masken außerhalb des"
" Arbeitsschutzes?",
"Ist der Einsatz von Visieren anstatt einer Mund-Nasen-Bedeckung im"
" öffentlichen Raum sinnvoll?",
"Ist der Einsatz von Visieren im öffentlichen Raum sinnvoll?",
"Welche Rolle spielen Aerosole bei der Übertragung von SARS-CoV-2?",
"Was ist beim Lüften zu beachten?",
"Können Luftreinigungsgeräte bzw. mobile Luftdesinfektionsgeräte andere Hygienemaßnahmen ersetzen?",
"Können Luftreinigungsgeräte bzw. mobile Luftdesinfektionsgeräte"
" andere Hygienemaßnahmen ersetzen?",
"Was ist aus Sicht des Infektionsschutzes im Schulumfeld zu beachten?",
"Was ist beim Umgang mit an COVID-19-Verstorbenen zu beachten?",
"Was müssen Arbeitnehmerinnen und Arbeitnehmer während der COVID-19-Pandemie beachten, welche Verpflichtungen haben Arbeitgeber?",
"Was müssen Arbeitnehmerinnen und Arbeitnehmer während der"
" COVID-19-Pandemie beachten, welche Verpflichtungen"
" haben Arbeitgeber?",
"Was ist bei Reisen zu beachten?",
"Besteht die Gefahr, sich über Lebensmittel, Oberflächen, Gegenstände oder in der Umwelt mit SARS-CoV-2 anzustecken?",
"Besteht die Gefahr, sich über Lebensmittel, Oberflächen,"
" Gegenstände oder in der Umwelt mit SARS-CoV-2"
" anzustecken?",
"Welche Empfehlungen gibt es für den Umgang mit Haustieren?",
"Was versteht man unter Isolierung, was unter Quarantäne und welchen Zweck haben diese?",
"Was versteht man unter Isolierung, was unter Quarantäne und welchen"
" Zweck haben diese?",
"Wann und wie lange muss man in Quarantäne?",
"Was wird empfohlen bei Personen, die als genesen gelten?",
"Was versteht man unter Kontaktpersonennachverfolgung, was müssen Kontaktpersonen beachten?",
"Was versteht man unter Kontaktpersonennachverfolgung, was müssen"
" Kontaktpersonen beachten?",
"Wie funktioniert die Corona Warn-App?",
"Wie funktioniert die Corona App?",
"Was ist ein Containment Scout?",
"Wie geht man bei Ausbruchsuntersuchungen vor?",
"Wie wird eine Infektion mit SARS-CoV-2 labordiagnostisch nachgewiesen, welche Tests gibt es?",
"Wie wird eine Infektion mit SARS-CoV-2 labordiagnostisch nachgewiesen"
", welche Tests gibt es?",
"Welche Anforderungen werden an Antigen-Tests gestellt?",
"Was ist bei Antigentests zur Eigenanwendung (Selbsttests) zu beachten?",
"Wann sollte ein Arzt eine Laboruntersuchung auf SARS-CoV-2 veranlassen?",
"Was ist bei Antigentests zur Eigenanwendung (Selbsttests) zu"
" beachten?",
"Wann sollte ein Arzt eine Laboruntersuchung auf SARS-CoV-2"
" veranlassen?",
"Was sollen Betroffene mit Symptomen tun?",
"Was sollen Symptomen mit Betroffene tun?",
"Was sollen Betroffene tun?",
......
......@@ -34,7 +34,7 @@ class TestNlpApi(TestCase):
# run test
response = client.get("/strategies")
#assert results
# assert results
assert response.status_code == 200
assert response.json() == ["test1", "test2"]
......@@ -49,7 +49,7 @@ class TestNlpApi(TestCase):
response2 = client.post(
"/summarize/test2", json.dumps({"text": "test text"}))
#assert result
# assert result
assert response1.status_code == 200
assert response1.json() == {"strategy": "test1",
"quality": 0.5, "summary": "test text"}
......
from app.summary.summary_strategy_interface import ISummaryStrategy
from unittest import TestCase
#valid implementation of ISummaryStrategy
# valid implementation of ISummaryStrategy
class TestImplementationValid(ISummaryStrategy):
id = "id1"
def summarize(self, text:str) -> str:
def summarize(self, text: str) -> str:
return text
#invalid implementation of ISummaryStrategy, missing id definition
# invalid implementation of ISummaryStrategy, missing id definition
class TestImplementationInvalidOne(ISummaryStrategy):
def summarize(self, text:str) -> str:
def summarize(self, text: str) -> str:
return text
#actual test case
# actual test case
class TestISummaryStrategy(TestCase):
def test_valid_implementation(self):
#init test data
# init test data
test_text = "this is a test text"
test_id = "id1"
#run test functions
# run test functions
ti = TestImplementationValid()
summary = ti.summarize(test_text)
#assert result
# assert result
self.assertEqual(summary, test_text)
self.assertEqual(ti.id, test_id)
def test_invalid_implementation_1(self):
#run test functions
# run test functions
excepted = False
try:
ti = TestImplementationInvalidOne()
......@@ -37,5 +40,5 @@ class TestISummaryStrategy(TestCase):
except NotImplementedError:
excepted = True
#assert result
self.assertTrue(excepted)
\ No newline at end of file
# assert result
self.assertTrue(excepted)
from unittest import TestCase
from app.utilities import generator
class TestIntentHandler(TestCase):
def test_generate_intent_id(self):
obj = generator.IntentHandler("Wo können sich hessische Bürger*Innen über das Virus informieren?")
obj = generator.IntentHandler("Wo können sich hessische Bürger*Innen"
" über das Virus informieren?")
id = obj.generate_intent_id(3)
assert id == "buergerinnen_virus_informieren"
import spacy
# from spacy.lang.de import German
# from spacy.lang.de.stop_words import STOP_WORDS
import de_core_news_md
import re # regular expressions
import re # regular expressions
class IntentHandler:
nlp = spacy.load("de_core_news_md")
......@@ -13,9 +14,11 @@ class IntentHandler:
def generate_intent_id(self, maxTokens: int):
"""
Takes all nouns and verbs of the given intents, replaces umlauts by ue etc. and other non-character chars,
lowercases them and returns them joined by _, e.g. the intent
'Wo können sich hessische Bürger*Innen über das Virus informieren?' will return 'buergerinnen_virus_informieren'
Takes all nouns and verbs of the given intents, replaces umlauts by
ue etc. and other non-character chars, lowercases them and returns
them joined by _, e.g. the intent 'Wo können sich hessische
Bürger*Innen über das Virus informieren?' will return
'buergerinnen_virus_informieren'
:param maxTokens: maximum number of tokens to be taken
:return: string describing the given intent
......@@ -25,20 +28,21 @@ class IntentHandler:
result_tokens = []
for index, pos in enumerate(pos_tags):
if pos == "NOUN" or pos == "VERB":
result_tokens.append(self.remove_non_ascii_chars(token_texts[index]))
result_tokens.append(
self.remove_non_ascii_chars(token_texts[index]))
# take the first maxtokens tokens and join them by _
shortened = result_tokens[0:maxTokens]
return '_'.join([str(x) for x in shortened])
def remove_non_ascii_chars(self, text):
""" remove everything that is not a-zA-Z0-9_ and make lower case, replace german umlauts by ue etc.
""" remove everything that is not a-zA-Z0-9_ and make lower case,
replace german umlauts by ue etc.
"""
text = text.lower().replace('ü', 'ue').replace('ä', 'ae').replace('ö', 'oe')
text = text.lower().replace('ü', 'ue').replace('ä', 'ae').replace('ö',
'oe')
text = re.sub('[^a-zA-Z0-9]+', '', text)
return text
# def removeStopWords(tokens: []):
# filtered = []
# nlp = German()
......@@ -57,7 +61,8 @@ class IntentHandler:
# return normalized
#
# def preprocessor(text):
# """ remove everything that is not a word and make it lowercase -- should be called after lemmatization, because
# """ remove everything that is not a word and make it lowercase -- should
# be called after lemmatization, because
# lemmatization needs this info
# """
# # text = re.sub('<[^>]*>', '', text) to remove html tags
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment