diff --git a/src/main/webapp/src/components/TextComplexity/TextComplexityPage.css b/src/main/webapp/src/components/TextComplexity/TextComplexityPage.css new file mode 100644 index 0000000..3c3885b --- /dev/null +++ b/src/main/webapp/src/components/TextComplexity/TextComplexityPage.css @@ -0,0 +1,61 @@ +.metrics-result { + padding: 20px; + max-width: 800px; + margin: 0 auto; + background-color: #f9f9f9; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} + +.metrics-list { + list-style-type: none; + padding: 0; + display: flex; + flex-direction: column; + gap: 10px; +} + +.metric-item { + padding: 10px; + border: 1px solid #ddd; + border-radius: 4px; + cursor: pointer; + background: #ffffff; + display: flex; + justify-content: space-between; + align-items: center; +} + +.metric-item:hover { + background-color: #f0f0f0; +} + +.metric-name { + font-weight: bold; +} + +.metric-value { + color: #333; +} + +.metric-details { + margin-top: 20px; + background: #ffffff; + border: 1px solid #ddd; + border-radius: 8px; + padding: 15px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15); +} + +.close-button { + background: #ff6b6b; + color: #ffffff; + border: none; + border-radius: 4px; + padding: 5px 10px; + cursor: pointer; +} + +.close-button:hover { + background: #ff4a4a; +} diff --git a/src/main/webapp/src/components/TextComplexity/TextComplexityPage.tsx b/src/main/webapp/src/components/TextComplexity/TextComplexityPage.tsx new file mode 100644 index 0000000..49dda58 --- /dev/null +++ b/src/main/webapp/src/components/TextComplexity/TextComplexityPage.tsx @@ -0,0 +1,51 @@ +import React, { useState } from 'react'; +import './MetricsResult.css'; + +type Metric = { + name: string; + value: number | string; + description: string; +}; + +interface MetricsResultProps { + data: Metric[]; +} + +const MetricsResult: React.FC = ({ data }) => { + const [selectedMetric, setSelectedMetric] = useState(null); + + const handleMetricClick = (metric: Metric) => { + setSelectedMetric(metric); + }; + + const handleClosePanel = () => { + setSelectedMetric(null); + }; + + return ( +
+

Результаты анализа предложений

+
    + {data.map((metric, index) => ( +
  • handleMetricClick(metric)} + > + {metric.name}: + {metric.value} +
  • + ))} +
+ + {selectedMetric && ( +
+ +

{selectedMetric.name}

+
+ )} +
+ ); +}; + +export default MetricsResult; diff --git a/src/main/webapp/src/pages/guide.tsx b/src/main/webapp/src/pages/guide.tsx new file mode 100644 index 0000000..b230228 --- /dev/null +++ b/src/main/webapp/src/pages/guide.tsx @@ -0,0 +1,69 @@ +import React, { useState } from 'react'; +import { Card, Text, Button, Title } from '@mantine/core'; +import firstStepGif from "../resources/firstStep.gif" +import secondStepGif from "../resources/secondStep.gif" +import thirdStepGif from "../resources/thirdStep.gif" +import fourthStepGif from "../resources/fourthStep.gif" +import fifthStepGif from "../resources/fifthStep.gif" +import sixthStepGif from "../resources/sixthStep.gif" +import '../css/guide.css'; +const GuidePage: React.FC = () => { + const [selectedCard, setSelectedCard] = useState(null); + const arrowDownCard = 2; + const arrowRightCards = [0,1]; + const arrowLeftCards = [5,4]; + const steps = [ + { text: 'Для начала вам следует выбрать набор правил. Если вы хотите провести проверку со всеми правилами, тогда игнорируйте этот пункт. Вам следует перейти в пункт "Наборы правил и там выбрать один из существующих наборов или создать свой."', gif: firstStepGif }, + { text: 'После выбора набора правил, вам следует перейти на страницу для загрузки файлов. Здесь надо выбрать файл и начать загрузку. После обработки нажмите на количество ошибок и сможете перейти на обзор файла.', gif: secondStepGif }, + { text: 'Вам откроется страница с тремя полями, в первом идет список ошибок, нажав на которые их можно просмотреть. Также тут возможно фильтровать ошибки по названиям или страницам.', gif: thirdStepGif }, + { text: 'Наконец, если среди нарушений вы нашли ошибочное, например, вам кажется, что тут все правильно, а отчет говорит обратное, то стоит отправить нам отчет, нажав на соответствующую кнопку. ', gif: sixthStepGif }, + { text: 'По центру будет поле со страницей, на которой подчеркнута ошибка. Если ошибка занимает больше одной строки, то будут подчеркнуты несколько строк.', gif: fifthStepGif }, + { text: 'Слева будет поле, на котором будут все найденные типы ошибок. Если навести на тип ошибки, то можно увидеть описание данной ошибки. Также рядом написан тип ошибки: "предупреждение" или "ошибка". Предупреждения не так принципиальны, как ошибки.', gif: fourthStepGif }, + ]; + + const handleCardClick = (index: number) => { + if (selectedCard === index) { + setSelectedCard(null); + } else { + setSelectedCard(index); + } + }; + return ( +
+ + Приложение{' '}<br /> + <Text component="span" color="violet"> + MAP + </Text>{' '}<br />- + это инструмент<br /> для проверки студенческих <br />работ + +
+ Ниже приведен демо файл для тестового прогона программы. Также снизу есть пошаговая инструкция. +
+ +
+ {steps.map((step, index) => ( + handleCardClick(index)} + > +
+ {step.text} + {index === arrowDownCard && selectedCard !== index &&
➜➤
} + {arrowLeftCards.includes(index) && selectedCard !== index &&
➜➤
} + {selectedCard !== index && arrowRightCards.includes(index) &&
➜➤
} + {selectedCard === index && {`Step} +
+
+ ))} +
+
+ ); +}; + +export default GuidePage; \ No newline at end of file diff --git a/text_complexity_microservice/Dockerfile b/text_complexity_microservice/Dockerfile new file mode 100644 index 0000000..27fb58a --- /dev/null +++ b/text_complexity_microservice/Dockerfile @@ -0,0 +1,6 @@ +FROM python:3.10-slim-buster +WORKDIR /code +COPY ./requirements.txt /code/requirements.txt +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt +COPY ./src/app /code/src/app +CMD ["uvicorn", "src.app.api:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/text_complexity_microservice/main.py b/text_complexity_microservice/main.py new file mode 100644 index 0000000..5c39c2a --- /dev/null +++ b/text_complexity_microservice/main.py @@ -0,0 +1,6 @@ +import uvicorn + +from src.app.api import app + +if __name__ == '__main__': + uvicorn.run(app, host="localhost", port=8000) diff --git a/text_complexity_microservice/requirements.txt b/text_complexity_microservice/requirements.txt new file mode 100644 index 0000000..be84493 --- /dev/null +++ b/text_complexity_microservice/requirements.txt @@ -0,0 +1,9 @@ +fastapi==0.85.1 +uvicorn==0.19.0 +orjson==3.8.1 +spacy==3.4.2 +spacy_conll==3.2.0 +pydantic==1.10.2 +textcomplexity==0.11.0 + +ru_core_news_sm @ https://github.com/explosion/spacy-models/releases/download/ru_core_news_sm-3.4.0/ru_core_news_sm-3.4.0-py3-none-any.whl diff --git a/text_complexity_microservice/src/__init__.py b/text_complexity_microservice/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/text_complexity_microservice/src/app/__init__.py b/text_complexity_microservice/src/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/text_complexity_microservice/src/app/api.py b/text_complexity_microservice/src/app/api.py new file mode 100644 index 0000000..d45fd51 --- /dev/null +++ b/text_complexity_microservice/src/app/api.py @@ -0,0 +1,48 @@ +from fastapi import FastAPI, Query +from fastapi.responses import ORJSONResponse + +from src.app import measure_calculator +from src.app.conll_converter import ConllConverter +from src.app.measure import SentenceMeasures +from src.app.models.request import SentenceMeasuresRequest, MeasureName +from src.app.models.response import SentenceMeasuresResponse, MeasureResult + +app = FastAPI(title="text-complexity-service", default_response_class=ORJSONResponse) + +conll_converters = {} + + +@app.on_event("startup") +async def startup_event(): + for lang, conv in ConllConverter.all_language_converters().items(): + conll_converters[lang] = conv + + +@app.get("/", include_in_schema=False) +async def openapi(): + return app.openapi() + + +@app.post("/sentence_measures", response_model=list[SentenceMeasuresResponse]) +async def sentence_measures(body: list[SentenceMeasuresRequest], measure: set[MeasureName] = Query(default=None)): + response = [] + if not measure: + measure = [m for m in MeasureName] + measure = [SentenceMeasures[m] for m in measure] + + for item in body: + if not item.sentence.strip(): + response.append(SentenceMeasuresResponse(id=item.id, error="Empty sentence")) + continue + + conll_sentences = conll_converters[item.language].text2conll_str(item.sentence).split("\n\n") + if len(conll_sentences) != 1: + response.append(SentenceMeasuresResponse(id=item.id, error="Required one sentence")) + continue + + conll_sentence = conll_sentences[0] + calculated_measures = [(m.name, measure_calculator.calculate_for_sentence(conll_sentence, m)) + for m in measure] + calculated_measures = [MeasureResult(name=res[0], value=res[1]) for res in calculated_measures] + response.append(SentenceMeasuresResponse(id=item.id, measures=calculated_measures)) + return response diff --git a/text_complexity_microservice/src/app/conll_converter.py b/text_complexity_microservice/src/app/conll_converter.py new file mode 100644 index 0000000..51b96b8 --- /dev/null +++ b/text_complexity_microservice/src/app/conll_converter.py @@ -0,0 +1,28 @@ +from spacy_conll import init_parser + + +class ConllConverter: + _spacy_models = { + "ru": "ru_core_news_sm", + } + + def __init__(self, lang: str): + if lang not in ConllConverter.supported_languages(): + raise ValueError("Unsupported language") + + self.nlp = init_parser(ConllConverter._spacy_models[lang], + "spacy", + conversion_maps={"deprel": {"ROOT": "root"}}) + + def text2conll_str(self, text: str) -> str: + doc = self.nlp(text) + return doc._.conll_str + + @staticmethod + def supported_languages() -> list[str]: + return list(ConllConverter._spacy_models.keys()) + + @staticmethod + def all_language_converters() -> dict[str, 'ConllConverter']: + languages = ConllConverter.supported_languages() + return dict([(lang, ConllConverter(lang)) for lang in languages]) diff --git a/text_complexity_microservice/src/app/measure.py b/text_complexity_microservice/src/app/measure.py new file mode 100644 index 0000000..a9a4f49 --- /dev/null +++ b/text_complexity_microservice/src/app/measure.py @@ -0,0 +1,58 @@ +from enum import Enum +from typing import Callable, NamedTuple + +from textcomplexity import surface, dependency, sentence + + +class MeasureType(Enum): + text = 0 + sentence = 1 + punctuation = 2 + graph = 3 + + +class Measure(NamedTuple): + text_name: str + type: MeasureType + func: Callable + + +class SentenceMeasures(Measure, Enum): + type_token_ratio = "type-token ratio", MeasureType.text, surface.type_token_ratio + guiraud_r = "Guiraud's R", MeasureType.text, surface.guiraud_r + herdan_c = "Herdan's C", MeasureType.text, surface.herdan_c + dugast_k = "Dugast's k", MeasureType.text, surface.dugast_k + maas_a2 = "Maas' a²", MeasureType.text, surface.maas_a2 + dugast_u = "Dugast's U", MeasureType.text, surface.dugast_u + tuldava_ln = "Tuldava's LN", MeasureType.text, surface.tuldava_ln + brunet_w = "Brunet's W", MeasureType.text, surface.brunet_w + cttr = "CTTR", MeasureType.text, surface.cttr + summer_s = "Summer's S", MeasureType.text, surface.summer_s + sichel_s = "Sichel's S", MeasureType.text, surface.sichel_s + michea_m = "Michéa's M", MeasureType.text, surface.michea_m + honore_h = "Honoré's H", MeasureType.text, surface.honore_h + entropy = "entropy", MeasureType.text, surface.entropy + evenness = "evenness", MeasureType.text, surface.evenness + jarvis_evenness = "Jarvis's evenness", MeasureType.text, surface.jarvis_evenness + yule_k = "Yule's K", MeasureType.text, surface.yule_k + simpson_d = "Simpson's D", MeasureType.text, surface.simpson_d + herdan_vm = "Herdan's Vm", MeasureType.text, surface.herdan_vm + hdd = "HD-D", MeasureType.text, surface.hdd + average_token_length = "average token length", MeasureType.text, surface.average_token_length + orlov_z = "Orlov's Z", MeasureType.text, surface.orlov_z + mtld = "MTLD", MeasureType.text, surface.mtld + + sentence_length_tokens = "sentence length (tokens)", MeasureType.sentence, sentence._sentence_length_tokens + sentence_length_characters = ("sentence length (characters)", MeasureType.sentence, + sentence._sentence_length_characters) + + sentence_length_words = "sentence length (words)", MeasureType.punctuation, sentence._sentence_length_words + punctuation_per_sentence = "punctuation per sentence", MeasureType.punctuation, sentence._punctuation_per_sentence + + average_dependency_distance = ("average dependency distance", MeasureType.graph, + dependency._average_dependency_distance) + closeness_centrality = "closeness centrality", MeasureType.graph, dependency._closeness_centrality + outdegree_centralization = "outdegree centralization", MeasureType.graph, dependency._outdegree_centralization + closeness_centralization = "closeness centralization", MeasureType.graph, dependency._closeness_centralization + longest_shortest_path = "longest shortest path", MeasureType.graph, dependency._longest_shortest_path + dependents_per_word = "dependents per word", MeasureType.graph, dependency._dependents_per_word diff --git a/text_complexity_microservice/src/app/measure_calculator.py b/text_complexity_microservice/src/app/measure_calculator.py new file mode 100644 index 0000000..62afb3b --- /dev/null +++ b/text_complexity_microservice/src/app/measure_calculator.py @@ -0,0 +1,36 @@ +import itertools +import math + +from textcomplexity.utils import conllu +from textcomplexity.utils.text import Text + +from src.app.measure import MeasureType, Measure + + +def calculate_for_sentence(conll_sentence: str, measure: Measure, punct_tags: list[str] = None) -> float: + if not punct_tags: + punct_tags = ["PUNCT"] + + conll_sentence = conll_sentence.split("\n") + sentences, graphs = zip(*conllu.read_conllu_sentences(conll_sentence, ignore_case=True)) + if len(sentences) > 1: + raise ValueError() + tokens = list(itertools.chain.from_iterable(sentences)) + graph = graphs[0] + + try: + match measure.type: + case MeasureType.text: + tokens = [t for t in tokens if t.pos not in punct_tags] + text = Text.from_tokens(tokens) + return measure.func(text) + case MeasureType.sentence: + return measure.func(tokens) + case MeasureType.punctuation: + return measure.func(tokens, punct_tags) + case MeasureType.graph: + return measure.func(graph) + case _: + raise TypeError() + except (ZeroDivisionError, ValueError): + return math.nan diff --git a/text_complexity_microservice/src/app/models/__init__.py b/text_complexity_microservice/src/app/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/text_complexity_microservice/src/app/models/request.py b/text_complexity_microservice/src/app/models/request.py new file mode 100644 index 0000000..553930d --- /dev/null +++ b/text_complexity_microservice/src/app/models/request.py @@ -0,0 +1,17 @@ +from enum import Enum + +from pydantic import BaseModel + +from src.app.conll_converter import ConllConverter +from src.app.measure import SentenceMeasures + + +MeasureName = Enum("Measure", dict([(m.name, m.name) for m in SentenceMeasures]), type=str) + +Language = Enum("Language", dict([(lang, lang) for lang in ConllConverter.supported_languages()]), type=str) + + +class SentenceMeasuresRequest(BaseModel): + id: int + sentence: str + language: Language diff --git a/text_complexity_microservice/src/app/models/response.py b/text_complexity_microservice/src/app/models/response.py new file mode 100644 index 0000000..a46d4fc --- /dev/null +++ b/text_complexity_microservice/src/app/models/response.py @@ -0,0 +1,14 @@ +from typing import Optional + +from pydantic import BaseModel + + +class MeasureResult(BaseModel): + name: str + value: float + + +class SentenceMeasuresResponse(BaseModel): + id: int + measures: list[MeasureResult] = [] + error: Optional[str] = None diff --git a/text_complexity_microservice/src/tests/__init__.py b/text_complexity_microservice/src/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/text_complexity_microservice/src/tests/config.py b/text_complexity_microservice/src/tests/config.py new file mode 100644 index 0000000..c4ea4aa --- /dev/null +++ b/text_complexity_microservice/src/tests/config.py @@ -0,0 +1,3 @@ +import pathlib + +resource_folder = f"{pathlib.Path(__file__).parent.resolve()}/resources" diff --git a/text_complexity_microservice/src/tests/requirements.txt b/text_complexity_microservice/src/tests/requirements.txt new file mode 100644 index 0000000..ee2f90c --- /dev/null +++ b/text_complexity_microservice/src/tests/requirements.txt @@ -0,0 +1,2 @@ +pytest==7.2.0 +conllu==4.5.2 diff --git a/text_complexity_microservice/src/tests/resources/conll_sentence.txt b/text_complexity_microservice/src/tests/resources/conll_sentence.txt new file mode 100644 index 0000000..d2e2f78 --- /dev/null +++ b/text_complexity_microservice/src/tests/resources/conll_sentence.txt @@ -0,0 +1,15 @@ +# text = Обработка текстов на естественном языке — общее направление искусственного интеллекта и математической лингвистики. +1 Обработка обработка NOUN NOUN Animacy=Inan|Case=Nom|Gender=Fem|Number=Sing 8 nsubj _ _ +2 текстов текст NOUN NOUN Animacy=Inan|Case=Gen|Gender=Masc|Number=Plur 1 nmod _ _ +3 на на ADP ADP _ 5 case _ _ +4 естественном естественный ADJ ADJ Case=Loc|Degree=Pos|Gender=Masc|Number=Sing 5 amod _ _ +5 языке язык NOUN NOUN Animacy=Inan|Case=Loc|Gender=Masc|Number=Sing 2 nmod _ _ +6 — — PUNCT PUNCT _ 1 punct _ _ +7 общее общий ADJ ADJ Case=Nom|Degree=Pos|Gender=Neut|Number=Sing 8 amod _ _ +8 направление направление NOUN NOUN Animacy=Inan|Case=Nom|Gender=Neut|Number=Sing 0 root _ _ +9 искусственного искусственный ADJ ADJ Case=Gen|Degree=Pos|Gender=Masc|Number=Sing 10 amod _ _ +10 интеллекта интеллект NOUN NOUN Animacy=Inan|Case=Gen|Gender=Masc|Number=Sing 8 nmod _ _ +11 и и CCONJ CCONJ _ 13 cc _ _ +12 математической математический ADJ ADJ Case=Gen|Degree=Pos|Gender=Fem|Number=Sing 13 amod _ _ +13 лингвистики лингвистика NOUN NOUN Animacy=Inan|Case=Gen|Gender=Fem|Number=Sing 10 conj _ SpaceAfter=No +14 . . PUNCT PUNCT _ 8 punct _ SpaceAfter=No diff --git a/text_complexity_microservice/src/tests/test_api.py b/text_complexity_microservice/src/tests/test_api.py new file mode 100644 index 0000000..8ff0905 --- /dev/null +++ b/text_complexity_microservice/src/tests/test_api.py @@ -0,0 +1,89 @@ +from fastapi.testclient import TestClient +from fastapi import status + +from src.app.api import app +from src.app.measure import SentenceMeasures +from src.app.models.request import SentenceMeasuresRequest, MeasureName + +url_measures = "/sentence_measures" + +test_sentence = "Обработка текстов на естественном языке — общее направление искусственного интеллекта " \ + "и математической лингвистики." + + +def test_root_get(): + with TestClient(app) as client: + response = client.get("/") + assert response.status_code == status.HTTP_200_OK + + +def test_one_sentence_measures_post(): + with TestClient(app) as client: + s_id = 0 + body = [SentenceMeasuresRequest(id=s_id, sentence=test_sentence, language="ru").dict()] + response = client.post(url_measures, json=body) + + assert response.status_code == status.HTTP_200_OK + + json = response.json() + assert isinstance(json, list) and len(json) == 1 + + first_obj = json[0] + assert first_obj["id"] == s_id + assert first_obj["error"] is None + + measures = first_obj["measures"] + assert isinstance(measures, list) and len(measures) == len(SentenceMeasures) + + actual_names = [m["name"] for m in measures] + expected_names = [m.name for m in SentenceMeasures] + assert all([a == b for a, b in zip(sorted(actual_names), sorted(expected_names))]) + + +def test_request_specific_measures(): + with TestClient(app) as client: + body = [SentenceMeasuresRequest(id=0, sentence=test_sentence, language="ru").dict()] + measures = [m.value for m in MeasureName][:3] + response = client.post(url_measures, json=body, params={"measure": measures}) + + measures_response = response.json()[0]["measures"] + assert isinstance(measures_response, list) and len(measures_response) == len(measures) + + actual_names = [m["name"] for m in measures_response] + assert all([a == b for a, b in zip(sorted(actual_names), sorted(measures))]) + + +def test_many_sentences_measures_post(): + with TestClient(app) as client: + number = 100 + body = [SentenceMeasuresRequest(id=s_id, sentence=test_sentence, language="ru").dict() for s_id in range(number)] + response = client.post(url_measures, json=body) + + assert response.status_code == status.HTTP_200_OK + + json = response.json() + assert isinstance(json, list) and len(json) == number + for item in json: + assert item["error"] is None + + +def test_empty_sentence(): + with TestClient(app) as client: + empty_sentences = ["", " "] + body = [SentenceMeasuresRequest(id=i, sentence=s, language="ru").dict() for i, s in enumerate(empty_sentences)] + response = client.post(url_measures, json=body) + + assert response.status_code == status.HTTP_200_OK + for item in response.json(): + assert item["error"] is not None + + +def test_more_one_sentence(): + with TestClient(app) as client: + two_sentences = " ".join([test_sentence, test_sentence]) + body = [SentenceMeasuresRequest(id=0, sentence=two_sentences, language="ru").dict()] + + response = client.post(url_measures, json=body) + + assert response.status_code == status.HTTP_200_OK + assert response.json()[0]["error"] is not None diff --git a/text_complexity_microservice/src/tests/test_conllu_converter.py b/text_complexity_microservice/src/tests/test_conllu_converter.py new file mode 100644 index 0000000..8f84730 --- /dev/null +++ b/text_complexity_microservice/src/tests/test_conllu_converter.py @@ -0,0 +1,39 @@ +import conllu as conllu +import pytest + +from src.app.conll_converter import ConllConverter + + +def test_supported_languages(): + ru_lang = "ru" + assert ru_lang in ConllConverter.supported_languages() + try: + _ = ConllConverter(ru_lang) + except ValueError: + pytest.fail("Unexpected Error") + + en_lang = "en" + assert en_lang not in ConllConverter.supported_languages() + with pytest.raises(ValueError): + _ = ConllConverter(en_lang) + + +def test_text2conll_str(): + text = "Обработка текстов на естественном языке — общее направление искусственного интеллекта " \ + "и математической лингвистики." + converter = ConllConverter("ru") + conll = converter.text2conll_str(text) + + sentences = conllu.parse(conll) + assert len(sentences) == 1 + + sentence = sentences[0] + assert len(sentence) == 14 + + expected_ids = list(range(1, 15)) + actual_ids = [token['id'] for token in sentence] + assert actual_ids == expected_ids + + expected_words = text.replace(".", " .").split() + actual_words = [token['form'] for token in sentence] + assert actual_words == expected_words diff --git a/text_complexity_microservice/src/tests/test_measure_calculator.py b/text_complexity_microservice/src/tests/test_measure_calculator.py new file mode 100644 index 0000000..9a14252 --- /dev/null +++ b/text_complexity_microservice/src/tests/test_measure_calculator.py @@ -0,0 +1,21 @@ +import os + +import pytest + +from src.app import measure_calculator as mc +from src.app.measure import SentenceMeasures +from src.tests import config + + +def test_measure_calculator(): + conll_file = "conll_sentence.txt" + conll_file_path = os.path.join(config.resource_folder, conll_file) + + with open(conll_file_path, encoding='utf-8') as f: + conll_sentence = f.read() + + for measure in SentenceMeasures: + try: + _ = mc.calculate_for_sentence(conll_sentence, measure) + except: + pytest.fail("Unexpected Error")