Repository: moevm/nosql1h25-reports
Pull request #12: Creating diploma repository
Author: login: vRedniy-LumbeRt
Created: 2025-04-27T07:20:42Z | Merged: 2025-04-27T20:36:47Z



Diff:
diff --git a/src/diploma_processing/data_types.py b/src/diploma_processing/data_types.py
index 7489637..d97506f 100644
--- a/src/diploma_processing/data_types.py
+++ b/src/diploma_processing/data_types.py
@@ -12,7 +12,7 @@ class Chapter:
     symbols: int
     commonly_used_words: list[str]
     commonly_used_words_amount: list[int]
-    chapters: list['Chapter']
+    chapters: list['Chapter' | int]
 
 
 @dataclass
@@ -24,4 +24,4 @@ class Diploma:
     year: int
     words: int
     load_date: datetime
-    chapters: list[Chapter]
+    chapters: list['Chapter' | int]
diff --git a/src/repository/diploma_repo.py b/src/repository/diploma_repo.py
new file mode 100644
index 0000000..09714a5
--- /dev/null
+++ b/src/repository/diploma_repo.py
@@ -0,0 +1,379 @@
+import sys
+from neo4j import GraphDatabase
+from neo4j.exceptions import ServiceUnavailable
+from src.diploma_processing.data_types import Chapter, Diploma
+
+
+class Neo4jDatabase:
+    def __init__(self, uri, user, password):
+        """
+        Инициализация соединения с базой данных Neo4j.
+        """
+        self._driver = None
+        try:
+            self._driver = GraphDatabase.driver(uri, auth=(user, password))
+            # >>> ПРОВЕРКА ДОСТУПНОСТИ БАЗЫ <<<
+            with self._driver.session() as session:
+                session.run("RETURN 1")  # минимальный запрос
+            print("Подключение к базе данных успешно установлено.")
+        except ServiceUnavailable as e:
+            print(f"Не удалось подключиться к базе данных: {e}")
+            raise
+        except Exception as e:
+            print(f"Ошибка подключения к базе данных: {e}")
+            raise
+
+    def close(self):
+        """
+        Закрытие соединения с базой данных.
+        """
+        if self._driver:
+            try:
+                self._driver.close()
+                print("Соединение с базой данных успешно закрыто.")
+            except Exception as e:
+                print(f"Ошибка при закрытии соединения: {e}")
+
+    def query(self, query, parameters=None):
+        """
+        Выполнение Cypher-запроса.
+        :param query: Строка с Cypher-запросом.
+        :param parameters: Параметры для запроса (опционально).
+        :return: Результат выполнения запроса.
+        """
+        assert self._driver is not None, "Driver not initialized!"
+        session = None
+        response = None
+
+        try:
+            session = self._driver.session()
+            if parameters is None:
+                response = list(session.run(query))
+            else:
+                response = list(session.run(query, parameters))  # <-- сюда передать parameters
+            print("Запрос успешно выполнен.")
+        except Exception as e:
+            print(f"Ошибка при выполнении запроса: {e}")
+        finally:
+            if session is not None:
+                session.close()
+
+        return response
+
+
+class DiplomaRepository:
+    def __init__(self, database: Neo4jDatabase):
+        self.database = database
+
+    def save_diploma(self, diploma: Diploma) -> int | None:
+        query = """
+        CREATE (d:Diploma { 
+            name: $name, 
+            author: $author, 
+            academic_supervisor: $academic_supervisor, 
+            year: $year, 
+            words: $words,
+            load_date: $load_date
+        })
+        SET d.id = ID(d)
+        RETURN d.id
+        """
+        parameters = {
+            "name": diploma.name,
+            "author": diploma.author,
+            "academic_supervisor": diploma.academic_supervisor,
+            "year": diploma.year,
+            "words": diploma.words,
+            "load_date": diploma.load_date.date()
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            diploma.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return diploma.id
+        return None
+
+    def save_chapter(self, chapter: Chapter) -> int | None:
+        query = """
+        CREATE (c:Chapter {
+            id_diploma: $id_diploma, 
+            name: $name, 
+            water_content: $water_content, 
+            words: $words, 
+            symbols: $symbols, 
+            commonly_used_words: $commonly_used_words, 
+            commonly_used_words_amount: $commonly_used_words_amount
+        })
+        SET c.id = ID(c)
+        RETURN c.id
+        """
+        parameters = {
+            "id_diploma": chapter.id_diploma,
+            "name": chapter.name,
+            "water_content": chapter.water_content,
+            "words": chapter.words,
+            "symbols": chapter.symbols,
+            "commonly_used_words": chapter.commonly_used_words,
+            "commonly_used_words_amount": chapter.commonly_used_words_amount
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            chapter.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return chapter.id
+        return None
+
+    def link_chapter_to_diploma(self, id_diploma: int) -> None:
+        query = """
+        MATCH (d:Diploma), (c:Chapter) 
+        WHERE d.id = $id_diploma AND c.id_diploma = $id_diploma
+        CREATE (d)-[:CONTAINS]->(c)
+        """
+        parameters = {"id_diploma": id_diploma}
+        self.database.query(query, parameters)
+
+    def link_subchapters(self, parent_chapter_id: int, subchapter_ids: list[int]) -> None:
+        query = """
+        MATCH (c1:Chapter), (c2:Chapter) 
+        WHERE c1.id = $parent_chapter_id AND c2.id IN $subchapter_ids
+        CREATE (c1)-[:CONTAINS]->(c2)
+        """
+        parameters = {
+            "parent_chapter_id": parent_chapter_id,
+            "subchapter_ids": subchapter_ids
+        }
+        self.database.query(query, parameters)
+
+    def get_other_diplomas_shingles(self, id_diploma: int) -> list[tuple[int, list[int]]]:
+        query = """
+        MATCH (d:Diploma)
+        WHERE d.id <> $id_diploma
+        RETURN d.id, d.shingles
+        """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+        return [(int(record[0]), list(map(int, record[1]))) for record in result]
+
+    def save_similarity(self, id_diploma: int, id_diploma_2: int, similarity: float) -> None:
+        query = """
+        MATCH (d1:Diploma), (d2:Diploma)
+        WHERE d1.id = $id_diploma AND d2.id = $id_diploma_2
+        CREATE (d1)-[:SIMILAR_TO {similarity: $similarity}]->(d2)
+        """
+        parameters = {
+            "id_diploma": id_diploma,
+            "id_diploma_2": id_diploma_2,
+            "similarity": similarity
+        }
+        self.database.query(query, parameters)
+
+    def load_diploma_graph(self, id_diploma: int) -> list[list[int], list[list[int]]]:
+        query = """
+        MATCH p = (d:Diploma)-[r:CONTAINS*0..]->(x) 
+        WHERE d.id = $id_diploma
+        RETURN 
+            COLLECT(DISTINCT ID(x)) AS nodes,
+            [r IN COLLECT(DISTINCT LAST(r)) | [ID(startNode(r)), ID(endNode(r))]] AS rels
+        """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+        nodes, rels = result[0] if result else ([], [])
+        return [list(map(int, nodes)), rels]
+
+    def subchapters_recursive_search(self, rels, id, key_run):
+        """Метод рекурсивного построения дерева диплома / раздела
+        Доступные ключи key:
+            - 1: работа с дипломом
+            - 2: работа с разделом
+        """
+        if key_run not in [1, 2]:
+            print(f"Неверный ключ: {key_run} (1 или 2 доступны)")
+            raise sys.exit(1)
+
+        def clear_rels(rls):
+            rls = sorted(rls)
+            i, j = 0, len(rls) // 2 + 1
+            while i <= j and i < len(rls):
+                while j < len(rls):
+                    if rls[i][1] == rls[j][1]:
+                        rls.pop(i)
+                        i -= 1
+                        break
+                    j += 1
+
+                i += 1
+                j = i + 1
+            return rls
+
+        def chapter_list_filler(id, chpts: list[Chapter], rls, used):
+            while rls:
+                i = 0
+                while rls[i][0] != id:
+                    i += 1
+                    if i >= len(rls):
+                        return chpts
+
+                new_id = rls[i][1]
+                chpts.extend(self.load_chapters([rls.pop(i)[1]]))
+                if new_id in used:
+                    rls.pop(i)
+                    return chpts
+
+                chpts[-1].chapters = chapter_list_filler(new_id, chpts[-1].chapters, rls, used + [new_id])
+
+            return chpts
+
+        if key_run == 1:
+            rels = clear_rels(rels)
+
+        cpt_list = chapter_list_filler(id, [], rels, [])
+        return cpt_list
+
+    def load_diploma_data(self, id_diploma: int) -> Diploma | None:
+        query = """
+        MATCH (d:Diploma)
+        WHERE d.id = $id_diploma
+        RETURN d
+        """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+
+        node, rels = self.load_diploma_graph(id_diploma)
+        cpt_list = self.subchapters_recursive_search(rels, id_diploma, key_run=1)
+
+        if not result:
+            return None
+
+        node = result[0]["d"]
+
+        diploma = Diploma(
+            id=node.get("id"),
+            name=node.get("name", ""),
+            author=node.get("author", ""),
+            academic_supervisor=node.get("academic_supervisor"),
+            year=node.get("year", 0),
+            words=node.get("words", 0),
+            load_date=datetime.datetime(
+                node.get("load_date").year,
+                node.get("load_date").month,
+                node.get("load_date").day),
+            chapters=cpt_list
+        )
+
+        return diploma
+
+    def load_chapters(self, chapter_ids: list[int]) -> list[Chapter]:
+        query = """
+        MATCH (c:Chapter)
+        WHERE c.id IN $chapter_ids
+        RETURN c
+        """
+        parameters = {"chapter_ids": chapter_ids}
+        result = self.database.query(query, parameters)
+
+        chapters = []
+        for record in result:
+            node = record["c"]
+
+            chapter = Chapter(
+                id=node.get("id"),
+                id_diploma=node.get("id_diploma"),
+                name=node.get("name", ""),
+                water_content=node.get("water_content", 0),
+                words=node.get("words", 0),
+                symbols=node.get("symbols", 0),
+                commonly_used_words=node.get("commonly_used_words", []),
+                commonly_used_words_amount=node.get("commonly_used_words_amount", []),
+                chapters=[]
+            )
+
+            chpt_node, rels = self.load_diploma_graph(chapter.id_diploma)
+            rels = list(filter(lambda x: x[0] != chapter.id_diploma, rels))
+            chapter.chapters = self.subchapters_recursive_search(rels, chapter.id, key_run=2)
+
+            chapters.append(chapter)
+
+        return chapters
+
+    def search_diplomas(self,
+                        min_id: int = None, max_id: int = None,
+                        name: str = None, author: str = None, academic_supervisor: str = None,
+                        min_year: int = None, max_year: int = None,
+                        min_words: int = None, max_words: int = None,
+                        min_date: str = None, max_date: str = None,
+                        chapters: list[int] = None,
+                        order_by: str = None) -> list[Diploma]:
+        query = """
+        MATCH (d:Diploma)-[:CONTAINS]->(c:Chapter)
+        WITH d, COLLECT(ID(c)) AS chapters
+        WHERE ($min_id IS NULL OR d.id >= $min_id)
+          AND ($max_id IS NULL OR d.id <= $max_id)
+          AND ($name IS NULL OR toLower(d.name) CONTAINS toLower($name))
+          AND ($author IS NULL OR toLower(d.author) CONTAINS toLower($author))
+          AND ($academic_supervisor IS NULL OR toLower(d.academic_supervisor) CONTAINS toLower($academic_supervisor))
+          AND ($min_year IS NULL OR d.year >= $min_year)
+          AND ($max_year IS NULL OR d.year <= $max_year)
+          AND ($min_words IS NULL OR d.words >= $min_words)
+          AND ($max_words IS NULL OR d.words <= $max_words)
+          AND ($min_date IS NULL OR d.load_date >= $min_date)
+          AND ($max_date IS NULL OR d.load_date <= $max_date)
+          AND ($chapters IS NULL OR ANY(chapter IN $chapters WHERE chapter IN chapters))
+        """
+        if order_by:
+            query += f"\nORDER BY d.{order_by}"
+
+        query += "\nRETURN d{.*, chapters: chapters} AS diploma"
+
+        parameters = {
+            "min_id": min_id, "max_id": max_id,
+            "name": name, "author": author, "academic_supervisor": academic_supervisor,
+            "min_year": min_year, "max_year": max_year,
+            "min_words": min_words, "max_words": max_words,
+            "min_date": min_date, "max_date": max_date,
+            "chapters": chapters
+        }
+        result = self.database.query(query, parameters)
+        return [record["diploma"] for record in result] if result else []
+
+    def search_chapters(self,
+                        min_id: int = None, max_id: int = None,
+                        min_id_diploma: int = None, max_id_diploma: int = None,
+                        name: str = None,
+                        min_words: int = None, max_words: int = None,
+                        min_symbols: int = None, max_symbols: int = None,
+                        min_water_content: float = None, max_water_content: float = None,
+                        words: list[str] = None,
+                        chapters: list[int] = None,
+                        order_by: str = None) -> list[Chapter]:
+        query = """
+        MATCH (c:Chapter)-[:CONTAINS*0..]->(c1:Chapter)
+        WITH c, COLLECT(ID(c1)) AS chapters
+        WHERE ($min_id IS NULL OR c.id >= $min_id)
+          AND ($max_id IS NULL OR c.id <= $max_id)
+          AND ($min_id_diploma IS NULL OR c.id_diploma >= $min_id_diploma)
+          AND ($max_id_diploma IS NULL OR c.id_diploma <= $max_id_diploma)
+          AND ($name IS NULL OR toLower(c.name) CONTAINS toLower($name))
+          AND ($min_words IS NULL OR c.words >= $min_words)
+          AND ($max_words IS NULL OR c.words <= $max_words)
+          AND ($min_symbols IS NULL OR c.symbols >= $min_symbols)
+          AND ($max_symbols IS NULL OR c.symbols <= $max_symbols)
+          AND ($min_water_content IS NULL OR c.water_content >= $min_water_content)
+          AND ($max_water_content IS NULL OR c.water_content <= $max_water_content)
+          AND ($words IS NULL OR ANY(word IN $words WHERE word IN c.commonly_used_words))
+          AND ($chapters IS NULL OR ANY(chapter IN $chapters WHERE chapter IN chapters))
+        """
+        if order_by:
+            query += f"\nORDER BY c.{order_by}"
+
+        query += "\nRETURN c{.*, chapters: chapters} AS chapter"
+
+        parameters = {
+            "min_id": min_id, "max_id": max_id,
+            "min_id_diploma": min_id_diploma, "max_id_diploma": max_id_diploma,
+            "name": name,
+            "min_words": min_words, "max_words": max_words,
+            "min_symbols": min_symbols, "max_symbols": max_symbols,
+            "min_water_content": min_water_content, "max_water_content": max_water_content,
+            "words": words,
+            "chapters": chapters
+        }
+        result = self.database.query(query, parameters)
+        return [record["chapter"] for record in result] if result else []
diff --git a/src/repository/testing_diploma_repo_methods.py b/src/repository/testing_diploma_repo_methods.py
new file mode 100644
index 0000000..915fed2
--- /dev/null
+++ b/src/repository/testing_diploma_repo_methods.py
@@ -0,0 +1,373 @@
+from diploma_repo import Neo4jDatabase, ServiceUnavailable
+from src.diploma_processing.data_types import *
+
+
+class DiplomaRepository:
+    def __init__(self, database: Neo4jDatabase):
+        self.database = database
+
+    # works
+    def save_diploma(self, diploma: Diploma) -> int | None:
+        query = """
+        CREATE (d:Diploma { 
+            name: $name, 
+            author: $author, 
+            academic_supervisor: $academic_supervisor, 
+            year: $year, 
+            words: $words,
+            load_date: $load_date
+        })
+        SET d.id = ID(d)
+        RETURN d.id
+        """
+        parameters = {
+            "name": diploma.name,
+            "author": diploma.author,
+            "academic_supervisor": diploma.academic_supervisor,
+            "year": diploma.year,
+            "words": diploma.words,
+            "load_date": diploma.load_date
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            diploma.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return diploma.id
+        return None
+
+    # works
+    def save_chapter(self, chapter: Chapter) -> int | None:
+        query = """
+        CREATE (c:Chapter {
+            id_diploma: $id_diploma, 
+            name: $name, 
+            water_content: $water_content, 
+            words: $words, 
+            symbols: $symbols, 
+            commonly_used_words: $commonly_used_words, 
+            commonly_used_words_amount: $commonly_used_words_amount
+        })
+        SET c.id = ID(c)
+        RETURN c.id
+        """
+        parameters = {
+            "id_diploma": chapter.id_diploma,
+            "name": chapter.name,
+            "water_content": chapter.water_content,
+            "words": chapter.words,
+            "symbols": chapter.symbols,
+            "commonly_used_words": chapter.commonly_used_words,
+            "commonly_used_words_amount": chapter.commonly_used_words_amount
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            chapter.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return chapter.id
+        return None
+
+    # works
+    def link_chapter_to_diploma(self, id_diploma: int) -> None:
+        query = """
+        MATCH (d:Diploma), (c:Chapter) 
+        WHERE d.id = $id_diploma AND c.id_diploma = $id_diploma
+        CREATE (d)-[:CONTAINS]->(c)
+        """
+        parameters = {"id_diploma": id_diploma}
+        self.database.query(query, parameters)
+
+    # works
+    def link_subchapters(self, parent_chapter_id: int, subchapter_ids: list[int]) -> None:
+        query = """
+        MATCH (c1:Chapter), (c2:Chapter) 
+        WHERE c1.id = $parent_chapter_id AND c2.id IN $subchapter_ids
+        CREATE (c1)-[:CONTAINS]->(c2)
+        """
+        parameters = {
+            "parent_chapter_id": parent_chapter_id,
+            "subchapter_ids": subchapter_ids
+        }
+        self.database.query(query, parameters)
+
+    # idk (no field 'shingles' in Diploma added yet)
+    def get_other_diplomas_shingles(self, id_diploma: int) -> list[tuple[int, list[int]]]:
+        query = """
+        MATCH (d:Diploma)
+        WHERE d.id <> $id_diploma
+        RETURN d.id, d.shingles
+        """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+        return [(int(record[0]), list(map(int, record[1]))) for record in result]
+
+    # works
+    def save_similarity(self, id_diploma: int, id_diploma_2: int, similarity: float) -> None:
+        query = """
+        MATCH (d1:Diploma), (d2:Diploma)
+        WHERE d1.id = $id_diploma AND d2.id = $id_diploma_2
+        CREATE (d1)-[:SIMILAR_TO {similarity: $similarity}]->(d2)
+        """
+        parameters = {
+            "id_diploma": id_diploma,
+            "id_diploma_2": id_diploma_2,
+            "similarity": similarity
+        }
+        self.database.query(query, parameters)
+
+    # works
+    def load_diploma_graph(self, id_diploma: int) -> tuple[list[int], list[list[int]]]:
+        query = """
+        MATCH p = (d:Diploma)-[r:CONTAINS*0..]->(x) 
+        WHERE d.id = $id_diploma
+        RETURN 
+            COLLECT(DISTINCT ID(x)) AS nodes,
+            [r IN COLLECT(DISTINCT LAST(r)) | [ID(startNode(r)), ID(endNode(r))]] AS rels
+        """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+        nodes, rels = result[0] if result else ([], [])
+        return nodes, rels
+
+    # works
+    def load_diploma_data(self, id_diploma: int) -> Diploma:
+        query = """
+        MATCH (d:Diploma)
+        WHERE d.id = $id_diploma
+        RETURN d
+        """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+        return result[0]["d"] if result else {}
+
+    # works
+    def load_chapters(self, chapter_ids: list[int]) -> list[Chapter]:
+        query = """
+        MATCH (c:Chapter)
+        WHERE c.id IN $chapter_ids
+        RETURN c
+        """
+        parameters = {"chapter_ids": chapter_ids}
+        result = self.database.query(query, parameters)
+        return [record["c"] for record in result] if result else []
+
+
+# Инициализируем соединение с базой данных Neo4j
+neo4j_db = Neo4jDatabase(uri="bolt://localhost:7687", user="neo4j", password="password")
+
+# Создаем репозиторий дипломов
+diploma_repository = DiplomaRepository(neo4j_db)
+
+# Пример диплома
+test_diploma1 = Diploma(
+    id=None,
+    name="ОЧЕНЬ ДЛИННОЕ НАЗВАНИЕ ДИПЛОМА 1",
+    author="Авторов А.А.",
+    academic_supervisor="Научруков А.А.",
+    year=2024,
+    words=24,
+    load_date=datetime.datetime.fromisoformat("2025-04-25T20:01:25.807687"),
+    chapters=[
+        Chapter(
+            id=0,
+            id_diploma=1,
+            name="ВВЕДЕНИЕ",
+            water_content=85,
+            words=2,
+            symbols=11,
+            commonly_used_words=["это", "введение"],
+            commonly_used_words_amount=[1, 1],
+            chapters=[]
+        ),
+        Chapter(
+            id=1,
+            id_diploma=1,
+            name="Структура",
+            water_content=60,
+            words=15,
+            symbols=66,
+            commonly_used_words=["это", "раздел", "пояснения"],
+            commonly_used_words_amount=[2, 1, 1],
+            chapters=[
+                Chapter(
+                    id=3,
+                    id_diploma=1,
+                    name="Раздел 1",
+                    water_content=70,
+                    words=5,
+                    symbols=16,
+                    commonly_used_words=["это", "раздел"],
+                    commonly_used_words_amount=[2, 1],
+                    chapters=[]
+                ),
+                Chapter(
+                    id=4,
+                    id_diploma=1,
+                    name="Раздел 2",
+                    water_content=50,
+                    words=10,
+                    symbols=50,
+                    commonly_used_words=["это", "пояснения"],
+                    commonly_used_words_amount=[2, 1],
+                    chapters=[]
+                )
+            ]
+        ),
+        Chapter(
+            id=2,
+            id_diploma=1,
+            name="ЗАКЛЮЧЕНИЕ",
+            water_content=80,
+            words=7,
+            symbols=60,
+            commonly_used_words=["наконец", "заключение"],
+            commonly_used_words_amount=[2, 1],
+            chapters=[]
+        )
+    ]
+)
+
+test_diploma2 = Diploma(
+    id=None,
+    name="ОЧЕНЬ ДЛИННОЕ НАЗВАНИЕ ДИПЛОМА 1",
+    author="Авторов А.А.",
+    academic_supervisor="Научруков А.А.",
+    year=2024,
+    words=24,
+    load_date=datetime.datetime.fromisoformat("2025-04-25T20:01:25.807687"),
+    chapters=[],
+)
+
+
+def testing():
+
+    # Получаем все узлы и связи
+    all_data = neo4j_db.query("MATCH (n)-[r]->(m) RETURN n, r, m")
+
+    # Получаем все узлы
+    all_nodes = neo4j_db.query("MATCH (n) RETURN n")
+
+    # Получаем все связи
+    all_relationships = neo4j_db.query("MATCH ()-[r]->() RETURN r")
+
+    print(all_data, all_nodes, sep='\n---------------\n')
+    print('\n\n\n', all_relationships, sep='\n\n')
+
+
+# 1. Сохраняем диплом в базу данных
+def test_save_diploma(test_diploma):
+    diploma_id = diploma_repository.save_diploma(test_diploma)
+    print(f"Диплом сохранен с ID: {diploma_id}")
+    return diploma_id
+
+
+# 2. Сохраняем главы диплома в базу данных
+def save_chapter_recursive(chapter: Chapter):
+    """
+    Сохраняет главу и её подразделы рекурсивно.
+    Использует внешнюю переменную test_diploma1.
+    """
+    chapter.id_diploma = test_diploma1.id  # <-- берем id диплома из внешней переменной
+    chapter_id = diploma_repository.save_chapter(chapter)
+    print(f"Глава '{chapter.name}' сохранена с ID: {chapter_id}")
+
+    # Если у главы есть подразделы, рекурсивно сохраняем их
+    if chapter.chapters:
+        for subchapter in chapter.chapters:
+            save_chapter_recursive(subchapter)
+
+
+def test_save_chapters():
+    """
+    Сохраняем все главы диплома, включая все вложенные подразделы.
+    """
+    for chapter in test_diploma1.chapters:
+        save_chapter_recursive(chapter)
+
+
+# 3. Связываем главы с дипломом
+def test_link_chapters_to_diploma():
+    diploma_repository.link_chapter_to_diploma(test_diploma1.id)
+
+
+# 4 Связывание глав
+def test_link_subchapters_recursive(chapter):
+    subchapter_ids = [subchapter.id for subchapter in chapter.chapters]
+    if subchapter_ids:
+        diploma_repository.link_subchapters(chapter.id, subchapter_ids)
+        print(f"Подглавы {subchapter_ids} успешно связаны с главой '{chapter.name}' (ID {chapter.id}).")
+        # Рекурсивно связываем подглавы
+        for subchapter in chapter.chapters:
+            test_link_subchapters_recursive(subchapter)
+    else:
+        print(f"У главы '{chapter.name}' нет подглав для связывания.")
+
+
+def test_link_subchapters():
+    # Начинаем рекурсивно
+    for chapt in test_diploma1.chapters:
+        test_link_subchapters_recursive(chapt)
+
+
+# 5 Извлечение шинглов
+# idk what to do yet
+
+# 6 Выставление параметра схожести
+def test_save_similarity():
+    similarity_value = 0.3  # допустим, какая-то вычисленная схожесть
+    diploma_repository.save_similarity(test_diploma1.id, test_diploma2.id, similarity_value)
+    print(f"Связь схожести между дипломами {test_diploma1.id} и {test_diploma2.id} успешно сохранена (похожесть {similarity_value}).")
+
+
+# 7 Извлечение диплома и его разделов (3 запроса)
+def test_load_diploma_graph(diploma):
+    if diploma.id is not None:
+        nodes, rels = diploma_repository.load_diploma_graph(diploma.id)
+        print(f"У диплома с ID {diploma.id} найдены узлы: {nodes}")
+        print(f"И связи: {rels}")
+    else:
+        print("У тестового диплома нет ID для загрузки графа.")
+
+
+def test_load_diploma_data(diploma):
+    if diploma.id is not None:
+        diploma_data = diploma_repository.load_diploma_data(diploma.id)
+        print(f"Данные диплома с ID {diploma.id}: {diploma_data}")
+    else:
+        print("У тестового диплома нет ID для загрузки данных.")
+
+
+def test_load_chapters(diploma):
+    chapter_ids = [chapter.id for chapter in diploma.chapters if chapter.id is not None]
+    if chapter_ids:
+        chapters_data = diploma_repository.load_chapters(chapter_ids)
+        print(f"Данные глав с ID {chapter_ids}:")
+        for chapter in chapters_data:
+            print(chapter)
+    else:
+        print("Нет доступных глав для загрузки.")
+
+
+neo4j_db.query("MATCH (n) DETACH DELETE n")
+testing()
+
+# 1
+id_dipl1 = test_save_diploma(test_diploma1)
+# 2
+test_save_chapters()
+# 3
+test_link_chapters_to_diploma()
+# 4
+test_link_subchapters()
+# 5
+# no field 'shingles' in Diploma
+# 6
+id_dipl2 = test_save_diploma(test_diploma2)
+test_save_similarity()
+# 7
+test_load_diploma_graph(test_diploma1)
+test_load_diploma_data(test_diploma1)
+test_load_chapters(test_diploma1)
+
+testing()
+print(test_diploma1, test_diploma2, sep='\n')
+
+# Закрываем соединение с базой данных
+neo4j_db.close()


Reviews:
user: login: Space27
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-27T08:37:37Z
user: login: Space27
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-27T08:38:29Z
user: login: Space27
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-27T08:39:08Z
user: login: Space27
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-27T08:39:28Z
user: login: Space27
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-27T08:41:47Z
user: login: Space27
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-27T08:43:13Z
user: login: Space27
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-27T08:44:48Z
user: login: Space27
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-27T08:46:23Z
user: login: Space27
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-27T08:46:59Z
user: login: Space27
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-27T08:47:18Z
user: login: Space27
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-27T08:49:54Z
user: login: Space27
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-27T08:50:13Z
user: login: Space27
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-27T08:55:02Z
user: login: volk6022
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-27T09:14:20Z
user: login: vRedniy-LumbeRt
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-27T11:11:20Z
user: login: Space27
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-27T11:26:00Z
user: login: Space27
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-27T11:27:57Z
user: login: vRedniy-LumbeRt
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-27T12:52:15Z
user: login: vRedniy-LumbeRt
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-27T12:52:58Z
user: login: Space27
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-27T13:01:38Z
user: login: Space27
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-27T13:01:48Z
user: login: Space27
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-27T13:03:08Z
user: login: Space27
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-27T19:22:09Z

Review comments:
diff_hunk: @@ -0,0 +1,197 @@
+from neo4j import GraphDatabase
+from neo4j.exceptions import ServiceUnavailable
+from src.diploma_processing.data_types import Chapter, Diploma
+
+
+class Neo4jDatabase:
+    def __init__(self, uri, user, password):
+        """
+        Инициализация соединения с базой данных Neo4j.
+        """
+        self._driver = None
+        try:
+            self._driver = GraphDatabase.driver(uri, auth=(user, password))
+            # >>> ПРОВЕРКА ДОСТУПНОСТИ БАЗЫ <<<
+            with self._driver.session() as session:
+                session.run("RETURN 1")  # минимальный запрос
+            print("Подключение к базе данных успешно установлено.")
+        except ServiceUnavailable as e:
+            print(f"Не удалось подключиться к базе данных: {e}")
+            raise
+        except Exception as e:
+            print(f"Ошибка подключения к базе данных: {e}")
+            raise
+
+    def close(self):
+        """
+        Закрытие соединения с базой данных.
+        """
+        if self._driver:
+            try:
+                self._driver.close()
+                print("Соединение с базой данных успешно закрыто.")
+            except Exception as e:
+                print(f"Ошибка при закрытии соединения: {e}")
+
+    def query(self, query, parameters=None):
+        """
+        Выполнение Cypher-запроса.
+        :param query: Строка с Cypher-запросом.
+        :param parameters: Параметры для запроса (опционально).
+        :return: Результат выполнения запроса.
+        """
+        assert self._driver is not None, "Driver not initialized!"
+        session = None
+        response = None
+
+        try:
+            session = self._driver.session()
+            if parameters is None:
+                response = list(session.run(query))
+            else:
+                response = list(session.run(query, parameters))  # <-- сюда передать parameters
+            print("Запрос успешно выполнен.")
+        except Exception as e:
+            print(f"Ошибка при выполнении запроса: {e}")
+        finally:
+            if session is not None:
+                session.close()
+
+        return response
+
+
+class DiplomaRepository:
+    def __init__(self, database: Neo4jDatabase):
+        self.database = database
+
+    def save_diploma(self, diploma: Diploma) -> int | None:
+        query = """
+                CREATE (d:Diploma { 
+                    name: $name, 
+                    author: $author, 
+                    academic_supervisor: $academic_supervisor, 
+                    year: $year, 
+                    disclosure_persentage: 0, 
+                    shingles: ''
+                })
+                SET d.id = ID(d)
+                RETURN d.id
+                """
+        parameters = {
+            "name": diploma.name,
+            "author": diploma.author,
+            "academic_supervisor": diploma.academic_supervisor,
+            "year": diploma.year
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            diploma.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return diploma.id
+        return None
+
+    def save_chapter(self, chapter: Chapter) -> int | None:
+        query = """
+                CREATE (c:Chapter {
+                    id_diploma: $id_diploma, 
+                    name: $name, 
+                    water_content: $water_content, 
+                    words: $words, 
+                    symbols: $symbols, 
+                    commonly_used_words: $commonly_used_words, 
+                    commonly_used_words_amount: $commonly_used_words_amount
+                })
+                SET c.id = ID(c)
+                RETURN ID(c)
+                """
+        parameters = {
+            "id_diploma": chapter.id_diploma,
+            "name": chapter.name,
+            "water_content": chapter.water_content,
+            "words": chapter.words,
+            "symbols": chapter.symbols,
+            "commonly_used_words": chapter.commonly_used_words,
+            "commonly_used_words_amount": chapter.commonly_used_words_amount
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            chapter.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return chapter.id
+        return None
+
+    def link_chapter_to_diploma(self, id_diploma: int) -> None:
+        query = """
+                MATCH (d:Diploma), (c:Chapter) 
+                WHERE ID(d) = $id_diploma AND c.id_diploma = $id_diploma
path: src/repository/diploma_repo.py
user: login: Space27
body: А зачем ID(d), если у него есть поле id?
created_at: 2025-04-27T08:37:37Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,197 @@
+from neo4j import GraphDatabase
+from neo4j.exceptions import ServiceUnavailable
+from src.diploma_processing.data_types import Chapter, Diploma
+
+
+class Neo4jDatabase:
+    def __init__(self, uri, user, password):
+        """
+        Инициализация соединения с базой данных Neo4j.
+        """
+        self._driver = None
+        try:
+            self._driver = GraphDatabase.driver(uri, auth=(user, password))
+            # >>> ПРОВЕРКА ДОСТУПНОСТИ БАЗЫ <<<
+            with self._driver.session() as session:
+                session.run("RETURN 1")  # минимальный запрос
+            print("Подключение к базе данных успешно установлено.")
+        except ServiceUnavailable as e:
+            print(f"Не удалось подключиться к базе данных: {e}")
+            raise
+        except Exception as e:
+            print(f"Ошибка подключения к базе данных: {e}")
+            raise
+
+    def close(self):
+        """
+        Закрытие соединения с базой данных.
+        """
+        if self._driver:
+            try:
+                self._driver.close()
+                print("Соединение с базой данных успешно закрыто.")
+            except Exception as e:
+                print(f"Ошибка при закрытии соединения: {e}")
+
+    def query(self, query, parameters=None):
+        """
+        Выполнение Cypher-запроса.
+        :param query: Строка с Cypher-запросом.
+        :param parameters: Параметры для запроса (опционально).
+        :return: Результат выполнения запроса.
+        """
+        assert self._driver is not None, "Driver not initialized!"
+        session = None
+        response = None
+
+        try:
+            session = self._driver.session()
+            if parameters is None:
+                response = list(session.run(query))
+            else:
+                response = list(session.run(query, parameters))  # <-- сюда передать parameters
+            print("Запрос успешно выполнен.")
+        except Exception as e:
+            print(f"Ошибка при выполнении запроса: {e}")
+        finally:
+            if session is not None:
+                session.close()
+
+        return response
+
+
+class DiplomaRepository:
+    def __init__(self, database: Neo4jDatabase):
+        self.database = database
+
+    def save_diploma(self, diploma: Diploma) -> int | None:
+        query = """
+                CREATE (d:Diploma { 
+                    name: $name, 
+                    author: $author, 
+                    academic_supervisor: $academic_supervisor, 
+                    year: $year, 
+                    disclosure_persentage: 0, 
+                    shingles: ''
+                })
+                SET d.id = ID(d)
+                RETURN d.id
+                """
+        parameters = {
+            "name": diploma.name,
+            "author": diploma.author,
+            "academic_supervisor": diploma.academic_supervisor,
+            "year": diploma.year
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            diploma.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return diploma.id
+        return None
+
+    def save_chapter(self, chapter: Chapter) -> int | None:
+        query = """
+                CREATE (c:Chapter {
+                    id_diploma: $id_diploma, 
+                    name: $name, 
+                    water_content: $water_content, 
+                    words: $words, 
+                    symbols: $symbols, 
+                    commonly_used_words: $commonly_used_words, 
+                    commonly_used_words_amount: $commonly_used_words_amount
+                })
+                SET c.id = ID(c)
+                RETURN ID(c)
+                """
+        parameters = {
+            "id_diploma": chapter.id_diploma,
+            "name": chapter.name,
+            "water_content": chapter.water_content,
+            "words": chapter.words,
+            "symbols": chapter.symbols,
+            "commonly_used_words": chapter.commonly_used_words,
+            "commonly_used_words_amount": chapter.commonly_used_words_amount
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            chapter.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return chapter.id
+        return None
+
+    def link_chapter_to_diploma(self, id_diploma: int) -> None:
+        query = """
+                MATCH (d:Diploma), (c:Chapter) 
+                WHERE ID(d) = $id_diploma AND c.id_diploma = $id_diploma
+                CREATE (d)-[:CONTAINS]->(c)
+                """
+        parameters = {"id_diploma": id_diploma}
+        self.database.query(query, parameters)
+
+    def link_subchapters(self, parent_chapter_id: int, subchapter_ids: list[int]) -> None:
+        query = """
+                MATCH (c1:Chapter), (c2:Chapter) 
+                WHERE ID(c1) = $parent_chapter_id AND ID(c2) IN $subchapter_ids
path: src/repository/diploma_repo.py
user: login: Space27
body: Тут тоже лучше поле id использовать
created_at: 2025-04-27T08:38:29Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,197 @@
+from neo4j import GraphDatabase
+from neo4j.exceptions import ServiceUnavailable
+from src.diploma_processing.data_types import Chapter, Diploma
+
+
+class Neo4jDatabase:
+    def __init__(self, uri, user, password):
+        """
+        Инициализация соединения с базой данных Neo4j.
+        """
+        self._driver = None
+        try:
+            self._driver = GraphDatabase.driver(uri, auth=(user, password))
+            # >>> ПРОВЕРКА ДОСТУПНОСТИ БАЗЫ <<<
+            with self._driver.session() as session:
+                session.run("RETURN 1")  # минимальный запрос
+            print("Подключение к базе данных успешно установлено.")
+        except ServiceUnavailable as e:
+            print(f"Не удалось подключиться к базе данных: {e}")
+            raise
+        except Exception as e:
+            print(f"Ошибка подключения к базе данных: {e}")
+            raise
+
+    def close(self):
+        """
+        Закрытие соединения с базой данных.
+        """
+        if self._driver:
+            try:
+                self._driver.close()
+                print("Соединение с базой данных успешно закрыто.")
+            except Exception as e:
+                print(f"Ошибка при закрытии соединения: {e}")
+
+    def query(self, query, parameters=None):
+        """
+        Выполнение Cypher-запроса.
+        :param query: Строка с Cypher-запросом.
+        :param parameters: Параметры для запроса (опционально).
+        :return: Результат выполнения запроса.
+        """
+        assert self._driver is not None, "Driver not initialized!"
+        session = None
+        response = None
+
+        try:
+            session = self._driver.session()
+            if parameters is None:
+                response = list(session.run(query))
+            else:
+                response = list(session.run(query, parameters))  # <-- сюда передать parameters
+            print("Запрос успешно выполнен.")
+        except Exception as e:
+            print(f"Ошибка при выполнении запроса: {e}")
+        finally:
+            if session is not None:
+                session.close()
+
+        return response
+
+
+class DiplomaRepository:
+    def __init__(self, database: Neo4jDatabase):
+        self.database = database
+
+    def save_diploma(self, diploma: Diploma) -> int | None:
+        query = """
+                CREATE (d:Diploma { 
+                    name: $name, 
+                    author: $author, 
+                    academic_supervisor: $academic_supervisor, 
+                    year: $year, 
+                    disclosure_persentage: 0, 
+                    shingles: ''
+                })
+                SET d.id = ID(d)
+                RETURN d.id
+                """
+        parameters = {
+            "name": diploma.name,
+            "author": diploma.author,
+            "academic_supervisor": diploma.academic_supervisor,
+            "year": diploma.year
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            diploma.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return diploma.id
+        return None
+
+    def save_chapter(self, chapter: Chapter) -> int | None:
+        query = """
+                CREATE (c:Chapter {
+                    id_diploma: $id_diploma, 
+                    name: $name, 
+                    water_content: $water_content, 
+                    words: $words, 
+                    symbols: $symbols, 
+                    commonly_used_words: $commonly_used_words, 
+                    commonly_used_words_amount: $commonly_used_words_amount
+                })
+                SET c.id = ID(c)
+                RETURN ID(c)
+                """
+        parameters = {
+            "id_diploma": chapter.id_diploma,
+            "name": chapter.name,
+            "water_content": chapter.water_content,
+            "words": chapter.words,
+            "symbols": chapter.symbols,
+            "commonly_used_words": chapter.commonly_used_words,
+            "commonly_used_words_amount": chapter.commonly_used_words_amount
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            chapter.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return chapter.id
+        return None
+
+    def link_chapter_to_diploma(self, id_diploma: int) -> None:
+        query = """
+                MATCH (d:Diploma), (c:Chapter) 
+                WHERE ID(d) = $id_diploma AND c.id_diploma = $id_diploma
+                CREATE (d)-[:CONTAINS]->(c)
+                """
+        parameters = {"id_diploma": id_diploma}
+        self.database.query(query, parameters)
+
+    def link_subchapters(self, parent_chapter_id: int, subchapter_ids: list[int]) -> None:
+        query = """
+                MATCH (c1:Chapter), (c2:Chapter) 
+                WHERE ID(c1) = $parent_chapter_id AND ID(c2) IN $subchapter_ids
+                CREATE (c1)-[:CONTAINS]->(c2)
+                """
+        parameters = {
+            "parent_chapter_id": parent_chapter_id,
+            "subchapter_ids": subchapter_ids
+        }
+        self.database.query(query, parameters)
+
+    def get_other_diplomas_shingles(self, id_diploma: int) -> list[tuple[int, str]]:
+        query = """
+                MATCH (d:Diploma)
+                WHERE ID(d) <> $id_diploma
+                RETURN ID(d), d.shingles
+                """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+        return [(record[0], record[1]) for record in result]
+
+    def save_similarity(self, id_diploma: int, id_diploma_2: int, similarity: float) -> None:
+        query = """
+                MATCH (d1:Diploma), (d2:Diploma)
+                WHERE ID(d1) = $id_diploma AND ID(d2) = $id_diploma_2
+                CREATE (d1)-[:SIMILAR_TO {similarity: $similarity}]->(d2)
+                """
+        parameters = {
+            "id_diploma": id_diploma,
+            "id_diploma_2": id_diploma_2,
+            "similarity": similarity
+        }
+        self.database.query(query, parameters)
+
+    def load_diploma_graph(self, id_diploma: int) -> tuple[list[int], list[list[int]]]:
+        query = """
+                MATCH p = (d:Diploma)-[r:CONTAINS*0..]->(x) 
+                WHERE ID(d) = $id_diploma
+                RETURN 
+                    COLLECT(DISTINCT ID(x)) AS nodes,
+                    [r IN COLLECT(DISTINCT LAST(r)) | [ID(startNode(r)), ID(endNode(r))]] AS rels
path: src/repository/diploma_repo.py
user: login: Space27
body: Тут тоже не использовать ID
created_at: 2025-04-27T08:39:08Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,197 @@
+from neo4j import GraphDatabase
+from neo4j.exceptions import ServiceUnavailable
+from src.diploma_processing.data_types import Chapter, Diploma
+
+
+class Neo4jDatabase:
+    def __init__(self, uri, user, password):
+        """
+        Инициализация соединения с базой данных Neo4j.
+        """
+        self._driver = None
+        try:
+            self._driver = GraphDatabase.driver(uri, auth=(user, password))
+            # >>> ПРОВЕРКА ДОСТУПНОСТИ БАЗЫ <<<
+            with self._driver.session() as session:
+                session.run("RETURN 1")  # минимальный запрос
+            print("Подключение к базе данных успешно установлено.")
+        except ServiceUnavailable as e:
+            print(f"Не удалось подключиться к базе данных: {e}")
+            raise
+        except Exception as e:
+            print(f"Ошибка подключения к базе данных: {e}")
+            raise
+
+    def close(self):
+        """
+        Закрытие соединения с базой данных.
+        """
+        if self._driver:
+            try:
+                self._driver.close()
+                print("Соединение с базой данных успешно закрыто.")
+            except Exception as e:
+                print(f"Ошибка при закрытии соединения: {e}")
+
+    def query(self, query, parameters=None):
+        """
+        Выполнение Cypher-запроса.
+        :param query: Строка с Cypher-запросом.
+        :param parameters: Параметры для запроса (опционально).
+        :return: Результат выполнения запроса.
+        """
+        assert self._driver is not None, "Driver not initialized!"
+        session = None
+        response = None
+
+        try:
+            session = self._driver.session()
+            if parameters is None:
+                response = list(session.run(query))
+            else:
+                response = list(session.run(query, parameters))  # <-- сюда передать parameters
+            print("Запрос успешно выполнен.")
+        except Exception as e:
+            print(f"Ошибка при выполнении запроса: {e}")
+        finally:
+            if session is not None:
+                session.close()
+
+        return response
+
+
+class DiplomaRepository:
+    def __init__(self, database: Neo4jDatabase):
+        self.database = database
+
+    def save_diploma(self, diploma: Diploma) -> int | None:
+        query = """
+                CREATE (d:Diploma { 
+                    name: $name, 
+                    author: $author, 
+                    academic_supervisor: $academic_supervisor, 
+                    year: $year, 
+                    disclosure_persentage: 0, 
+                    shingles: ''
+                })
+                SET d.id = ID(d)
+                RETURN d.id
+                """
+        parameters = {
+            "name": diploma.name,
+            "author": diploma.author,
+            "academic_supervisor": diploma.academic_supervisor,
+            "year": diploma.year
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            diploma.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return diploma.id
+        return None
+
+    def save_chapter(self, chapter: Chapter) -> int | None:
+        query = """
+                CREATE (c:Chapter {
+                    id_diploma: $id_diploma, 
+                    name: $name, 
+                    water_content: $water_content, 
+                    words: $words, 
+                    symbols: $symbols, 
+                    commonly_used_words: $commonly_used_words, 
+                    commonly_used_words_amount: $commonly_used_words_amount
+                })
+                SET c.id = ID(c)
+                RETURN ID(c)
+                """
+        parameters = {
+            "id_diploma": chapter.id_diploma,
+            "name": chapter.name,
+            "water_content": chapter.water_content,
+            "words": chapter.words,
+            "symbols": chapter.symbols,
+            "commonly_used_words": chapter.commonly_used_words,
+            "commonly_used_words_amount": chapter.commonly_used_words_amount
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            chapter.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return chapter.id
+        return None
+
+    def link_chapter_to_diploma(self, id_diploma: int) -> None:
+        query = """
+                MATCH (d:Diploma), (c:Chapter) 
+                WHERE ID(d) = $id_diploma AND c.id_diploma = $id_diploma
+                CREATE (d)-[:CONTAINS]->(c)
+                """
+        parameters = {"id_diploma": id_diploma}
+        self.database.query(query, parameters)
+
+    def link_subchapters(self, parent_chapter_id: int, subchapter_ids: list[int]) -> None:
+        query = """
+                MATCH (c1:Chapter), (c2:Chapter) 
+                WHERE ID(c1) = $parent_chapter_id AND ID(c2) IN $subchapter_ids
+                CREATE (c1)-[:CONTAINS]->(c2)
+                """
+        parameters = {
+            "parent_chapter_id": parent_chapter_id,
+            "subchapter_ids": subchapter_ids
+        }
+        self.database.query(query, parameters)
+
+    def get_other_diplomas_shingles(self, id_diploma: int) -> list[tuple[int, str]]:
+        query = """
+                MATCH (d:Diploma)
+                WHERE ID(d) <> $id_diploma
+                RETURN ID(d), d.shingles
+                """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+        return [(record[0], record[1]) for record in result]
+
+    def save_similarity(self, id_diploma: int, id_diploma_2: int, similarity: float) -> None:
+        query = """
+                MATCH (d1:Diploma), (d2:Diploma)
+                WHERE ID(d1) = $id_diploma AND ID(d2) = $id_diploma_2
+                CREATE (d1)-[:SIMILAR_TO {similarity: $similarity}]->(d2)
+                """
+        parameters = {
+            "id_diploma": id_diploma,
+            "id_diploma_2": id_diploma_2,
+            "similarity": similarity
+        }
+        self.database.query(query, parameters)
+
+    def load_diploma_graph(self, id_diploma: int) -> tuple[list[int], list[list[int]]]:
+        query = """
+                MATCH p = (d:Diploma)-[r:CONTAINS*0..]->(x) 
+                WHERE ID(d) = $id_diploma
+                RETURN 
+                    COLLECT(DISTINCT ID(x)) AS nodes,
+                    [r IN COLLECT(DISTINCT LAST(r)) | [ID(startNode(r)), ID(endNode(r))]] AS rels
+                """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+        nodes, rels = result[0] if result else ([], [])
+        return nodes, rels
+
+    def load_diploma_data(self, id_diploma: int) -> dict:
+        query = """
+                MATCH (d:Diploma)
+                WHERE ID(d) = $id_diploma
path: src/repository/diploma_repo.py
user: login: Space27
body: то же самое
created_at: 2025-04-27T08:39:27Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,455 @@
+from neo4j import GraphDatabase
path: src/repository/testing_diploma_repo_methods.py
user: login: Space27
body: Я вообще этот файл не понял
Почему ты отдельно подтягиваешь датаклассы и их примеры?
Да и в принципе можно обойтись без этого класса (просто отладить локально и всё), но если есть, то пусть будет
created_at: 2025-04-27T08:41:47Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,197 @@
+from neo4j import GraphDatabase
+from neo4j.exceptions import ServiceUnavailable
+from src.diploma_processing.data_types import Chapter, Diploma
+
+
+class Neo4jDatabase:
+    def __init__(self, uri, user, password):
+        """
+        Инициализация соединения с базой данных Neo4j.
+        """
+        self._driver = None
+        try:
+            self._driver = GraphDatabase.driver(uri, auth=(user, password))
+            # >>> ПРОВЕРКА ДОСТУПНОСТИ БАЗЫ <<<
+            with self._driver.session() as session:
+                session.run("RETURN 1")  # минимальный запрос
+            print("Подключение к базе данных успешно установлено.")
+        except ServiceUnavailable as e:
+            print(f"Не удалось подключиться к базе данных: {e}")
+            raise
+        except Exception as e:
+            print(f"Ошибка подключения к базе данных: {e}")
+            raise
+
+    def close(self):
+        """
+        Закрытие соединения с базой данных.
+        """
+        if self._driver:
+            try:
+                self._driver.close()
+                print("Соединение с базой данных успешно закрыто.")
+            except Exception as e:
+                print(f"Ошибка при закрытии соединения: {e}")
+
+    def query(self, query, parameters=None):
+        """
+        Выполнение Cypher-запроса.
+        :param query: Строка с Cypher-запросом.
+        :param parameters: Параметры для запроса (опционально).
+        :return: Результат выполнения запроса.
+        """
+        assert self._driver is not None, "Driver not initialized!"
+        session = None
+        response = None
+
+        try:
+            session = self._driver.session()
+            if parameters is None:
+                response = list(session.run(query))
+            else:
+                response = list(session.run(query, parameters))  # <-- сюда передать parameters
+            print("Запрос успешно выполнен.")
+        except Exception as e:
+            print(f"Ошибка при выполнении запроса: {e}")
+        finally:
+            if session is not None:
+                session.close()
+
+        return response
+
+
+class DiplomaRepository:
+    def __init__(self, database: Neo4jDatabase):
+        self.database = database
+
+    def save_diploma(self, diploma: Diploma) -> int | None:
+        query = """
+                CREATE (d:Diploma { 
+                    name: $name, 
+                    author: $author, 
+                    academic_supervisor: $academic_supervisor, 
+                    year: $year, 
+                    disclosure_persentage: 0, 
+                    shingles: ''
path: src/repository/diploma_repo.py
user: login: Space27
body: Эти поля убрать и добавить words, load_date
created_at: 2025-04-27T08:43:12Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,197 @@
+from neo4j import GraphDatabase
+from neo4j.exceptions import ServiceUnavailable
+from src.diploma_processing.data_types import Chapter, Diploma
+
+
+class Neo4jDatabase:
+    def __init__(self, uri, user, password):
+        """
+        Инициализация соединения с базой данных Neo4j.
+        """
+        self._driver = None
+        try:
+            self._driver = GraphDatabase.driver(uri, auth=(user, password))
+            # >>> ПРОВЕРКА ДОСТУПНОСТИ БАЗЫ <<<
+            with self._driver.session() as session:
+                session.run("RETURN 1")  # минимальный запрос
+            print("Подключение к базе данных успешно установлено.")
+        except ServiceUnavailable as e:
+            print(f"Не удалось подключиться к базе данных: {e}")
+            raise
+        except Exception as e:
+            print(f"Ошибка подключения к базе данных: {e}")
+            raise
+
+    def close(self):
+        """
+        Закрытие соединения с базой данных.
+        """
+        if self._driver:
+            try:
+                self._driver.close()
+                print("Соединение с базой данных успешно закрыто.")
+            except Exception as e:
+                print(f"Ошибка при закрытии соединения: {e}")
+
+    def query(self, query, parameters=None):
+        """
+        Выполнение Cypher-запроса.
+        :param query: Строка с Cypher-запросом.
+        :param parameters: Параметры для запроса (опционально).
+        :return: Результат выполнения запроса.
+        """
+        assert self._driver is not None, "Driver not initialized!"
+        session = None
+        response = None
+
+        try:
+            session = self._driver.session()
+            if parameters is None:
+                response = list(session.run(query))
+            else:
+                response = list(session.run(query, parameters))  # <-- сюда передать parameters
+            print("Запрос успешно выполнен.")
+        except Exception as e:
+            print(f"Ошибка при выполнении запроса: {e}")
+        finally:
+            if session is not None:
+                session.close()
+
+        return response
+
+
+class DiplomaRepository:
+    def __init__(self, database: Neo4jDatabase):
+        self.database = database
+
+    def save_diploma(self, diploma: Diploma) -> int | None:
+        query = """
+                CREATE (d:Diploma { 
+                    name: $name, 
+                    author: $author, 
+                    academic_supervisor: $academic_supervisor, 
+                    year: $year, 
+                    disclosure_persentage: 0, 
+                    shingles: ''
+                })
+                SET d.id = ID(d)
+                RETURN d.id
+                """
+        parameters = {
+            "name": diploma.name,
+            "author": diploma.author,
+            "academic_supervisor": diploma.academic_supervisor,
+            "year": diploma.year
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            diploma.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return diploma.id
+        return None
+
+    def save_chapter(self, chapter: Chapter) -> int | None:
+        query = """
+                CREATE (c:Chapter {
+                    id_diploma: $id_diploma, 
+                    name: $name, 
+                    water_content: $water_content, 
+                    words: $words, 
+                    symbols: $symbols, 
+                    commonly_used_words: $commonly_used_words, 
+                    commonly_used_words_amount: $commonly_used_words_amount
+                })
+                SET c.id = ID(c)
+                RETURN ID(c)
path: src/repository/diploma_repo.py
user: login: Space27
body: c.id лучше
created_at: 2025-04-27T08:44:48Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,197 @@
+from neo4j import GraphDatabase
+from neo4j.exceptions import ServiceUnavailable
+from src.diploma_processing.data_types import Chapter, Diploma
+
+
+class Neo4jDatabase:
+    def __init__(self, uri, user, password):
+        """
+        Инициализация соединения с базой данных Neo4j.
+        """
+        self._driver = None
+        try:
+            self._driver = GraphDatabase.driver(uri, auth=(user, password))
+            # >>> ПРОВЕРКА ДОСТУПНОСТИ БАЗЫ <<<
+            with self._driver.session() as session:
+                session.run("RETURN 1")  # минимальный запрос
+            print("Подключение к базе данных успешно установлено.")
+        except ServiceUnavailable as e:
+            print(f"Не удалось подключиться к базе данных: {e}")
+            raise
+        except Exception as e:
+            print(f"Ошибка подключения к базе данных: {e}")
+            raise
+
+    def close(self):
+        """
+        Закрытие соединения с базой данных.
+        """
+        if self._driver:
+            try:
+                self._driver.close()
+                print("Соединение с базой данных успешно закрыто.")
+            except Exception as e:
+                print(f"Ошибка при закрытии соединения: {e}")
+
+    def query(self, query, parameters=None):
+        """
+        Выполнение Cypher-запроса.
+        :param query: Строка с Cypher-запросом.
+        :param parameters: Параметры для запроса (опционально).
+        :return: Результат выполнения запроса.
+        """
+        assert self._driver is not None, "Driver not initialized!"
+        session = None
+        response = None
+
+        try:
+            session = self._driver.session()
+            if parameters is None:
+                response = list(session.run(query))
+            else:
+                response = list(session.run(query, parameters))  # <-- сюда передать parameters
+            print("Запрос успешно выполнен.")
+        except Exception as e:
+            print(f"Ошибка при выполнении запроса: {e}")
+        finally:
+            if session is not None:
+                session.close()
+
+        return response
+
+
+class DiplomaRepository:
+    def __init__(self, database: Neo4jDatabase):
+        self.database = database
+
+    def save_diploma(self, diploma: Diploma) -> int | None:
+        query = """
+                CREATE (d:Diploma { 
+                    name: $name, 
+                    author: $author, 
+                    academic_supervisor: $academic_supervisor, 
+                    year: $year, 
+                    disclosure_persentage: 0, 
+                    shingles: ''
+                })
+                SET d.id = ID(d)
+                RETURN d.id
+                """
+        parameters = {
+            "name": diploma.name,
+            "author": diploma.author,
+            "academic_supervisor": diploma.academic_supervisor,
+            "year": diploma.year
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            diploma.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return diploma.id
+        return None
+
+    def save_chapter(self, chapter: Chapter) -> int | None:
+        query = """
+                CREATE (c:Chapter {
+                    id_diploma: $id_diploma, 
+                    name: $name, 
+                    water_content: $water_content, 
+                    words: $words, 
+                    symbols: $symbols, 
+                    commonly_used_words: $commonly_used_words, 
+                    commonly_used_words_amount: $commonly_used_words_amount
+                })
+                SET c.id = ID(c)
+                RETURN ID(c)
+                """
+        parameters = {
+            "id_diploma": chapter.id_diploma,
+            "name": chapter.name,
+            "water_content": chapter.water_content,
+            "words": chapter.words,
+            "symbols": chapter.symbols,
+            "commonly_used_words": chapter.commonly_used_words,
+            "commonly_used_words_amount": chapter.commonly_used_words_amount
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            chapter.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return chapter.id
+        return None
+
+    def link_chapter_to_diploma(self, id_diploma: int) -> None:
+        query = """
+                MATCH (d:Diploma), (c:Chapter) 
+                WHERE ID(d) = $id_diploma AND c.id_diploma = $id_diploma
+                CREATE (d)-[:CONTAINS]->(c)
+                """
+        parameters = {"id_diploma": id_diploma}
+        self.database.query(query, parameters)
+
+    def link_subchapters(self, parent_chapter_id: int, subchapter_ids: list[int]) -> None:
+        query = """
+                MATCH (c1:Chapter), (c2:Chapter) 
+                WHERE ID(c1) = $parent_chapter_id AND ID(c2) IN $subchapter_ids
+                CREATE (c1)-[:CONTAINS]->(c2)
+                """
+        parameters = {
+            "parent_chapter_id": parent_chapter_id,
+            "subchapter_ids": subchapter_ids
+        }
+        self.database.query(query, parameters)
+
+    def get_other_diplomas_shingles(self, id_diploma: int) -> list[tuple[int, str]]:
+        query = """
+                MATCH (d:Diploma)
+                WHERE ID(d) <> $id_diploma
+                RETURN ID(d), d.shingles
path: src/repository/diploma_repo.py
user: login: Space27
body: Тоже лучше d.id
created_at: 2025-04-27T08:46:23Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,197 @@
+from neo4j import GraphDatabase
+from neo4j.exceptions import ServiceUnavailable
+from src.diploma_processing.data_types import Chapter, Diploma
+
+
+class Neo4jDatabase:
+    def __init__(self, uri, user, password):
+        """
+        Инициализация соединения с базой данных Neo4j.
+        """
+        self._driver = None
+        try:
+            self._driver = GraphDatabase.driver(uri, auth=(user, password))
+            # >>> ПРОВЕРКА ДОСТУПНОСТИ БАЗЫ <<<
+            with self._driver.session() as session:
+                session.run("RETURN 1")  # минимальный запрос
+            print("Подключение к базе данных успешно установлено.")
+        except ServiceUnavailable as e:
+            print(f"Не удалось подключиться к базе данных: {e}")
+            raise
+        except Exception as e:
+            print(f"Ошибка подключения к базе данных: {e}")
+            raise
+
+    def close(self):
+        """
+        Закрытие соединения с базой данных.
+        """
+        if self._driver:
+            try:
+                self._driver.close()
+                print("Соединение с базой данных успешно закрыто.")
+            except Exception as e:
+                print(f"Ошибка при закрытии соединения: {e}")
+
+    def query(self, query, parameters=None):
+        """
+        Выполнение Cypher-запроса.
+        :param query: Строка с Cypher-запросом.
+        :param parameters: Параметры для запроса (опционально).
+        :return: Результат выполнения запроса.
+        """
+        assert self._driver is not None, "Driver not initialized!"
+        session = None
+        response = None
+
+        try:
+            session = self._driver.session()
+            if parameters is None:
+                response = list(session.run(query))
+            else:
+                response = list(session.run(query, parameters))  # <-- сюда передать parameters
+            print("Запрос успешно выполнен.")
+        except Exception as e:
+            print(f"Ошибка при выполнении запроса: {e}")
+        finally:
+            if session is not None:
+                session.close()
+
+        return response
+
+
+class DiplomaRepository:
+    def __init__(self, database: Neo4jDatabase):
+        self.database = database
+
+    def save_diploma(self, diploma: Diploma) -> int | None:
+        query = """
+                CREATE (d:Diploma { 
+                    name: $name, 
+                    author: $author, 
+                    academic_supervisor: $academic_supervisor, 
+                    year: $year, 
+                    disclosure_persentage: 0, 
+                    shingles: ''
+                })
+                SET d.id = ID(d)
+                RETURN d.id
+                """
+        parameters = {
+            "name": diploma.name,
+            "author": diploma.author,
+            "academic_supervisor": diploma.academic_supervisor,
+            "year": diploma.year
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            diploma.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return diploma.id
+        return None
+
+    def save_chapter(self, chapter: Chapter) -> int | None:
+        query = """
+                CREATE (c:Chapter {
+                    id_diploma: $id_diploma, 
+                    name: $name, 
+                    water_content: $water_content, 
+                    words: $words, 
+                    symbols: $symbols, 
+                    commonly_used_words: $commonly_used_words, 
+                    commonly_used_words_amount: $commonly_used_words_amount
+                })
+                SET c.id = ID(c)
+                RETURN ID(c)
+                """
+        parameters = {
+            "id_diploma": chapter.id_diploma,
+            "name": chapter.name,
+            "water_content": chapter.water_content,
+            "words": chapter.words,
+            "symbols": chapter.symbols,
+            "commonly_used_words": chapter.commonly_used_words,
+            "commonly_used_words_amount": chapter.commonly_used_words_amount
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            chapter.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return chapter.id
+        return None
+
+    def link_chapter_to_diploma(self, id_diploma: int) -> None:
+        query = """
+                MATCH (d:Diploma), (c:Chapter) 
+                WHERE ID(d) = $id_diploma AND c.id_diploma = $id_diploma
+                CREATE (d)-[:CONTAINS]->(c)
+                """
+        parameters = {"id_diploma": id_diploma}
+        self.database.query(query, parameters)
+
+    def link_subchapters(self, parent_chapter_id: int, subchapter_ids: list[int]) -> None:
+        query = """
+                MATCH (c1:Chapter), (c2:Chapter) 
+                WHERE ID(c1) = $parent_chapter_id AND ID(c2) IN $subchapter_ids
+                CREATE (c1)-[:CONTAINS]->(c2)
+                """
+        parameters = {
+            "parent_chapter_id": parent_chapter_id,
+            "subchapter_ids": subchapter_ids
+        }
+        self.database.query(query, parameters)
+
+    def get_other_diplomas_shingles(self, id_diploma: int) -> list[tuple[int, str]]:
path: src/repository/diploma_repo.py
user: login: Space27
body: Возвращаемое значение - не такое
d.shigles - список, то есть возвращается список кортежей (id, list[int])
created_at: 2025-04-27T08:46:59Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,197 @@
+from neo4j import GraphDatabase
+from neo4j.exceptions import ServiceUnavailable
+from src.diploma_processing.data_types import Chapter, Diploma
+
+
+class Neo4jDatabase:
+    def __init__(self, uri, user, password):
+        """
+        Инициализация соединения с базой данных Neo4j.
+        """
+        self._driver = None
+        try:
+            self._driver = GraphDatabase.driver(uri, auth=(user, password))
+            # >>> ПРОВЕРКА ДОСТУПНОСТИ БАЗЫ <<<
+            with self._driver.session() as session:
+                session.run("RETURN 1")  # минимальный запрос
+            print("Подключение к базе данных успешно установлено.")
+        except ServiceUnavailable as e:
+            print(f"Не удалось подключиться к базе данных: {e}")
+            raise
+        except Exception as e:
+            print(f"Ошибка подключения к базе данных: {e}")
+            raise
+
+    def close(self):
+        """
+        Закрытие соединения с базой данных.
+        """
+        if self._driver:
+            try:
+                self._driver.close()
+                print("Соединение с базой данных успешно закрыто.")
+            except Exception as e:
+                print(f"Ошибка при закрытии соединения: {e}")
+
+    def query(self, query, parameters=None):
+        """
+        Выполнение Cypher-запроса.
+        :param query: Строка с Cypher-запросом.
+        :param parameters: Параметры для запроса (опционально).
+        :return: Результат выполнения запроса.
+        """
+        assert self._driver is not None, "Driver not initialized!"
+        session = None
+        response = None
+
+        try:
+            session = self._driver.session()
+            if parameters is None:
+                response = list(session.run(query))
+            else:
+                response = list(session.run(query, parameters))  # <-- сюда передать parameters
+            print("Запрос успешно выполнен.")
+        except Exception as e:
+            print(f"Ошибка при выполнении запроса: {e}")
+        finally:
+            if session is not None:
+                session.close()
+
+        return response
+
+
+class DiplomaRepository:
+    def __init__(self, database: Neo4jDatabase):
+        self.database = database
+
+    def save_diploma(self, diploma: Diploma) -> int | None:
+        query = """
+                CREATE (d:Diploma { 
+                    name: $name, 
+                    author: $author, 
+                    academic_supervisor: $academic_supervisor, 
+                    year: $year, 
+                    disclosure_persentage: 0, 
+                    shingles: ''
+                })
+                SET d.id = ID(d)
+                RETURN d.id
+                """
+        parameters = {
+            "name": diploma.name,
+            "author": diploma.author,
+            "academic_supervisor": diploma.academic_supervisor,
+            "year": diploma.year
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            diploma.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return diploma.id
+        return None
+
+    def save_chapter(self, chapter: Chapter) -> int | None:
+        query = """
+                CREATE (c:Chapter {
+                    id_diploma: $id_diploma, 
+                    name: $name, 
+                    water_content: $water_content, 
+                    words: $words, 
+                    symbols: $symbols, 
+                    commonly_used_words: $commonly_used_words, 
+                    commonly_used_words_amount: $commonly_used_words_amount
+                })
+                SET c.id = ID(c)
+                RETURN ID(c)
+                """
+        parameters = {
+            "id_diploma": chapter.id_diploma,
+            "name": chapter.name,
+            "water_content": chapter.water_content,
+            "words": chapter.words,
+            "symbols": chapter.symbols,
+            "commonly_used_words": chapter.commonly_used_words,
+            "commonly_used_words_amount": chapter.commonly_used_words_amount
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            chapter.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return chapter.id
+        return None
+
+    def link_chapter_to_diploma(self, id_diploma: int) -> None:
+        query = """
+                MATCH (d:Diploma), (c:Chapter) 
+                WHERE ID(d) = $id_diploma AND c.id_diploma = $id_diploma
+                CREATE (d)-[:CONTAINS]->(c)
+                """
+        parameters = {"id_diploma": id_diploma}
+        self.database.query(query, parameters)
+
+    def link_subchapters(self, parent_chapter_id: int, subchapter_ids: list[int]) -> None:
+        query = """
+                MATCH (c1:Chapter), (c2:Chapter) 
+                WHERE ID(c1) = $parent_chapter_id AND ID(c2) IN $subchapter_ids
+                CREATE (c1)-[:CONTAINS]->(c2)
+                """
+        parameters = {
+            "parent_chapter_id": parent_chapter_id,
+            "subchapter_ids": subchapter_ids
+        }
+        self.database.query(query, parameters)
+
+    def get_other_diplomas_shingles(self, id_diploma: int) -> list[tuple[int, str]]:
+        query = """
+                MATCH (d:Diploma)
+                WHERE ID(d) <> $id_diploma
+                RETURN ID(d), d.shingles
+                """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+        return [(record[0], record[1]) for record in result]
+
+    def save_similarity(self, id_diploma: int, id_diploma_2: int, similarity: float) -> None:
+        query = """
+                MATCH (d1:Diploma), (d2:Diploma)
+                WHERE ID(d1) = $id_diploma AND ID(d2) = $id_diploma_2
path: src/repository/diploma_repo.py
user: login: Space27
body: тоже d.id
created_at: 2025-04-27T08:47:18Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,197 @@
+from neo4j import GraphDatabase
+from neo4j.exceptions import ServiceUnavailable
+from src.diploma_processing.data_types import Chapter, Diploma
+
+
+class Neo4jDatabase:
+    def __init__(self, uri, user, password):
+        """
+        Инициализация соединения с базой данных Neo4j.
+        """
+        self._driver = None
+        try:
+            self._driver = GraphDatabase.driver(uri, auth=(user, password))
+            # >>> ПРОВЕРКА ДОСТУПНОСТИ БАЗЫ <<<
+            with self._driver.session() as session:
+                session.run("RETURN 1")  # минимальный запрос
+            print("Подключение к базе данных успешно установлено.")
+        except ServiceUnavailable as e:
+            print(f"Не удалось подключиться к базе данных: {e}")
+            raise
+        except Exception as e:
+            print(f"Ошибка подключения к базе данных: {e}")
+            raise
+
+    def close(self):
+        """
+        Закрытие соединения с базой данных.
+        """
+        if self._driver:
+            try:
+                self._driver.close()
+                print("Соединение с базой данных успешно закрыто.")
+            except Exception as e:
+                print(f"Ошибка при закрытии соединения: {e}")
+
+    def query(self, query, parameters=None):
+        """
+        Выполнение Cypher-запроса.
+        :param query: Строка с Cypher-запросом.
+        :param parameters: Параметры для запроса (опционально).
+        :return: Результат выполнения запроса.
+        """
+        assert self._driver is not None, "Driver not initialized!"
+        session = None
+        response = None
+
+        try:
+            session = self._driver.session()
+            if parameters is None:
+                response = list(session.run(query))
+            else:
+                response = list(session.run(query, parameters))  # <-- сюда передать parameters
+            print("Запрос успешно выполнен.")
+        except Exception as e:
+            print(f"Ошибка при выполнении запроса: {e}")
+        finally:
+            if session is not None:
+                session.close()
+
+        return response
+
+
+class DiplomaRepository:
+    def __init__(self, database: Neo4jDatabase):
+        self.database = database
+
+    def save_diploma(self, diploma: Diploma) -> int | None:
+        query = """
+                CREATE (d:Diploma { 
+                    name: $name, 
+                    author: $author, 
+                    academic_supervisor: $academic_supervisor, 
+                    year: $year, 
+                    disclosure_persentage: 0, 
+                    shingles: ''
+                })
+                SET d.id = ID(d)
+                RETURN d.id
+                """
+        parameters = {
+            "name": diploma.name,
+            "author": diploma.author,
+            "academic_supervisor": diploma.academic_supervisor,
+            "year": diploma.year
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            diploma.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return diploma.id
+        return None
+
+    def save_chapter(self, chapter: Chapter) -> int | None:
+        query = """
+                CREATE (c:Chapter {
+                    id_diploma: $id_diploma, 
+                    name: $name, 
+                    water_content: $water_content, 
+                    words: $words, 
+                    symbols: $symbols, 
+                    commonly_used_words: $commonly_used_words, 
+                    commonly_used_words_amount: $commonly_used_words_amount
+                })
+                SET c.id = ID(c)
+                RETURN ID(c)
+                """
+        parameters = {
+            "id_diploma": chapter.id_diploma,
+            "name": chapter.name,
+            "water_content": chapter.water_content,
+            "words": chapter.words,
+            "symbols": chapter.symbols,
+            "commonly_used_words": chapter.commonly_used_words,
+            "commonly_used_words_amount": chapter.commonly_used_words_amount
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            chapter.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return chapter.id
+        return None
+
+    def link_chapter_to_diploma(self, id_diploma: int) -> None:
+        query = """
+                MATCH (d:Diploma), (c:Chapter) 
+                WHERE ID(d) = $id_diploma AND c.id_diploma = $id_diploma
+                CREATE (d)-[:CONTAINS]->(c)
+                """
+        parameters = {"id_diploma": id_diploma}
+        self.database.query(query, parameters)
+
+    def link_subchapters(self, parent_chapter_id: int, subchapter_ids: list[int]) -> None:
+        query = """
+                MATCH (c1:Chapter), (c2:Chapter) 
+                WHERE ID(c1) = $parent_chapter_id AND ID(c2) IN $subchapter_ids
+                CREATE (c1)-[:CONTAINS]->(c2)
+                """
+        parameters = {
+            "parent_chapter_id": parent_chapter_id,
+            "subchapter_ids": subchapter_ids
+        }
+        self.database.query(query, parameters)
+
+    def get_other_diplomas_shingles(self, id_diploma: int) -> list[tuple[int, str]]:
+        query = """
+                MATCH (d:Diploma)
+                WHERE ID(d) <> $id_diploma
+                RETURN ID(d), d.shingles
+                """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+        return [(record[0], record[1]) for record in result]
+
+    def save_similarity(self, id_diploma: int, id_diploma_2: int, similarity: float) -> None:
+        query = """
+                MATCH (d1:Diploma), (d2:Diploma)
+                WHERE ID(d1) = $id_diploma AND ID(d2) = $id_diploma_2
+                CREATE (d1)-[:SIMILAR_TO {similarity: $similarity}]->(d2)
+                """
+        parameters = {
+            "id_diploma": id_diploma,
+            "id_diploma_2": id_diploma_2,
+            "similarity": similarity
+        }
+        self.database.query(query, parameters)
+
+    def load_diploma_graph(self, id_diploma: int) -> tuple[list[int], list[list[int]]]:
+        query = """
+                MATCH p = (d:Diploma)-[r:CONTAINS*0..]->(x) 
+                WHERE ID(d) = $id_diploma
+                RETURN 
+                    COLLECT(DISTINCT ID(x)) AS nodes,
+                    [r IN COLLECT(DISTINCT LAST(r)) | [ID(startNode(r)), ID(endNode(r))]] AS rels
+                """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+        nodes, rels = result[0] if result else ([], [])
+        return nodes, rels
+
+    def load_diploma_data(self, id_diploma: int) -> dict:
path: src/repository/diploma_repo.py
user: login: Space27
body: Вовращать dict не очень красиво - пользователь же загружает Диплом, а не какой-то сферический словарь в вакууме
created_at: 2025-04-27T08:49:54Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,197 @@
+from neo4j import GraphDatabase
+from neo4j.exceptions import ServiceUnavailable
+from src.diploma_processing.data_types import Chapter, Diploma
+
+
+class Neo4jDatabase:
+    def __init__(self, uri, user, password):
+        """
+        Инициализация соединения с базой данных Neo4j.
+        """
+        self._driver = None
+        try:
+            self._driver = GraphDatabase.driver(uri, auth=(user, password))
+            # >>> ПРОВЕРКА ДОСТУПНОСТИ БАЗЫ <<<
+            with self._driver.session() as session:
+                session.run("RETURN 1")  # минимальный запрос
+            print("Подключение к базе данных успешно установлено.")
+        except ServiceUnavailable as e:
+            print(f"Не удалось подключиться к базе данных: {e}")
+            raise
+        except Exception as e:
+            print(f"Ошибка подключения к базе данных: {e}")
+            raise
+
+    def close(self):
+        """
+        Закрытие соединения с базой данных.
+        """
+        if self._driver:
+            try:
+                self._driver.close()
+                print("Соединение с базой данных успешно закрыто.")
+            except Exception as e:
+                print(f"Ошибка при закрытии соединения: {e}")
+
+    def query(self, query, parameters=None):
+        """
+        Выполнение Cypher-запроса.
+        :param query: Строка с Cypher-запросом.
+        :param parameters: Параметры для запроса (опционально).
+        :return: Результат выполнения запроса.
+        """
+        assert self._driver is not None, "Driver not initialized!"
+        session = None
+        response = None
+
+        try:
+            session = self._driver.session()
+            if parameters is None:
+                response = list(session.run(query))
+            else:
+                response = list(session.run(query, parameters))  # <-- сюда передать parameters
+            print("Запрос успешно выполнен.")
+        except Exception as e:
+            print(f"Ошибка при выполнении запроса: {e}")
+        finally:
+            if session is not None:
+                session.close()
+
+        return response
+
+
+class DiplomaRepository:
+    def __init__(self, database: Neo4jDatabase):
+        self.database = database
+
+    def save_diploma(self, diploma: Diploma) -> int | None:
+        query = """
+                CREATE (d:Diploma { 
+                    name: $name, 
+                    author: $author, 
+                    academic_supervisor: $academic_supervisor, 
+                    year: $year, 
+                    disclosure_persentage: 0, 
+                    shingles: ''
+                })
+                SET d.id = ID(d)
+                RETURN d.id
+                """
+        parameters = {
+            "name": diploma.name,
+            "author": diploma.author,
+            "academic_supervisor": diploma.academic_supervisor,
+            "year": diploma.year
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            diploma.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return diploma.id
+        return None
+
+    def save_chapter(self, chapter: Chapter) -> int | None:
+        query = """
+                CREATE (c:Chapter {
+                    id_diploma: $id_diploma, 
+                    name: $name, 
+                    water_content: $water_content, 
+                    words: $words, 
+                    symbols: $symbols, 
+                    commonly_used_words: $commonly_used_words, 
+                    commonly_used_words_amount: $commonly_used_words_amount
+                })
+                SET c.id = ID(c)
+                RETURN ID(c)
+                """
+        parameters = {
+            "id_diploma": chapter.id_diploma,
+            "name": chapter.name,
+            "water_content": chapter.water_content,
+            "words": chapter.words,
+            "symbols": chapter.symbols,
+            "commonly_used_words": chapter.commonly_used_words,
+            "commonly_used_words_amount": chapter.commonly_used_words_amount
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            chapter.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return chapter.id
+        return None
+
+    def link_chapter_to_diploma(self, id_diploma: int) -> None:
+        query = """
+                MATCH (d:Diploma), (c:Chapter) 
+                WHERE ID(d) = $id_diploma AND c.id_diploma = $id_diploma
+                CREATE (d)-[:CONTAINS]->(c)
+                """
+        parameters = {"id_diploma": id_diploma}
+        self.database.query(query, parameters)
+
+    def link_subchapters(self, parent_chapter_id: int, subchapter_ids: list[int]) -> None:
+        query = """
+                MATCH (c1:Chapter), (c2:Chapter) 
+                WHERE ID(c1) = $parent_chapter_id AND ID(c2) IN $subchapter_ids
+                CREATE (c1)-[:CONTAINS]->(c2)
+                """
+        parameters = {
+            "parent_chapter_id": parent_chapter_id,
+            "subchapter_ids": subchapter_ids
+        }
+        self.database.query(query, parameters)
+
+    def get_other_diplomas_shingles(self, id_diploma: int) -> list[tuple[int, str]]:
+        query = """
+                MATCH (d:Diploma)
+                WHERE ID(d) <> $id_diploma
+                RETURN ID(d), d.shingles
+                """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+        return [(record[0], record[1]) for record in result]
+
+    def save_similarity(self, id_diploma: int, id_diploma_2: int, similarity: float) -> None:
+        query = """
+                MATCH (d1:Diploma), (d2:Diploma)
+                WHERE ID(d1) = $id_diploma AND ID(d2) = $id_diploma_2
+                CREATE (d1)-[:SIMILAR_TO {similarity: $similarity}]->(d2)
+                """
+        parameters = {
+            "id_diploma": id_diploma,
+            "id_diploma_2": id_diploma_2,
+            "similarity": similarity
+        }
+        self.database.query(query, parameters)
+
+    def load_diploma_graph(self, id_diploma: int) -> tuple[list[int], list[list[int]]]:
+        query = """
+                MATCH p = (d:Diploma)-[r:CONTAINS*0..]->(x) 
+                WHERE ID(d) = $id_diploma
+                RETURN 
+                    COLLECT(DISTINCT ID(x)) AS nodes,
+                    [r IN COLLECT(DISTINCT LAST(r)) | [ID(startNode(r)), ID(endNode(r))]] AS rels
+                """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+        nodes, rels = result[0] if result else ([], [])
+        return nodes, rels
+
+    def load_diploma_data(self, id_diploma: int) -> dict:
+        query = """
+                MATCH (d:Diploma)
+                WHERE ID(d) = $id_diploma
+                RETURN d
+                """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+        return dict(result[0]["d"]) if result else {}
+
+    def load_chapters(self, chapter_ids: list[int]) -> list[dict]:
path: src/repository/diploma_repo.py
user: login: Space27
body: То же самое, лучше возвращать список разделов
created_at: 2025-04-27T08:50:13Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,455 @@
+from neo4j import GraphDatabase
+from neo4j.exceptions import ServiceUnavailable
+import datetime
+from dataclasses import dataclass
+
+
+@dataclass
+class Chapter:
+    id: int | None
+    id_diploma: int | None
+    name: str
+    water_content: int
+    words: int
+    symbols: int
+    commonly_used_words: list[str]
+    commonly_used_words_amount: list[int]
+    chapters: list['Chapter']
+
+
+@dataclass
+class Diploma:
+    id: int | None
+    name: str
+    author: str
+    academic_supervisor: str | None  # None, если не получилось извлечь руководителя
+    year: int
+    words: int
+    load_date: datetime
+    chapters: list[Chapter]
+
+
+class Neo4jDatabase:
path: src/repository/testing_diploma_repo_methods.py
user: login: Space27
body: Зачем копировать эти классы?)
created_at: 2025-04-27T08:55:02Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,455 @@
+from neo4j import GraphDatabase
+from neo4j.exceptions import ServiceUnavailable
+import datetime
+from dataclasses import dataclass
+
+
+@dataclass
+class Chapter:
+    id: int | None
+    id_diploma: int | None
+    name: str
+    water_content: int
+    words: int
+    symbols: int
+    commonly_used_words: list[str]
+    commonly_used_words_amount: list[int]
+    chapters: list['Chapter']
+
+
+@dataclass
+class Diploma:
+    id: int | None
+    name: str
+    author: str
+    academic_supervisor: str | None  # None, если не получилось извлечь руководителя
+    year: int
+    words: int
+    load_date: datetime
+    chapters: list[Chapter]
+
+
+class Neo4jDatabase:
path: src/repository/testing_diploma_repo_methods.py
user: login: volk6022
body: для того, чтобы получить доступ к модулю в соседней папке, нужно добавить папку в sys.path:
```python
import sys
sys.path.append('c:/nosql1h25-reports/')
```
сначала добавляешь в sys.path, а потом импортируешь
```python
from src.diploma_processing.data_types import Diploma, Chapter
```
created_at: 2025-04-27T09:14:20Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,455 @@
+from neo4j import GraphDatabase
path: src/repository/testing_diploma_repo_methods.py
user: login: vRedniy-LumbeRt
body: убрал
created_at: 2025-04-27T11:11:20Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,198 @@
+from neo4j import GraphDatabase
+from neo4j.exceptions import ServiceUnavailable
+from src.diploma_processing.data_types import Chapter, Diploma
+
+
+class Neo4jDatabase:
+    def __init__(self, uri, user, password):
+        """
+        Инициализация соединения с базой данных Neo4j.
+        """
+        self._driver = None
+        try:
+            self._driver = GraphDatabase.driver(uri, auth=(user, password))
+            # >>> ПРОВЕРКА ДОСТУПНОСТИ БАЗЫ <<<
+            with self._driver.session() as session:
+                session.run("RETURN 1")  # минимальный запрос
+            print("Подключение к базе данных успешно установлено.")
+        except ServiceUnavailable as e:
+            print(f"Не удалось подключиться к базе данных: {e}")
+            raise
+        except Exception as e:
+            print(f"Ошибка подключения к базе данных: {e}")
+            raise
+
+    def close(self):
+        """
+        Закрытие соединения с базой данных.
+        """
+        if self._driver:
+            try:
+                self._driver.close()
+                print("Соединение с базой данных успешно закрыто.")
+            except Exception as e:
+                print(f"Ошибка при закрытии соединения: {e}")
+
+    def query(self, query, parameters=None):
+        """
+        Выполнение Cypher-запроса.
+        :param query: Строка с Cypher-запросом.
+        :param parameters: Параметры для запроса (опционально).
+        :return: Результат выполнения запроса.
+        """
+        assert self._driver is not None, "Driver not initialized!"
+        session = None
+        response = None
+
+        try:
+            session = self._driver.session()
+            if parameters is None:
+                response = list(session.run(query))
+            else:
+                response = list(session.run(query, parameters))  # <-- сюда передать parameters
+            print("Запрос успешно выполнен.")
+        except Exception as e:
+            print(f"Ошибка при выполнении запроса: {e}")
+        finally:
+            if session is not None:
+                session.close()
+
+        return response
+
+
+class DiplomaRepository:
+    def __init__(self, database: Neo4jDatabase):
+        self.database = database
+
+    def save_diploma(self, diploma: Diploma) -> int | None:
+        query = """
+        CREATE (d:Diploma { 
+            name: $name, 
+            author: $author, 
+            academic_supervisor: $academic_supervisor, 
+            year: $year, 
+            words: $words,
+            load_date: $load_date
+        })
+        SET d.id = ID(d)
+        RETURN d.id
+        """
+        parameters = {
+            "name": diploma.name,
+            "author": diploma.author,
+            "academic_supervisor": diploma.academic_supervisor,
+            "year": diploma.year,
+            "words": diploma.words,
+            "load_date": diploma.load_date
path: src/repository/diploma_repo.py
user: login: Space27
body: Так оно работает?
created_at: 2025-04-27T11:25:59Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,198 @@
+from neo4j import GraphDatabase
+from neo4j.exceptions import ServiceUnavailable
+from src.diploma_processing.data_types import Chapter, Diploma
+
+
+class Neo4jDatabase:
+    def __init__(self, uri, user, password):
+        """
+        Инициализация соединения с базой данных Neo4j.
+        """
+        self._driver = None
+        try:
+            self._driver = GraphDatabase.driver(uri, auth=(user, password))
+            # >>> ПРОВЕРКА ДОСТУПНОСТИ БАЗЫ <<<
+            with self._driver.session() as session:
+                session.run("RETURN 1")  # минимальный запрос
+            print("Подключение к базе данных успешно установлено.")
+        except ServiceUnavailable as e:
+            print(f"Не удалось подключиться к базе данных: {e}")
+            raise
+        except Exception as e:
+            print(f"Ошибка подключения к базе данных: {e}")
+            raise
+
+    def close(self):
+        """
+        Закрытие соединения с базой данных.
+        """
+        if self._driver:
+            try:
+                self._driver.close()
+                print("Соединение с базой данных успешно закрыто.")
+            except Exception as e:
+                print(f"Ошибка при закрытии соединения: {e}")
+
+    def query(self, query, parameters=None):
+        """
+        Выполнение Cypher-запроса.
+        :param query: Строка с Cypher-запросом.
+        :param parameters: Параметры для запроса (опционально).
+        :return: Результат выполнения запроса.
+        """
+        assert self._driver is not None, "Driver not initialized!"
+        session = None
+        response = None
+
+        try:
+            session = self._driver.session()
+            if parameters is None:
+                response = list(session.run(query))
+            else:
+                response = list(session.run(query, parameters))  # <-- сюда передать parameters
+            print("Запрос успешно выполнен.")
+        except Exception as e:
+            print(f"Ошибка при выполнении запроса: {e}")
+        finally:
+            if session is not None:
+                session.close()
+
+        return response
+
+
+class DiplomaRepository:
+    def __init__(self, database: Neo4jDatabase):
+        self.database = database
+
+    def save_diploma(self, diploma: Diploma) -> int | None:
+        query = """
+        CREATE (d:Diploma { 
+            name: $name, 
+            author: $author, 
+            academic_supervisor: $academic_supervisor, 
+            year: $year, 
+            words: $words,
+            load_date: $load_date
+        })
+        SET d.id = ID(d)
+        RETURN d.id
+        """
+        parameters = {
+            "name": diploma.name,
+            "author": diploma.author,
+            "academic_supervisor": diploma.academic_supervisor,
+            "year": diploma.year,
+            "words": diploma.words,
+            "load_date": diploma.load_date
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            diploma.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return diploma.id
+        return None
+
+    def save_chapter(self, chapter: Chapter) -> int | None:
+        query = """
+        CREATE (c:Chapter {
+            id_diploma: $id_diploma, 
+            name: $name, 
+            water_content: $water_content, 
+            words: $words, 
+            symbols: $symbols, 
+            commonly_used_words: $commonly_used_words, 
+            commonly_used_words_amount: $commonly_used_words_amount
+        })
+        SET c.id = ID(c)
+        RETURN c.id
+        """
+        parameters = {
+            "id_diploma": chapter.id_diploma,
+            "name": chapter.name,
+            "water_content": chapter.water_content,
+            "words": chapter.words,
+            "symbols": chapter.symbols,
+            "commonly_used_words": chapter.commonly_used_words,
+            "commonly_used_words_amount": chapter.commonly_used_words_amount
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            chapter.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return chapter.id
+        return None
+
+    def link_chapter_to_diploma(self, id_diploma: int) -> None:
+        query = """
+        MATCH (d:Diploma), (c:Chapter) 
+        WHERE d.id = $id_diploma AND c.id_diploma = $id_diploma
+        CREATE (d)-[:CONTAINS]->(c)
+        """
+        parameters = {"id_diploma": id_diploma}
+        self.database.query(query, parameters)
+
+    def link_subchapters(self, parent_chapter_id: int, subchapter_ids: list[int]) -> None:
+        query = """
+        MATCH (c1:Chapter), (c2:Chapter) 
+        WHERE c1.id = $parent_chapter_id AND c2.id IN $subchapter_ids
+        CREATE (c1)-[:CONTAINS]->(c2)
+        """
+        parameters = {
+            "parent_chapter_id": parent_chapter_id,
+            "subchapter_ids": subchapter_ids
+        }
+        self.database.query(query, parameters)
+
+    def get_other_diplomas_shingles(self, id_diploma: int) -> list[tuple[int, list[int]]]:
+        query = """
+        MATCH (d:Diploma)
+        WHERE d.id <> $id_diploma
+        RETURN d.id, d.shingles
+        """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+        return [(int(record[0]), list(map(int, record[1]))) for record in result]
+
+    def save_similarity(self, id_diploma: int, id_diploma_2: int, similarity: float) -> None:
+        query = """
+        MATCH (d1:Diploma), (d2:Diploma)
+        WHERE d1.id = $id_diploma AND d2.id = $id_diploma_2
+        CREATE (d1)-[:SIMILAR_TO {similarity: $similarity}]->(d2)
+        """
+        parameters = {
+            "id_diploma": id_diploma,
+            "id_diploma_2": id_diploma_2,
+            "similarity": similarity
+        }
+        self.database.query(query, parameters)
+
+    def load_diploma_graph(self, id_diploma: int) -> tuple[list[int], list[list[int]]]:
+        query = """
+        MATCH p = (d:Diploma)-[r:CONTAINS*0..]->(x) 
+        WHERE d.id = $id_diploma
+        RETURN 
+            COLLECT(DISTINCT ID(x)) AS nodes,
+            [r IN COLLECT(DISTINCT LAST(r)) | [ID(startNode(r)), ID(endNode(r))]] AS rels
+        """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+        nodes, rels = result[0] if result else ([], [])
+        return nodes, rels
+
+    def load_diploma_data(self, id_diploma: int) -> Diploma:
+        query = """
+        MATCH (d:Diploma)
+        WHERE d.id = $id_diploma
+        RETURN d
+        """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+        return result[0]["d"] if result else {}
path: src/repository/diploma_repo.py
user: login: Space27
body: Оно так не работает как будто
created_at: 2025-04-27T11:27:57Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,198 @@
+from neo4j import GraphDatabase
+from neo4j.exceptions import ServiceUnavailable
+from src.diploma_processing.data_types import Chapter, Diploma
+
+
+class Neo4jDatabase:
+    def __init__(self, uri, user, password):
+        """
+        Инициализация соединения с базой данных Neo4j.
+        """
+        self._driver = None
+        try:
+            self._driver = GraphDatabase.driver(uri, auth=(user, password))
+            # >>> ПРОВЕРКА ДОСТУПНОСТИ БАЗЫ <<<
+            with self._driver.session() as session:
+                session.run("RETURN 1")  # минимальный запрос
+            print("Подключение к базе данных успешно установлено.")
+        except ServiceUnavailable as e:
+            print(f"Не удалось подключиться к базе данных: {e}")
+            raise
+        except Exception as e:
+            print(f"Ошибка подключения к базе данных: {e}")
+            raise
+
+    def close(self):
+        """
+        Закрытие соединения с базой данных.
+        """
+        if self._driver:
+            try:
+                self._driver.close()
+                print("Соединение с базой данных успешно закрыто.")
+            except Exception as e:
+                print(f"Ошибка при закрытии соединения: {e}")
+
+    def query(self, query, parameters=None):
+        """
+        Выполнение Cypher-запроса.
+        :param query: Строка с Cypher-запросом.
+        :param parameters: Параметры для запроса (опционально).
+        :return: Результат выполнения запроса.
+        """
+        assert self._driver is not None, "Driver not initialized!"
+        session = None
+        response = None
+
+        try:
+            session = self._driver.session()
+            if parameters is None:
+                response = list(session.run(query))
+            else:
+                response = list(session.run(query, parameters))  # <-- сюда передать parameters
+            print("Запрос успешно выполнен.")
+        except Exception as e:
+            print(f"Ошибка при выполнении запроса: {e}")
+        finally:
+            if session is not None:
+                session.close()
+
+        return response
+
+
+class DiplomaRepository:
+    def __init__(self, database: Neo4jDatabase):
+        self.database = database
+
+    def save_diploma(self, diploma: Diploma) -> int | None:
+        query = """
+        CREATE (d:Diploma { 
+            name: $name, 
+            author: $author, 
+            academic_supervisor: $academic_supervisor, 
+            year: $year, 
+            words: $words,
+            load_date: $load_date
+        })
+        SET d.id = ID(d)
+        RETURN d.id
+        """
+        parameters = {
+            "name": diploma.name,
+            "author": diploma.author,
+            "academic_supervisor": diploma.academic_supervisor,
+            "year": diploma.year,
+            "words": diploma.words,
+            "load_date": diploma.load_date
path: src/repository/diploma_repo.py
user: login: vRedniy-LumbeRt
body: исправил, работает
created_at: 2025-04-27T12:52:15Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,198 @@
+from neo4j import GraphDatabase
+from neo4j.exceptions import ServiceUnavailable
+from src.diploma_processing.data_types import Chapter, Diploma
+
+
+class Neo4jDatabase:
+    def __init__(self, uri, user, password):
+        """
+        Инициализация соединения с базой данных Neo4j.
+        """
+        self._driver = None
+        try:
+            self._driver = GraphDatabase.driver(uri, auth=(user, password))
+            # >>> ПРОВЕРКА ДОСТУПНОСТИ БАЗЫ <<<
+            with self._driver.session() as session:
+                session.run("RETURN 1")  # минимальный запрос
+            print("Подключение к базе данных успешно установлено.")
+        except ServiceUnavailable as e:
+            print(f"Не удалось подключиться к базе данных: {e}")
+            raise
+        except Exception as e:
+            print(f"Ошибка подключения к базе данных: {e}")
+            raise
+
+    def close(self):
+        """
+        Закрытие соединения с базой данных.
+        """
+        if self._driver:
+            try:
+                self._driver.close()
+                print("Соединение с базой данных успешно закрыто.")
+            except Exception as e:
+                print(f"Ошибка при закрытии соединения: {e}")
+
+    def query(self, query, parameters=None):
+        """
+        Выполнение Cypher-запроса.
+        :param query: Строка с Cypher-запросом.
+        :param parameters: Параметры для запроса (опционально).
+        :return: Результат выполнения запроса.
+        """
+        assert self._driver is not None, "Driver not initialized!"
+        session = None
+        response = None
+
+        try:
+            session = self._driver.session()
+            if parameters is None:
+                response = list(session.run(query))
+            else:
+                response = list(session.run(query, parameters))  # <-- сюда передать parameters
+            print("Запрос успешно выполнен.")
+        except Exception as e:
+            print(f"Ошибка при выполнении запроса: {e}")
+        finally:
+            if session is not None:
+                session.close()
+
+        return response
+
+
+class DiplomaRepository:
+    def __init__(self, database: Neo4jDatabase):
+        self.database = database
+
+    def save_diploma(self, diploma: Diploma) -> int | None:
+        query = """
+        CREATE (d:Diploma { 
+            name: $name, 
+            author: $author, 
+            academic_supervisor: $academic_supervisor, 
+            year: $year, 
+            words: $words,
+            load_date: $load_date
+        })
+        SET d.id = ID(d)
+        RETURN d.id
+        """
+        parameters = {
+            "name": diploma.name,
+            "author": diploma.author,
+            "academic_supervisor": diploma.academic_supervisor,
+            "year": diploma.year,
+            "words": diploma.words,
+            "load_date": diploma.load_date
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            diploma.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return diploma.id
+        return None
+
+    def save_chapter(self, chapter: Chapter) -> int | None:
+        query = """
+        CREATE (c:Chapter {
+            id_diploma: $id_diploma, 
+            name: $name, 
+            water_content: $water_content, 
+            words: $words, 
+            symbols: $symbols, 
+            commonly_used_words: $commonly_used_words, 
+            commonly_used_words_amount: $commonly_used_words_amount
+        })
+        SET c.id = ID(c)
+        RETURN c.id
+        """
+        parameters = {
+            "id_diploma": chapter.id_diploma,
+            "name": chapter.name,
+            "water_content": chapter.water_content,
+            "words": chapter.words,
+            "symbols": chapter.symbols,
+            "commonly_used_words": chapter.commonly_used_words,
+            "commonly_used_words_amount": chapter.commonly_used_words_amount
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            chapter.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return chapter.id
+        return None
+
+    def link_chapter_to_diploma(self, id_diploma: int) -> None:
+        query = """
+        MATCH (d:Diploma), (c:Chapter) 
+        WHERE d.id = $id_diploma AND c.id_diploma = $id_diploma
+        CREATE (d)-[:CONTAINS]->(c)
+        """
+        parameters = {"id_diploma": id_diploma}
+        self.database.query(query, parameters)
+
+    def link_subchapters(self, parent_chapter_id: int, subchapter_ids: list[int]) -> None:
+        query = """
+        MATCH (c1:Chapter), (c2:Chapter) 
+        WHERE c1.id = $parent_chapter_id AND c2.id IN $subchapter_ids
+        CREATE (c1)-[:CONTAINS]->(c2)
+        """
+        parameters = {
+            "parent_chapter_id": parent_chapter_id,
+            "subchapter_ids": subchapter_ids
+        }
+        self.database.query(query, parameters)
+
+    def get_other_diplomas_shingles(self, id_diploma: int) -> list[tuple[int, list[int]]]:
+        query = """
+        MATCH (d:Diploma)
+        WHERE d.id <> $id_diploma
+        RETURN d.id, d.shingles
+        """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+        return [(int(record[0]), list(map(int, record[1]))) for record in result]
+
+    def save_similarity(self, id_diploma: int, id_diploma_2: int, similarity: float) -> None:
+        query = """
+        MATCH (d1:Diploma), (d2:Diploma)
+        WHERE d1.id = $id_diploma AND d2.id = $id_diploma_2
+        CREATE (d1)-[:SIMILAR_TO {similarity: $similarity}]->(d2)
+        """
+        parameters = {
+            "id_diploma": id_diploma,
+            "id_diploma_2": id_diploma_2,
+            "similarity": similarity
+        }
+        self.database.query(query, parameters)
+
+    def load_diploma_graph(self, id_diploma: int) -> tuple[list[int], list[list[int]]]:
+        query = """
+        MATCH p = (d:Diploma)-[r:CONTAINS*0..]->(x) 
+        WHERE d.id = $id_diploma
+        RETURN 
+            COLLECT(DISTINCT ID(x)) AS nodes,
+            [r IN COLLECT(DISTINCT LAST(r)) | [ID(startNode(r)), ID(endNode(r))]] AS rels
+        """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+        nodes, rels = result[0] if result else ([], [])
+        return nodes, rels
+
+    def load_diploma_data(self, id_diploma: int) -> Diploma:
+        query = """
+        MATCH (d:Diploma)
+        WHERE d.id = $id_diploma
+        RETURN d
+        """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+        return result[0]["d"] if result else {}
path: src/repository/diploma_repo.py
user: login: vRedniy-LumbeRt
body: исправил
created_at: 2025-04-27T12:52:58Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,329 @@
+from neo4j import GraphDatabase
+from neo4j.exceptions import ServiceUnavailable
+from src.diploma_processing.data_types import Chapter, Diploma
+
+
+class Neo4jDatabase:
+    def __init__(self, uri, user, password):
+        """
+        Инициализация соединения с базой данных Neo4j.
+        """
+        self._driver = None
+        try:
+            self._driver = GraphDatabase.driver(uri, auth=(user, password))
+            # >>> ПРОВЕРКА ДОСТУПНОСТИ БАЗЫ <<<
+            with self._driver.session() as session:
+                session.run("RETURN 1")  # минимальный запрос
+            print("Подключение к базе данных успешно установлено.")
+        except ServiceUnavailable as e:
+            print(f"Не удалось подключиться к базе данных: {e}")
+            raise
+        except Exception as e:
+            print(f"Ошибка подключения к базе данных: {e}")
+            raise
+
+    def close(self):
+        """
+        Закрытие соединения с базой данных.
+        """
+        if self._driver:
+            try:
+                self._driver.close()
+                print("Соединение с базой данных успешно закрыто.")
+            except Exception as e:
+                print(f"Ошибка при закрытии соединения: {e}")
+
+    def query(self, query, parameters=None):
+        """
+        Выполнение Cypher-запроса.
+        :param query: Строка с Cypher-запросом.
+        :param parameters: Параметры для запроса (опционально).
+        :return: Результат выполнения запроса.
+        """
+        assert self._driver is not None, "Driver not initialized!"
+        session = None
+        response = None
+
+        try:
+            session = self._driver.session()
+            if parameters is None:
+                response = list(session.run(query))
+            else:
+                response = list(session.run(query, parameters))  # <-- сюда передать parameters
+            print("Запрос успешно выполнен.")
+        except Exception as e:
+            print(f"Ошибка при выполнении запроса: {e}")
+        finally:
+            if session is not None:
+                session.close()
+
+        return response
+
+
+class DiplomaRepository:
+    def __init__(self, database: Neo4jDatabase):
+        self.database = database
+
+    # works
+    def save_diploma(self, diploma: Diploma) -> int | None:
+        query = """
+        CREATE (d:Diploma { 
+            name: $name, 
+            author: $author, 
+            academic_supervisor: $academic_supervisor, 
+            year: $year, 
+            words: $words,
+            load_date: $load_date
+        })
+        SET d.id = ID(d)
+        RETURN d.id
+        """
+        parameters = {
+            "name": diploma.name,
+            "author": diploma.author,
+            "academic_supervisor": diploma.academic_supervisor,
+            "year": diploma.year,
+            "words": diploma.words,
+            "load_date": diploma.load_date.date()
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            diploma.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return diploma.id
+        return None
+
+    # works
+    def save_chapter(self, chapter: Chapter) -> int | None:
+        query = """
+        CREATE (c:Chapter {
+            id_diploma: $id_diploma, 
+            name: $name, 
+            water_content: $water_content, 
+            words: $words, 
+            symbols: $symbols, 
+            commonly_used_words: $commonly_used_words, 
+            commonly_used_words_amount: $commonly_used_words_amount
+        })
+        SET c.id = ID(c)
+        RETURN c.id
+        """
+        parameters = {
+            "id_diploma": chapter.id_diploma,
+            "name": chapter.name,
+            "water_content": chapter.water_content,
+            "words": chapter.words,
+            "symbols": chapter.symbols,
+            "commonly_used_words": chapter.commonly_used_words,
+            "commonly_used_words_amount": chapter.commonly_used_words_amount
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            chapter.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return chapter.id
+        return None
+
+    # works
+    def link_chapter_to_diploma(self, id_diploma: int) -> None:
+        query = """
+        MATCH (d:Diploma), (c:Chapter) 
+        WHERE d.id = $id_diploma AND c.id_diploma = $id_diploma
+        CREATE (d)-[:CONTAINS]->(c)
+        """
+        parameters = {"id_diploma": id_diploma}
+        self.database.query(query, parameters)
+
+    # works
+    def link_subchapters(self, parent_chapter_id: int, subchapter_ids: list[int]) -> None:
+        query = """
+        MATCH (c1:Chapter), (c2:Chapter) 
+        WHERE c1.id = $parent_chapter_id AND c2.id IN $subchapter_ids
+        CREATE (c1)-[:CONTAINS]->(c2)
+        """
+        parameters = {
+            "parent_chapter_id": parent_chapter_id,
+            "subchapter_ids": subchapter_ids
+        }
+        self.database.query(query, parameters)
+
+    # idk (no field 'shingles' in Diploma added yet)
+    def get_other_diplomas_shingles(self, id_diploma: int) -> list[tuple[int, list[int]]]:
+        query = """
+        MATCH (d:Diploma)
+        WHERE d.id <> $id_diploma
+        RETURN d.id, d.shingles
+        """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+        return [(int(record[0]), list(map(int, record[1]))) for record in result]
+
+    # works
+    def save_similarity(self, id_diploma: int, id_diploma_2: int, similarity: float) -> None:
+        query = """
+        MATCH (d1:Diploma), (d2:Diploma)
+        WHERE d1.id = $id_diploma AND d2.id = $id_diploma_2
+        CREATE (d1)-[:SIMILAR_TO {similarity: $similarity}]->(d2)
+        """
+        parameters = {
+            "id_diploma": id_diploma,
+            "id_diploma_2": id_diploma_2,
+            "similarity": similarity
+        }
+        self.database.query(query, parameters)
+
+    # works
+    def load_diploma_graph(self, id_diploma: int) -> list[list[int], list[list[int]]]:
+        query = """
+        MATCH p = (d:Diploma)-[r:CONTAINS*0..]->(x) 
+        WHERE d.id = $id_diploma
+        RETURN 
+            COLLECT(DISTINCT ID(x)) AS nodes,
+            [r IN COLLECT(DISTINCT LAST(r)) | [ID(startNode(r)), ID(endNode(r))]] AS rels
+        """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+        nodes, rels = result[0] if result else ([], [])
+        return [list(map(int, nodes)), rels]
+
+    # works
+    def load_diploma_data(self, id_diploma: int) -> Diploma | None:
+        query = """
+        MATCH (d:Diploma)
+        WHERE d.id = $id_diploma
+        RETURN d
+        """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+
+        if not result:
+            return None
+
+        node = result[0]["d"]
+
+        diploma = Diploma(
+            id=node.get("id"),
+            name=node.get("name", ""),
+            author=node.get("author", ""),
+            academic_supervisor=node.get("academic_supervisor"),
+            year=node.get("year", 0),
+            words=node.get("words", 0),
+            load_date=datetime.datetime(
+                node.get("load_date").year,
+                node.get("load_date").month,
+                node.get("load_date").day),
+            chapters=[]
+        )
+
+        return diploma
+
+    # works
+    def load_chapters(self, chapter_ids: list[int]) -> list[Chapter]:
+        query = """
+        MATCH (c:Chapter)
+        WHERE c.id IN $chapter_ids
+        RETURN c
+        """
+        parameters = {"chapter_ids": chapter_ids}
+        result = self.database.query(query, parameters)
+
+        chapters = []
+        for record in result:
+            node = record["c"]
+
+            chapter = Chapter(
+                id=node.get("id"),
+                id_diploma=node.get("id_diploma"),
+                name=node.get("name", ""),
+                water_content=node.get("water_content", 0),
+                words=node.get("words", 0),
+                symbols=node.get("symbols", 0),
+                commonly_used_words=node.get("commonly_used_words", []),
+                commonly_used_words_amount=node.get("commonly_used_words_amount", []),
+                chapters=[]
+            )
+
+            chapters.append(chapter)
+
+        return chapters
+
+    def search_diplomas(self,
+                        min_id: int = None, max_id: int = None,
+                        name: str = None, author: str = None, academic_supervisor: str = None,
+                        min_year: int = None, max_year: int = None,
+                        min_words: int = None, max_words: int = None,
+                        min_date: str = None, max_date: str = None,
+                        chapters: list[int] = None,
+                        order_by: str = "id", limit: int = 10, skip: int = 0) -> list[Diploma]:
+        query = """
+        MATCH (d:Diploma)-[:CONTAINS]->(c:Chapter)
+        WITH d, COLLECT(ID(c)) AS chapters
+        WHERE ($min_id IS NULL OR d.id >= $min_id)
+          AND ($max_id IS NULL OR d.id <= $max_id)
+          AND ($name IS NULL OR toLower(d.name) CONTAINS toLower($name))
+          AND ($author IS NULL OR toLower(d.author) CONTAINS toLower($author))
+          AND ($academic_supervisor IS NULL OR toLower(d.academic_supervisor) CONTAINS toLower($academic_supervisor))
+          AND ($min_year IS NULL OR d.year >= $min_year)
+          AND ($max_year IS NULL OR d.year <= $max_year)
+          AND ($min_words IS NULL OR d.words >= $min_words)
+          AND ($max_words IS NULL OR d.words <= $max_words)
+          AND ($min_date IS NULL OR d.load_date >= $min_date)
+          AND ($max_date IS NULL OR d.load_date <= $max_date)
+          AND ($chapters IS NULL OR ANY(chapter IN $chapters WHERE chapter IN chapters))
+        ORDER BY d.""" + order_by + """
path: src/repository/diploma_repo.py
user: login: Space27
body: не учел вариант, что сортировки может не быть
created_at: 2025-04-27T13:01:37Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,329 @@
+from neo4j import GraphDatabase
+from neo4j.exceptions import ServiceUnavailable
+from src.diploma_processing.data_types import Chapter, Diploma
+
+
+class Neo4jDatabase:
+    def __init__(self, uri, user, password):
+        """
+        Инициализация соединения с базой данных Neo4j.
+        """
+        self._driver = None
+        try:
+            self._driver = GraphDatabase.driver(uri, auth=(user, password))
+            # >>> ПРОВЕРКА ДОСТУПНОСТИ БАЗЫ <<<
+            with self._driver.session() as session:
+                session.run("RETURN 1")  # минимальный запрос
+            print("Подключение к базе данных успешно установлено.")
+        except ServiceUnavailable as e:
+            print(f"Не удалось подключиться к базе данных: {e}")
+            raise
+        except Exception as e:
+            print(f"Ошибка подключения к базе данных: {e}")
+            raise
+
+    def close(self):
+        """
+        Закрытие соединения с базой данных.
+        """
+        if self._driver:
+            try:
+                self._driver.close()
+                print("Соединение с базой данных успешно закрыто.")
+            except Exception as e:
+                print(f"Ошибка при закрытии соединения: {e}")
+
+    def query(self, query, parameters=None):
+        """
+        Выполнение Cypher-запроса.
+        :param query: Строка с Cypher-запросом.
+        :param parameters: Параметры для запроса (опционально).
+        :return: Результат выполнения запроса.
+        """
+        assert self._driver is not None, "Driver not initialized!"
+        session = None
+        response = None
+
+        try:
+            session = self._driver.session()
+            if parameters is None:
+                response = list(session.run(query))
+            else:
+                response = list(session.run(query, parameters))  # <-- сюда передать parameters
+            print("Запрос успешно выполнен.")
+        except Exception as e:
+            print(f"Ошибка при выполнении запроса: {e}")
+        finally:
+            if session is not None:
+                session.close()
+
+        return response
+
+
+class DiplomaRepository:
+    def __init__(self, database: Neo4jDatabase):
+        self.database = database
+
+    # works
+    def save_diploma(self, diploma: Diploma) -> int | None:
+        query = """
+        CREATE (d:Diploma { 
+            name: $name, 
+            author: $author, 
+            academic_supervisor: $academic_supervisor, 
+            year: $year, 
+            words: $words,
+            load_date: $load_date
+        })
+        SET d.id = ID(d)
+        RETURN d.id
+        """
+        parameters = {
+            "name": diploma.name,
+            "author": diploma.author,
+            "academic_supervisor": diploma.academic_supervisor,
+            "year": diploma.year,
+            "words": diploma.words,
+            "load_date": diploma.load_date.date()
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            diploma.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return diploma.id
+        return None
+
+    # works
+    def save_chapter(self, chapter: Chapter) -> int | None:
+        query = """
+        CREATE (c:Chapter {
+            id_diploma: $id_diploma, 
+            name: $name, 
+            water_content: $water_content, 
+            words: $words, 
+            symbols: $symbols, 
+            commonly_used_words: $commonly_used_words, 
+            commonly_used_words_amount: $commonly_used_words_amount
+        })
+        SET c.id = ID(c)
+        RETURN c.id
+        """
+        parameters = {
+            "id_diploma": chapter.id_diploma,
+            "name": chapter.name,
+            "water_content": chapter.water_content,
+            "words": chapter.words,
+            "symbols": chapter.symbols,
+            "commonly_used_words": chapter.commonly_used_words,
+            "commonly_used_words_amount": chapter.commonly_used_words_amount
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            chapter.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return chapter.id
+        return None
+
+    # works
+    def link_chapter_to_diploma(self, id_diploma: int) -> None:
+        query = """
+        MATCH (d:Diploma), (c:Chapter) 
+        WHERE d.id = $id_diploma AND c.id_diploma = $id_diploma
+        CREATE (d)-[:CONTAINS]->(c)
+        """
+        parameters = {"id_diploma": id_diploma}
+        self.database.query(query, parameters)
+
+    # works
+    def link_subchapters(self, parent_chapter_id: int, subchapter_ids: list[int]) -> None:
+        query = """
+        MATCH (c1:Chapter), (c2:Chapter) 
+        WHERE c1.id = $parent_chapter_id AND c2.id IN $subchapter_ids
+        CREATE (c1)-[:CONTAINS]->(c2)
+        """
+        parameters = {
+            "parent_chapter_id": parent_chapter_id,
+            "subchapter_ids": subchapter_ids
+        }
+        self.database.query(query, parameters)
+
+    # idk (no field 'shingles' in Diploma added yet)
+    def get_other_diplomas_shingles(self, id_diploma: int) -> list[tuple[int, list[int]]]:
+        query = """
+        MATCH (d:Diploma)
+        WHERE d.id <> $id_diploma
+        RETURN d.id, d.shingles
+        """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+        return [(int(record[0]), list(map(int, record[1]))) for record in result]
+
+    # works
+    def save_similarity(self, id_diploma: int, id_diploma_2: int, similarity: float) -> None:
+        query = """
+        MATCH (d1:Diploma), (d2:Diploma)
+        WHERE d1.id = $id_diploma AND d2.id = $id_diploma_2
+        CREATE (d1)-[:SIMILAR_TO {similarity: $similarity}]->(d2)
+        """
+        parameters = {
+            "id_diploma": id_diploma,
+            "id_diploma_2": id_diploma_2,
+            "similarity": similarity
+        }
+        self.database.query(query, parameters)
+
+    # works
+    def load_diploma_graph(self, id_diploma: int) -> list[list[int], list[list[int]]]:
+        query = """
+        MATCH p = (d:Diploma)-[r:CONTAINS*0..]->(x) 
+        WHERE d.id = $id_diploma
+        RETURN 
+            COLLECT(DISTINCT ID(x)) AS nodes,
+            [r IN COLLECT(DISTINCT LAST(r)) | [ID(startNode(r)), ID(endNode(r))]] AS rels
+        """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+        nodes, rels = result[0] if result else ([], [])
+        return [list(map(int, nodes)), rels]
+
+    # works
+    def load_diploma_data(self, id_diploma: int) -> Diploma | None:
+        query = """
+        MATCH (d:Diploma)
+        WHERE d.id = $id_diploma
+        RETURN d
+        """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+
+        if not result:
+            return None
+
+        node = result[0]["d"]
+
+        diploma = Diploma(
+            id=node.get("id"),
+            name=node.get("name", ""),
+            author=node.get("author", ""),
+            academic_supervisor=node.get("academic_supervisor"),
+            year=node.get("year", 0),
+            words=node.get("words", 0),
+            load_date=datetime.datetime(
+                node.get("load_date").year,
+                node.get("load_date").month,
+                node.get("load_date").day),
+            chapters=[]
+        )
+
+        return diploma
+
+    # works
+    def load_chapters(self, chapter_ids: list[int]) -> list[Chapter]:
+        query = """
+        MATCH (c:Chapter)
+        WHERE c.id IN $chapter_ids
+        RETURN c
+        """
+        parameters = {"chapter_ids": chapter_ids}
+        result = self.database.query(query, parameters)
+
+        chapters = []
+        for record in result:
+            node = record["c"]
+
+            chapter = Chapter(
+                id=node.get("id"),
+                id_diploma=node.get("id_diploma"),
+                name=node.get("name", ""),
+                water_content=node.get("water_content", 0),
+                words=node.get("words", 0),
+                symbols=node.get("symbols", 0),
+                commonly_used_words=node.get("commonly_used_words", []),
+                commonly_used_words_amount=node.get("commonly_used_words_amount", []),
+                chapters=[]
+            )
+
+            chapters.append(chapter)
+
+        return chapters
+
+    def search_diplomas(self,
+                        min_id: int = None, max_id: int = None,
+                        name: str = None, author: str = None, academic_supervisor: str = None,
+                        min_year: int = None, max_year: int = None,
+                        min_words: int = None, max_words: int = None,
+                        min_date: str = None, max_date: str = None,
+                        chapters: list[int] = None,
+                        order_by: str = "id", limit: int = 10, skip: int = 0) -> list[Diploma]:
+        query = """
+        MATCH (d:Diploma)-[:CONTAINS]->(c:Chapter)
+        WITH d, COLLECT(ID(c)) AS chapters
+        WHERE ($min_id IS NULL OR d.id >= $min_id)
+          AND ($max_id IS NULL OR d.id <= $max_id)
+          AND ($name IS NULL OR toLower(d.name) CONTAINS toLower($name))
+          AND ($author IS NULL OR toLower(d.author) CONTAINS toLower($author))
+          AND ($academic_supervisor IS NULL OR toLower(d.academic_supervisor) CONTAINS toLower($academic_supervisor))
+          AND ($min_year IS NULL OR d.year >= $min_year)
+          AND ($max_year IS NULL OR d.year <= $max_year)
+          AND ($min_words IS NULL OR d.words >= $min_words)
+          AND ($max_words IS NULL OR d.words <= $max_words)
+          AND ($min_date IS NULL OR d.load_date >= $min_date)
+          AND ($max_date IS NULL OR d.load_date <= $max_date)
+          AND ($chapters IS NULL OR ANY(chapter IN $chapters WHERE chapter IN chapters))
+        ORDER BY d.""" + order_by + """
+        LIMIT $limit SKIP $skip
path: src/repository/diploma_repo.py
user: login: Space27
body: давай пока без пагинации
created_at: 2025-04-27T13:01:48Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,329 @@
+from neo4j import GraphDatabase
+from neo4j.exceptions import ServiceUnavailable
+from src.diploma_processing.data_types import Chapter, Diploma
+
+
+class Neo4jDatabase:
+    def __init__(self, uri, user, password):
+        """
+        Инициализация соединения с базой данных Neo4j.
+        """
+        self._driver = None
+        try:
+            self._driver = GraphDatabase.driver(uri, auth=(user, password))
+            # >>> ПРОВЕРКА ДОСТУПНОСТИ БАЗЫ <<<
+            with self._driver.session() as session:
+                session.run("RETURN 1")  # минимальный запрос
+            print("Подключение к базе данных успешно установлено.")
+        except ServiceUnavailable as e:
+            print(f"Не удалось подключиться к базе данных: {e}")
+            raise
+        except Exception as e:
+            print(f"Ошибка подключения к базе данных: {e}")
+            raise
+
+    def close(self):
+        """
+        Закрытие соединения с базой данных.
+        """
+        if self._driver:
+            try:
+                self._driver.close()
+                print("Соединение с базой данных успешно закрыто.")
+            except Exception as e:
+                print(f"Ошибка при закрытии соединения: {e}")
+
+    def query(self, query, parameters=None):
+        """
+        Выполнение Cypher-запроса.
+        :param query: Строка с Cypher-запросом.
+        :param parameters: Параметры для запроса (опционально).
+        :return: Результат выполнения запроса.
+        """
+        assert self._driver is not None, "Driver not initialized!"
+        session = None
+        response = None
+
+        try:
+            session = self._driver.session()
+            if parameters is None:
+                response = list(session.run(query))
+            else:
+                response = list(session.run(query, parameters))  # <-- сюда передать parameters
+            print("Запрос успешно выполнен.")
+        except Exception as e:
+            print(f"Ошибка при выполнении запроса: {e}")
+        finally:
+            if session is not None:
+                session.close()
+
+        return response
+
+
+class DiplomaRepository:
+    def __init__(self, database: Neo4jDatabase):
+        self.database = database
+
+    # works
+    def save_diploma(self, diploma: Diploma) -> int | None:
+        query = """
+        CREATE (d:Diploma { 
+            name: $name, 
+            author: $author, 
+            academic_supervisor: $academic_supervisor, 
+            year: $year, 
+            words: $words,
+            load_date: $load_date
+        })
+        SET d.id = ID(d)
+        RETURN d.id
+        """
+        parameters = {
+            "name": diploma.name,
+            "author": diploma.author,
+            "academic_supervisor": diploma.academic_supervisor,
+            "year": diploma.year,
+            "words": diploma.words,
+            "load_date": diploma.load_date.date()
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            diploma.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return diploma.id
+        return None
+
+    # works
+    def save_chapter(self, chapter: Chapter) -> int | None:
+        query = """
+        CREATE (c:Chapter {
+            id_diploma: $id_diploma, 
+            name: $name, 
+            water_content: $water_content, 
+            words: $words, 
+            symbols: $symbols, 
+            commonly_used_words: $commonly_used_words, 
+            commonly_used_words_amount: $commonly_used_words_amount
+        })
+        SET c.id = ID(c)
+        RETURN c.id
+        """
+        parameters = {
+            "id_diploma": chapter.id_diploma,
+            "name": chapter.name,
+            "water_content": chapter.water_content,
+            "words": chapter.words,
+            "symbols": chapter.symbols,
+            "commonly_used_words": chapter.commonly_used_words,
+            "commonly_used_words_amount": chapter.commonly_used_words_amount
+        }
+        result = self.database.query(query, parameters)
+        if result:
+            chapter.id = result[0][0]  # Теперь id будет и в объекте, и в БД в поле id
+            return chapter.id
+        return None
+
+    # works
+    def link_chapter_to_diploma(self, id_diploma: int) -> None:
+        query = """
+        MATCH (d:Diploma), (c:Chapter) 
+        WHERE d.id = $id_diploma AND c.id_diploma = $id_diploma
+        CREATE (d)-[:CONTAINS]->(c)
+        """
+        parameters = {"id_diploma": id_diploma}
+        self.database.query(query, parameters)
+
+    # works
+    def link_subchapters(self, parent_chapter_id: int, subchapter_ids: list[int]) -> None:
+        query = """
+        MATCH (c1:Chapter), (c2:Chapter) 
+        WHERE c1.id = $parent_chapter_id AND c2.id IN $subchapter_ids
+        CREATE (c1)-[:CONTAINS]->(c2)
+        """
+        parameters = {
+            "parent_chapter_id": parent_chapter_id,
+            "subchapter_ids": subchapter_ids
+        }
+        self.database.query(query, parameters)
+
+    # idk (no field 'shingles' in Diploma added yet)
+    def get_other_diplomas_shingles(self, id_diploma: int) -> list[tuple[int, list[int]]]:
+        query = """
+        MATCH (d:Diploma)
+        WHERE d.id <> $id_diploma
+        RETURN d.id, d.shingles
+        """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+        return [(int(record[0]), list(map(int, record[1]))) for record in result]
+
+    # works
+    def save_similarity(self, id_diploma: int, id_diploma_2: int, similarity: float) -> None:
+        query = """
+        MATCH (d1:Diploma), (d2:Diploma)
+        WHERE d1.id = $id_diploma AND d2.id = $id_diploma_2
+        CREATE (d1)-[:SIMILAR_TO {similarity: $similarity}]->(d2)
+        """
+        parameters = {
+            "id_diploma": id_diploma,
+            "id_diploma_2": id_diploma_2,
+            "similarity": similarity
+        }
+        self.database.query(query, parameters)
+
+    # works
+    def load_diploma_graph(self, id_diploma: int) -> list[list[int], list[list[int]]]:
+        query = """
+        MATCH p = (d:Diploma)-[r:CONTAINS*0..]->(x) 
+        WHERE d.id = $id_diploma
+        RETURN 
+            COLLECT(DISTINCT ID(x)) AS nodes,
+            [r IN COLLECT(DISTINCT LAST(r)) | [ID(startNode(r)), ID(endNode(r))]] AS rels
+        """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+        nodes, rels = result[0] if result else ([], [])
+        return [list(map(int, nodes)), rels]
+
+    # works
+    def load_diploma_data(self, id_diploma: int) -> Diploma | None:
+        query = """
+        MATCH (d:Diploma)
+        WHERE d.id = $id_diploma
+        RETURN d
+        """
+        parameters = {"id_diploma": id_diploma}
+        result = self.database.query(query, parameters)
+
+        if not result:
+            return None
+
+        node = result[0]["d"]
+
+        diploma = Diploma(
+            id=node.get("id"),
+            name=node.get("name", ""),
+            author=node.get("author", ""),
+            academic_supervisor=node.get("academic_supervisor"),
+            year=node.get("year", 0),
+            words=node.get("words", 0),
+            load_date=datetime.datetime(
+                node.get("load_date").year,
+                node.get("load_date").month,
+                node.get("load_date").day),
+            chapters=[]
+        )
+
+        return diploma
+
+    # works
+    def load_chapters(self, chapter_ids: list[int]) -> list[Chapter]:
+        query = """
+        MATCH (c:Chapter)
+        WHERE c.id IN $chapter_ids
+        RETURN c
+        """
+        parameters = {"chapter_ids": chapter_ids}
+        result = self.database.query(query, parameters)
+
+        chapters = []
+        for record in result:
+            node = record["c"]
+
+            chapter = Chapter(
+                id=node.get("id"),
+                id_diploma=node.get("id_diploma"),
+                name=node.get("name", ""),
+                water_content=node.get("water_content", 0),
+                words=node.get("words", 0),
+                symbols=node.get("symbols", 0),
+                commonly_used_words=node.get("commonly_used_words", []),
+                commonly_used_words_amount=node.get("commonly_used_words_amount", []),
+                chapters=[]
+            )
+
+            chapters.append(chapter)
+
+        return chapters
+
+    def search_diplomas(self,
+                        min_id: int = None, max_id: int = None,
+                        name: str = None, author: str = None, academic_supervisor: str = None,
+                        min_year: int = None, max_year: int = None,
+                        min_words: int = None, max_words: int = None,
+                        min_date: str = None, max_date: str = None,
+                        chapters: list[int] = None,
+                        order_by: str = "id", limit: int = 10, skip: int = 0) -> list[Diploma]:
+        query = """
+        MATCH (d:Diploma)-[:CONTAINS]->(c:Chapter)
+        WITH d, COLLECT(ID(c)) AS chapters
+        WHERE ($min_id IS NULL OR d.id >= $min_id)
+          AND ($max_id IS NULL OR d.id <= $max_id)
+          AND ($name IS NULL OR toLower(d.name) CONTAINS toLower($name))
+          AND ($author IS NULL OR toLower(d.author) CONTAINS toLower($author))
+          AND ($academic_supervisor IS NULL OR toLower(d.academic_supervisor) CONTAINS toLower($academic_supervisor))
+          AND ($min_year IS NULL OR d.year >= $min_year)
+          AND ($max_year IS NULL OR d.year <= $max_year)
+          AND ($min_words IS NULL OR d.words >= $min_words)
+          AND ($max_words IS NULL OR d.words <= $max_words)
+          AND ($min_date IS NULL OR d.load_date >= $min_date)
+          AND ($max_date IS NULL OR d.load_date <= $max_date)
+          AND ($chapters IS NULL OR ANY(chapter IN $chapters WHERE chapter IN chapters))
+        ORDER BY d.""" + order_by + """
+        LIMIT $limit SKIP $skip
+        RETURN d{.*, chapters: chapters} AS diploma
+        """
+        parameters = {
+            "min_id": min_id, "max_id": max_id,
+            "name": name, "author": author, "academic_supervisor": academic_supervisor,
+            "min_year": min_year, "max_year": max_year,
+            "min_words": min_words, "max_words": max_words,
+            "min_date": min_date, "max_date": max_date,
+            "chapters": chapters,
+            "limit": limit, "skip": skip
+        }
+        result = self.database.query(query, parameters)
+        return [record["diploma"] for record in result] if result else []
+
+    def search_chapters(self,
+                        min_id: int = None, max_id: int = None,
+                        min_id_diploma: int = None, max_id_diploma: int = None,
+                        name: str = None,
+                        min_words: int = None, max_words: int = None,
+                        min_symbols: int = None, max_symbols: int = None,
+                        min_water_content: float = None, max_water_content: float = None,
+                        words: list[str] = None,
+                        chapters: list[int] = None,
+                        order_by: str = "id", limit: int = 10, skip: int = 0) -> list[Chapter]:
+        query = """
+        MATCH (c:Chapter)-[:CONTAINS*0..]->(c1:Chapter)
+        WITH c, COLLECT(ID(c1)) AS chapters
+        WHERE ($min_id IS NULL OR c.id >= $min_id)
+          AND ($max_id IS NULL OR c.id <= $max_id)
+          AND ($min_id_diploma IS NULL OR c.id_diploma >= $min_id_diploma)
+          AND ($max_id_diploma IS NULL OR c.id_diploma <= $max_id_diploma)
+          AND ($name IS NULL OR toLower(c.name) CONTAINS toLower($name))
+          AND ($min_words IS NULL OR c.words >= $min_words)
+          AND ($max_words IS NULL OR c.words <= $max_words)
+          AND ($min_symbols IS NULL OR c.symbols >= $min_symbols)
+          AND ($max_symbols IS NULL OR c.symbols <= $max_symbols)
+          AND ($min_water_content IS NULL OR c.water_content >= $min_water_content)
+          AND ($max_water_content IS NULL OR c.water_content <= $max_water_content)
+          AND ($words IS NULL OR ANY(word IN $words WHERE word IN c.commonly_used_words))
+          AND ($chapters IS NULL OR ANY(chapter IN $chapters WHERE chapter IN chapters))
+        ORDER BY c.""" + order_by + """
+        LIMIT $limit SKIP $skip
path: src/repository/diploma_repo.py
user: login: Space27
body: Тут тоже нужно учесть вариант без сортировки и убрать пагинацию
created_at: 2025-04-27T13:03:07Z
author_association: COLLABORATOR

PR comments:
user: login: vRedniy-LumbeRt
created_at: 2025-04-27T12:54:16Z
body: пока вложенности в загрузках нет, буду шаманить с графом
даты поправил

сейчас хочу разобраться с фильтрацией, а точнее -- с возвращаемыми типами (пака там тоже словари)
author_association: COLLABORATOR
user: login: vRedniy-LumbeRt
created_at: 2025-04-27T16:16:33Z
body: рекурсию сделал таки
теперь поправлю сортировки

есть ненужные поля в коммите, уберу
author_association: COLLABORATOR
user: login: vRedniy-LumbeRt
created_at: 2025-04-27T16:56:58Z
body: сортировку поправил
как выяснилось, рекурсия неверна, так что структура диплома и разделов с тебя (можно просто откатить на 2 коммита назад изменения)

author_association: COLLABORATOR