commit 2e79b9766284e411cd1f16726d736b79d6b17211 Author: G0DSEND016 Date: Fri Dec 12 08:41:11 2025 +0100 Initial commit diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/ai-lawyer-agent.iml b/.idea/ai-lawyer-agent.iml new file mode 100644 index 0000000..7d67021 --- /dev/null +++ b/.idea/ai-lawyer-agent.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..06bb031 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..8b05534 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..01b20db --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.streamlit/config.toml b/.streamlit/config.toml new file mode 100644 index 0000000..b5fa8c9 --- /dev/null +++ b/.streamlit/config.toml @@ -0,0 +1,18 @@ + +[browser] +serverAddress = "localhost" +gatherUsageStats = false +serverPort = 8501 + +[theme] +base = "light" +backgroundColor = "#E6F4FF" +secondaryBackgroundColor = "#D6EDFF" + +font = "Ubuntu, sans-serif" +baseFontSize = 16 +baseFontWeight = 300 +headingFont = "Ubuntu, sans-serif" + +baseRadius = "small" +#borderColor = "#D6EDFF" diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/__pycache__/__init__.cpython-313.pyc b/app/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..11894a3 Binary files /dev/null and b/app/__pycache__/__init__.cpython-313.pyc differ diff --git a/app/__pycache__/app.cpython-313.pyc b/app/__pycache__/app.cpython-313.pyc new file mode 100644 index 0000000..97a8972 Binary files /dev/null and b/app/__pycache__/app.cpython-313.pyc differ diff --git a/app/app.py b/app/app.py new file mode 100644 index 0000000..446febd --- /dev/null +++ b/app/app.py @@ -0,0 +1,93 @@ +import asyncio +from datetime import datetime +import streamlit as st +from core.model import assistant_agent, SQLiteSession +from app.components.sidebar import add_sidebar + +style_chat_message = """ + +""" + +def get_time() -> str: + return datetime.now().strftime("%d.%m.%Y %H:%M:%S") + +def init_session_state() -> None: + if "messages" not in st.session_state: + st.session_state.messages = [] + if "chat_session" not in st.session_state: + st.session_state.chat_session = SQLiteSession(":memory:") + if "show_about" not in st.session_state: + st.session_state.show_about = True + + st.markdown(style_chat_message, unsafe_allow_html=True) + +def create_app() -> None: + + st.set_page_config( + page_title="LawGPT", + page_icon="app/assets/images/title.png", + initial_sidebar_state="collapsed", + layout="centered", + menu_items={ + 'Get help': None, + 'Report a bug': None, + 'About': """ + This is a cool educational project exploring the creation of an AI agent powered by API keys. + You can learn how to build, interact with, and experiment with AI using real API integration. + """ + } + ) + + add_sidebar() + init_session_state() + + for message in st.session_state.messages: + with st.chat_message(message["role"], avatar=message["avatar"]): + st.markdown(message["content"]) + if "time" in message: + st.caption(message["time"]) + + user_avatar = "app/assets/images/user.png" + assistant_avatar = "app/assets/images/assistant.png" + + if request := st.chat_input("Ask anything"): + user_time = get_time() + + with st.chat_message(name="user", avatar=user_avatar): + st.markdown(f"{request}") + st.caption(user_time) + + user_message = {"role": "user", + "avatar": user_avatar, + "content": request, + "time": user_time} + st.session_state.messages.append(user_message) + + with st.chat_message(name="assistant", avatar=assistant_avatar): + with st.spinner("Thinking..."): + try: + response = st.write_stream(assistant_agent(request, st.session_state.chat_session)) + except Exception as e: + response = f"⚠️ Error: {e}" + finally: + assistant_time = get_time() + + st.caption(assistant_time) + + assistant_message = {"role": "assistant", + "avatar": assistant_avatar, + "content": response, + "time": assistant_time} + st.session_state.messages.append(assistant_message) + + st.rerun() \ No newline at end of file diff --git a/app/assets/images/assistant.png b/app/assets/images/assistant.png new file mode 100644 index 0000000..9768b79 Binary files /dev/null and b/app/assets/images/assistant.png differ diff --git a/app/assets/images/title.png b/app/assets/images/title.png new file mode 100644 index 0000000..895dc45 Binary files /dev/null and b/app/assets/images/title.png differ diff --git a/app/assets/images/user.png b/app/assets/images/user.png new file mode 100644 index 0000000..fe3d48e Binary files /dev/null and b/app/assets/images/user.png differ diff --git a/app/components/__init__.py b/app/components/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/components/__pycache__/__init__.cpython-313.pyc b/app/components/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..8d9e969 Binary files /dev/null and b/app/components/__pycache__/__init__.cpython-313.pyc differ diff --git a/app/components/__pycache__/sidebar.cpython-313.pyc b/app/components/__pycache__/sidebar.cpython-313.pyc new file mode 100644 index 0000000..cf4d54e Binary files /dev/null and b/app/components/__pycache__/sidebar.cpython-313.pyc differ diff --git a/app/components/sidebar.py b/app/components/sidebar.py new file mode 100644 index 0000000..a776852 --- /dev/null +++ b/app/components/sidebar.py @@ -0,0 +1,32 @@ +import streamlit as st +from agents import SQLiteSession + +sidebar_style = """ + +""" + +def add_sidebar(): + st.markdown(sidebar_style, unsafe_allow_html=True) + + with st.sidebar: + + st.title("⚖️ LawGPT") + st.markdown(":blue-badge[⚙️Tool] :orange-badge[⚠️Current chat will be deleted]") + if st.button(":material/note_stack_add: Create new chat"): + st.session_state.messages = [] + st.session_state.chat_session = SQLiteSession(":memory:") diff --git a/core/.env b/core/.env new file mode 100644 index 0000000..da67259 --- /dev/null +++ b/core/.env @@ -0,0 +1 @@ +OPENAI_API_KEY="" \ No newline at end of file diff --git a/core/__init__.py b/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/__pycache__/__init__.cpython-313.pyc b/core/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..da1bef2 Binary files /dev/null and b/core/__pycache__/__init__.cpython-313.pyc differ diff --git a/core/__pycache__/model.cpython-313.pyc b/core/__pycache__/model.cpython-313.pyc new file mode 100644 index 0000000..b3a371b Binary files /dev/null and b/core/__pycache__/model.cpython-313.pyc differ diff --git a/core/model.py b/core/model.py new file mode 100644 index 0000000..bae1d87 --- /dev/null +++ b/core/model.py @@ -0,0 +1,116 @@ +from openai.types.responses import ResponseTextDeltaEvent +from agents import Agent, Runner, SQLiteSession +from agents import OpenAIChatCompletionsModel, AsyncOpenAI + +from core.tools.zmluvy import ContractsAPI +from core.tools.sud import CourtAPI +from core.tools.sudca import JudgeAPI +from core.tools.rozhodnutie import DecisionAPI +from core.tools.obcianPojednavania import CivilProceedingAPI +from core.tools.spravneKonanie import AdminProceedAPI + + +SYSTEM_PROMPT = """ + # Overview + Tento systémový prompt definuje právneho AI agenta, ktorý pracuje výhradne s integrovanými + nástrojmi API Ministerstva spravodlivosti SR. Cieľom je poskytovať presné, overené a stručné + právne informácie bez nadbytočných údajov. + + # Context + - Agent pracuje len s oficiálnymi verejnými databázami SR. + - Nepoužíva internet ani vlastné domnienky. + - V odpovediach uvádza zdroj: API Ministerstva spravodlivosti SR. + - Pri nejasných dopytoch žiada o spresnenie. + - Rešpektuje GDPR a neposkytuje právne rady, iba fakty. + + # Instructions + 1. Všetky dotazy spracúvaj výhradne cez dostupné nástroje. + 2. Pri zoznamoch vždy najprv použi autocomplete nástroj. + 3. Pri detailoch používaj nástroje typu `*_id` s konkrétnym identifikátorom. + 4. Dátumy vracaj vo formáte DD.MM.RRRR. + 5. Pri filtroch používaj hodnoty oddelené čiarkou bez medzier. + 6. Pri veľkých datasetoch používaj parametre page a size. + 7. Odpovede formuluj stručne, právne presne a výhradne v slovenskom jazyku. + 8. Ak nástroj nevráti výsledok, používateľovi to jasne oznám. + + # Tools + - SÚDY: about_courts, get_court, court_autocomplete + - ZMLUVY SÚDOV: about_contracts, get_contract, contracts_autocomplete + - SUDCOVIA: about_judge, judge_id, judge_autocomplete + - ROZHODNUTIA: about_decision, decision_id, decision_autocomplete + - OBČIANSKE POJEDNÁVANIA: about_civil_proceeding, civil_proceeding_id, civil_proceeding_autocomplete + - SPRÁVNE KONANIA: about_admin_proceed, admin_proceed_id, admin_proceed_autocomplete, admin_proceed_attachments + + # Examples + - Vstup: „Vyhľadaj Okresný súd v Trnave.“ + - Postup: použije sa court_autocomplete → get_court. + - Výstup: stručné potvrdenie s detailmi a zdrojom. + + - Vstup: „Nájdi rozhodnutie so spisovou značkou XY.“ + - Postup: decision_autocomplete → decision_id. + + # SOP (Standard Operating Procedure) + 1. Identifikuj typ požiadavky (zoznam, detail, vyhľadávanie). + 2. Použi príslušný autocomplete na zistenie identifikátorov. + 3. Zavolaj detailný nástroj `*_id`. + 4. Spracuj odpoveď a zhrň ju do stručného a presného výstupu. + 5. Uveď zdroj dát. + 6. Ak výsledok chýba, oznám to a navrhni ďalší krok (overenie názvu, filtra, dátumu). + + # Final Notes + - Agent odpovedá vždy len fakticky na základe API. + - Neposkytuje interpretácie ani právne rady. + - Výstup musí byť vecný, prehľadný a stredne dlhý. + +""" + +async def stream_response(agent: Agent, prompt: str, session: SQLiteSession): + """Stream agent response and update the UI.""" + + try: + result = Runner.run_streamed(agent, input=prompt, session=session) + async for event in result.stream_events(): + if event.type == "raw_response_event" and isinstance(event.data, ResponseTextDeltaEvent): + yield event.data.delta # <-- sends the next piece of response text + except Exception as e: + yield f"⚠️ Error: {e}" + + +def assistant_agent(prompt: str, session): + + court_api = CourtAPI() + contracts_api = ContractsAPI() + judge_api = JudgeAPI() + decision_api = DecisionAPI() + civil_proceeding_api = CivilProceedingAPI() + admin_proceeding_api = AdminProceedAPI() + + model = OpenAIChatCompletionsModel( + model="gpt-oss:20b-cloud", + openai_client=AsyncOpenAI(base_url="http://localhost:11434/v1", + api_key="ollama" + ) + ) + + agent = Agent( + name="Assistant", + instructions=SYSTEM_PROMPT, + model=model, + tools=[ + court_api.about_courts, court_api.get_court, court_api.court_autocomplete, + + contracts_api.get_contract, contracts_api.about_contracts, contracts_api.contracts_autocomplete, + + judge_api.about_judge, judge_api.judge_id, judge_api.judge_autocomplete, + + decision_api.about_decision, decision_api.decision_id, decision_api.decision_autocomplete, + + civil_proceeding_api.about_civil_proceeding, civil_proceeding_api.civil_proceeding_id, + civil_proceeding_api.civil_proceeding_autocomplete, + + admin_proceeding_api.about_admin_proceed, admin_proceeding_api.admin_proceed_id, + admin_proceeding_api.admin_proceed_autocomplete, admin_proceeding_api.admin_proceed_attachments, + ] + ) + + return stream_response(agent, prompt, session) \ No newline at end of file diff --git a/core/tools/__init__.py b/core/tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/tools/__pycache__/__init__.cpython-313.pyc b/core/tools/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..bd0940d Binary files /dev/null and b/core/tools/__pycache__/__init__.cpython-313.pyc differ diff --git a/core/tools/__pycache__/obcianPojednavania.cpython-313.pyc b/core/tools/__pycache__/obcianPojednavania.cpython-313.pyc new file mode 100644 index 0000000..e4cb7e5 Binary files /dev/null and b/core/tools/__pycache__/obcianPojednavania.cpython-313.pyc differ diff --git a/core/tools/__pycache__/rozhodnutie.cpython-313.pyc b/core/tools/__pycache__/rozhodnutie.cpython-313.pyc new file mode 100644 index 0000000..c562657 Binary files /dev/null and b/core/tools/__pycache__/rozhodnutie.cpython-313.pyc differ diff --git a/core/tools/__pycache__/spravneKonanie.cpython-313.pyc b/core/tools/__pycache__/spravneKonanie.cpython-313.pyc new file mode 100644 index 0000000..bc4c9fe Binary files /dev/null and b/core/tools/__pycache__/spravneKonanie.cpython-313.pyc differ diff --git a/core/tools/__pycache__/sud.cpython-313.pyc b/core/tools/__pycache__/sud.cpython-313.pyc new file mode 100644 index 0000000..457af3b Binary files /dev/null and b/core/tools/__pycache__/sud.cpython-313.pyc differ diff --git a/core/tools/__pycache__/sudca.cpython-313.pyc b/core/tools/__pycache__/sudca.cpython-313.pyc new file mode 100644 index 0000000..5885e63 Binary files /dev/null and b/core/tools/__pycache__/sudca.cpython-313.pyc differ diff --git a/core/tools/__pycache__/zmluvy.cpython-313.pyc b/core/tools/__pycache__/zmluvy.cpython-313.pyc new file mode 100644 index 0000000..a26de07 Binary files /dev/null and b/core/tools/__pycache__/zmluvy.cpython-313.pyc differ diff --git a/core/tools/obcianPojednavania.py b/core/tools/obcianPojednavania.py new file mode 100644 index 0000000..ddb8a64 --- /dev/null +++ b/core/tools/obcianPojednavania.py @@ -0,0 +1,137 @@ +import requests +from pydantic import BaseModel, conint +from typing import Optional, List +from enum import Enum + +from agents import function_tool + +API_BASE_URL = "https://obcan.justice.sk/pilot/api/ress-isu-service" + +class SortDirection(str, Enum): + ASC = "ASC" + DESC = "DESC" + +class CivilProceedingAPI: + + class CivilProceedParams(BaseModel): + query: Optional[str] = None + typSuduFacetFilter: Optional[List[str]] = None + krajFacetFilter: Optional[List[str]] = None + okresFacetFilter: Optional[List[str]] = None + usekFacetFilter: Optional[List[str]] = None + formaUkonuFacetFilter: Optional[List[str]] = None + pojednavaniaOd: Optional[str] = None + pojednavaniaDo: Optional[str] = None + guidSudca: Optional[str] = None + guidSud: Optional[str] = None + spisovaZnacka: Optional[str] = None + verejneVyhlasenie: Optional[bool] = None + indexDatumOd: Optional[str] = None + indexDatumDo: Optional[str] = None + sortProperty: Optional[str] = None + sortDirection: SortDirection = SortDirection.ASC + page: int = 0 + size: int = 20 + + @function_tool + def about_civil_proceeding(args: CivilProceedParams) -> dict: + """ + Načítanie zoznamu občianskoprávnych pojednávaní a verejne vyhlásených rozsudkov na základe filtrovacích kritérií + + :param query: Hľadané slovo alebo slovné spojenie, podľa ktorého sa vyhľadávajú občianskoprávne pojednávania. + :param typSuduFacetFilter: Zoznam typov súdov (fazetový filter), napr. ["Okresný súd", "Krajský súd", "Mestský súd"]. + :param krajFacetFilter: Zoznam krajov (fazetový filter), napr. ["Bratislavský kraj", "Košický kraj", "Prešovský kraj"]. + :param okresFacetFilter: Zoznam okresov (fazetový filter), napr. ["Okres Košice I", "Okres Bratislava IV", "Okres Žilina"]. + :param usekFacetFilter: Zoznam úsekov (fazetový filter), napr. ["C", "O", "S"]. + :param formaUkonuFacetFilter: Zoznam foriem úkonu (fazetový filter), napr. ["Pojednávanie bez rozhodnutia", "Pojednávanie a rozhodnutie", + "Verejné vyhlásenie rozsudku"]. + :param pojednavaniaOd: Dátum pojednávania OD (formát: DD.MM.RRRR), napr. "01.01.2025". + :param pojednavaniaDo: Dátum pojednávania DO (formát: DD.MM.RRRR), napr. "31.12.2025". + :param guidSudca: Identifikátor sudcu pre filtrovanie pojednávaní konkrétneho sudcu, napr. "sudca_2442". + :param guidSud: Identifikátor súdu pre filtrovanie pojednávaní konkrétneho súdu, napr. "sud_135". + :param spisovaZnacka: Spisová značka pojednávania, napr. "30P/1/2025". + :param verejneVyhlasenie: Príznak, či zahrnúť len verejne vyhlásené rozsudky. Hodnota typu boolean. + :param indexDatumOd: Dátum indexácie OD (formát: DD.MM.RRRR), napr. "01.01.2025". + :param indexDatumDo: Dátum indexácie DO (formát: DD.MM.RRRR), napr. "31.12.2025". + :param sortProperty: Názov atribútu, podľa ktorého sa majú záznamy zoradiť, napr. "datumPojednavania", "spisovaZnacka". + :param sortDirection: Smer zoradenia záznamov. Možnosti: "ASC" (predvolené) alebo "DESC". + :param page: Číslo stránky (0 = prvá stránka) pre stránkovanie výsledkov. + :param size: Počet záznamov na stránku (predvolené 20) pre stránkovanie výsledkov. + + :return: Slovník (JSON) obsahujúci zoznam občianskoprávnych pojednávaní a verejne vyhlásených rozsudkov s detailnými informáciami, metadátami a filtrami. + """ + + try: + url = f"{API_BASE_URL}/v1/obcianPojednavania" + response = requests.get(url, params=args.model_dump()) + response.raise_for_status() + + if response.status_code == 200: + data = response.json() + print(f"❗🔨\n {data}") + return {"success": True, "data": data} + else: + return {"success": False, "error": f"Unexpected status code: {response.status_code} ⚠️"} + + except Exception as e: + return {"success": False, "error": f"Error get contract details: {str(e)} 🛑"} + + @function_tool + def civil_proceeding_id(self, id: str) -> dict: + """ + Načítanie detailu občianskoprávneho pojednávania na základe identifikátora. + + :param id: Identifikátor sudcu (povinný parameter), napr. "sudca_123", "sudca_456". + + :return: Slovník (JSON) obsahujúci detailné informácie o občianskoprávnom pojednávaní na základe identifikátora. + """ + try: + url = f"{API_BASE_URL}/v1/obcianPojednavania/{id}" + response = requests.get(url) + response.raise_for_status() + + if response.status_code == 200: + data = response.json() + print(f"⭕🔨\n {data}") + return {"success": True, "data": data} + else: + return {"success": False, "error": f"Unexpected status code: {response.status_code} ⚠️"} + + except Exception as e: + return {"success": False, "error": f"Error : {str(e)} 🛑"} + + class CivilProceedAutocomplete(BaseModel): + query: Optional[str] = None + guidSud: Optional[str] = None + giudSud: Optional[str] = None + verejneVyhlasenie: Optional[bool] = None + limit: Optional[conint(ge=0)] = None + + @function_tool + def civil_proceeding_autocomplete(self, args: CivilProceedAutocomplete) -> dict: + """ + Autocomplete služba pre vyhľadávanie občianskoprávnych pojednávaní a verejne vyhlásených rozsudkov. + + :param query: Hľadané slovo alebo slovné spojenie pre vyhľadávanie pojednávaní. + :param guidSud: Identifikátor súdu pre filtrovanie pojednávaní konkrétneho súdu. + :param giudSud: Duplicitný parameter - pravdepodobne chyba, použite guidSud. + :param verejneVyhlasenie: Príznak, či zahrnúť len verejne vyhlásené rozsudky. + :param limit: Maximálny počet návrhov (0 = všetky možné návrhy). + + :return: Slovník (JSON) obsahujúci zoznam návrhov pre autocomplete. + """ + + try: + url = f"{API_BASE_URL}/v1/obcianPojednavania/autocomplete" + response = requests.get(url, args.model_dump()) + response.raise_for_status() + + if response.status_code == 200: + data = response.json() + print(f"📛🔨\n {data}") + return {"success": True, "data": data} + else: + return {"success": False, "error": f"Unexpected status code: {response.status_code} ⚠️"} + + except Exception as e: + return {"success": False, "error": f"Error : {str(e)} 🛑"} \ No newline at end of file diff --git a/core/tools/rozhodnutie.py b/core/tools/rozhodnutie.py new file mode 100644 index 0000000..cf05090 --- /dev/null +++ b/core/tools/rozhodnutie.py @@ -0,0 +1,141 @@ +import requests +from pydantic import BaseModel, conint +from typing import Optional, List +from enum import Enum + +from agents import function_tool + +API_BASE_URL = "https://obcan.justice.sk/pilot/api/ress-isu-service" + +class SortDirection(str, Enum): + ASC = "ASC" + DESC = "DESC" + +class DecisionAPI: + + class AboutDecision(BaseModel): + query: Optional[str] = None + typSuduFacetFilter: Optional[List[str]] = None + krajFacetFilter: Optional[List[str]] = None + okresFacetFilter: Optional[List[str]] = None + odkazovanePredpisy: Optional[str] = None + oblastPravnejUpravyFacetFilter: Optional[List[str]] = None + podOblastPravnejUpravyFacetFilter: Optional[List[str]] = None + formaRozhodnutiaFacetFilter: Optional[List[str]] = None + povahaRozhodnutiaFacetFilter: Optional[str] = None + vydaniaOd: Optional[str] = None + vydaniaDo: Optional[str] = None + ecli: Optional[str] = None + spisovaZnacka: Optional[str] = None + cisloSpisu: Optional[str] = None + guidSudca: Optional[str] = None + guidSud: Optional[str] = None + indexDatumOd: Optional[str] = None + indexDatumDo: Optional[str] = None + sortProperty: Optional[str] = None + sortDirection: SortDirection = SortDirection.ASC + page: Optional[int] = None + size : conint(ge=0) = 20 + + @function_tool + def about_decision(self, args: AboutDecision) -> dict: + """ + Načítanie zoznamu rozhodnutí na základe filtrovacích kritérií + + :param query: Hľadané slovo alebo slovné spojenie, podľa ktorého sa vyhľadávajú rozhodnutia. + :param typSuduFacetFilter: Zoznam typov súdov (fazetový filter), napr. ["Okresný súd", "Krajský súd", "Mestský súd"]. + :param krajFacetFilter: Zoznam krajov (fazetový filter), napr. ["Košický kraj", "Bratislavský kraj", "Banskobystrický kraj"]. + :param okresFacetFilter: Zoznam okresov (fazetový filter), napr. ["Okres Banská Bystrica", "Okres Košice I", "Okres Bratislava I"]. + :param odkazovanePredpisy: Odkazované predpisy, na ktoré sa rozhodnutie odvoláva. + :param oblastPravnejUpravyFacetFilter: Zoznam oblastí právnej úpravy (fazetový filter), napr. ["Občianske právo", + "Rodinné právo", "Obchodné právo"]. + :param podOblastPravnejUpravyFacetFilter: Zoznam podoblastí právnej úpravy + (fazetový filter), napr. ["Exekúcia a výkon rozhodnutí", + "Spotrebiteľské zmluvy", "Vyživovacie povinnosti"]. + :param formaRozhodnutiaFacetFilter: Zoznam foriem rozhodnutia (fazetový filter), napr. ["Uznesenie", "Rozsudok", "Platobný rozkaz"]. + :param povahaRozhodnutiaFacetFilter: Povaha rozhodnutia, napr. "Prvostupňové nenapadnuté opravnými prostriedkami". + :param vydaniaOd: Dátum vydania OD (formát: DD.MM.RRRR), napr. "01.01.2020". + :param vydaniaDo: Dátum vydania DO (formát: DD.MM.RRRR), napr. "31.12.2025". + :param ecli: ECLI identifikátor rozhodnutia. + :param spisovaZnacka: Spisová značka rozhodnutia, napr. "4T/20/2014". + :param cisloSpisu: Identifikačné číslo spisu. + :param guidSudca: Identifikátor sudcu pre filtrovanie rozhodnutí konkrétneho sudcu, napr. "sudca_1600". + :param guidSud: Identifikátor súdu pre filtrovanie rozhodnutí konkrétneho súdu, napr. "sud_156". + :param indexDatumOd: Dátum indexácie OD (formát: DD.MM.RRRR), napr. "01.01.2025". + :param indexDatumDo: Dátum indexácie DO (formát: DD.MM.RRRR), napr. "31.12.2025". + :param sortProperty: Názov atribútu, podľa ktorého sa majú záznamy zoradiť, napr. "datumVydania", "spisovaZnacka". + :param sortDirection: Smer zoradenia záznamov. Možnosti: "ASC" (predvolené) alebo "DESC". + :param page: Číslo stránky (0 = prvá stránka) pre stránkovanie výsledkov. + :param size: Počet záznamov na stránku (predvolené 20) pre stránkovanie výsledkov. + + :return: Slovník (JSON) obsahujúci zoznam rozhodnutí s detailnými informáciami, metadátami a filtrami. + """ + try: + url = f"{API_BASE_URL}/v1/rozhodnutie" + response = requests.get(url, params=args.model_dump()) + response.raise_for_status() + + if response.status_code == 200: + data = response.json() + print(f"⭕📃\n {data}") + return {"success": True, "data": data} + else: + return {"success": False, "error": f"Unexpected status code: {response.status_code} ⚠️"} + + except Exception as e: + return {"success": False, "error": f"Error : {str(e)} 🛑"} + + @function_tool + def decision_id(self, id: str) -> dict: + """ + Načítanie rozhodnutia na základe id + + :param id: Identifikátor sudu (povinný parameter), napr. "sud_123", "sud_456". + + :return: Slovník (JSON) obsahujúci detailné informácie o rozhodnutií na základe id + """ + try: + url = f"{API_BASE_URL}/v1/rozhodnutie/{id}" + response = requests.get(url) + response.raise_for_status() + + if response.status_code == 200: + data = response.json() + print(f"⛔📃\n {data}") + return {"success": True, "data": data} + else: + return {"success": False, "error": f"Unexpected status code: {response.status_code} ⚠️"} + + except Exception as e: + return {"success": False, "error": f"Error : {str(e)} 🛑"} + + class DecisionAutocomplete(BaseModel): + query: Optional[str] = None + guidSud: Optional[str] = None + limit: Optional[conint(ge=0)] = None + + @function_tool + def decision_autocomplete(self, args: DecisionAutocomplete) -> dict: + """ + Autocomplete služba pre načítanie rozhodnutia. + + :param query: Hľadané slovo alebo slovné spojenie + :param guidSud: Identifikátor súdu, napr. "sud_123", "sud_456". + :param limit: Maximálny počet rozhodnutie, ktoré sa majú vrátiť. + + :return: Slovník (JSON) obsahujúci zoznam rozhodnutie pre autocomplete. + """ + try: + url = f"{API_BASE_URL}/v1/rozhodnutie/autocomplete" + response = requests.get(url, params=args.model_dump()) + response.raise_for_status() + + if response.status_code == 200: + data = response.json() + print(f"📛📃\n {data}") + return {"success": True, "data": data } + else: + return { "success": False, "error": f"Unexpected status code: {response.status_code} ⚠️"} + + except Exception as e: + return { "success": False, "error": f"Error get contract details: {str(e)} 🛑" } \ No newline at end of file diff --git a/core/tools/spravneKonanie.py b/core/tools/spravneKonanie.py new file mode 100644 index 0000000..2d8a8b7 --- /dev/null +++ b/core/tools/spravneKonanie.py @@ -0,0 +1,142 @@ +import requests +from pydantic import BaseModel, conint +from typing import List, Optional +from enum import Enum + +from agents import function_tool + +API_BASE_URL = "https://obcan.justice.sk/pilot/api/ress-isu-service" + +class SortDirection(str, Enum): + ASC = "ASC" + DESC = "DESC" + +class AdminProceedAPI: + + class AdminProceed(BaseModel): + query: Optional[str] = None + druhFacetFilter: Optional[List[str]] = None + datumPravoplatnostiOd : Optional[str] = None + datumPravoplatnostiDo : Optional[str] = None + page: Optional[int] = None + size : conint(ge=0) = 20 + sortProperty: Optional[str] = None + sortDirection: SortDirection = SortDirection.ASC + + @function_tool + def about_admin_proceed(self, args: AdminProceed) -> dict: + """ + + :param query: Hľadané slovo alebo slovné spojenie. + :param druhFacetFilter: Zoznam druhov správnych konaní (fazetový filter). + :param datumPravoplatnostiOd: Dátum právoplatnosti OD (formát: DD.MM.RRRR). + Príklad: "15.03.2023", "01.01.2020" + :param datumPravoplatnosiDo: Dátum právoplatnosti DO (formát: DD.MM.RRRR). + Príklad: "15.03.2023", "01.01.2020" + :param page: Číslo stránky (0 = prvá stránka). + Poznámka: Zmena 'page' zobrazí ĎALŠIE konania, nie tie isté. + Príklad: 'page=0' → konania 1-20, 'page=1' → konania 21-40. + :param size: Počet záznamov na stránku (predvolené 20). + Príklad: 'size=10' → 10 konaní na stránke. + :param sortProperty: Atribút pre zoradenie výsledkov. + :param sortDirection: Smer zoradenia (ASC alebo DESC). + + :return: Slovník s výsledkami vyhľadávania správnych konaní. + """ + + try: + url = f"{API_BASE_URL}/v1/spravneKonanie" + response = requests.get(url, params=args.model_dump()) + response.raise_for_status() + + if response.status_code == 200: + data = response.json() + print(f"⭕👨\n {data}") + return {"success": True, "data": data} + else: + return {"success": False, "error": f"Unexpected status code: {response.status_code} ⚠️"} + + except Exception as e: + return {"success": False, "error": f"Error : {str(e)} 🛑"} + + @function_tool + def admin_proceed_id(self, id: str) -> dict: + """ + Načítanie binárneho obsahu prílohy správneho konania na základe ID. + + :param id: Unikátny identifikátor administratívneho konania (povinný parameter). + Príklad: sud_175 + + :return: Slovník (JSON) obsahujúci binárne dáta prílohy a metainformácie. + """ + try: + url = f"{API_BASE_URL}/v1/spravneKonanie/{id}" + response = requests.get(url) + response.raise_for_status() + + if response.status_code == 200: + data = response.json() + print(f"📛👨\n {data}") + return {"success": True, "data": data} + else: + return {"success": False, "error": f"Unexpected status code: {response.status_code} ⚠️"} + + except Exception as e: + return {"success": False, "error": f"Error : {str(e)} 🛑"} + + @function_tool + def admin_proceed_attachments(self, id: str) -> dict: + """ + Načítanie správneho konania na základe id + + :param id: Unikátny identifikátor administratívneho konania (povinný parameter). + Príklad: sud_175 + + :return: Slovník (JSON) obsahujúci detailné informácie o správnom konaní na základe id. + """ + try: + url = f"{API_BASE_URL}/v1/spravneKonanie/priloha/{id}" + response = requests.get(url) + response.raise_for_status() + + if response.status_code == 200: + data = response.json() + print(f"❗👨\n {data}") + return {"success": True, "data": data} + else: + return {"success": False, "error": f"Unexpected status code: {response.status_code} ⚠️"} + + except Exception as e: + return {"success": False, "error": f"Error : {str(e)} 🛑"} + + class AdminProceedAutocomplete(BaseModel): + query: Optional[str] = None + limit: Optional[conint(ge=1)] = None + + @function_tool + def admin_proceed_autocomplete(self, args: AdminProceedAutocomplete) -> dict: + """ + Autocomplete služba pre vyhľadávanie správnych konaní. + + :param query: Hľadané slovo alebo slovné spojenie pre vyhľadávanie správnych konaní. + + :param limit: Maximálny počet návrhov, ktoré sa majú vrátiť. + None → API vráti 5 dokumentov (predvolená hodnota). + Hodnota 0 spôsobí chybu API, preto použite None namiesto 0 + + :return: Slovník (JSON) obsahujúci zoznam návrhov pre autocomplete. + """ + try: + url = f"{API_BASE_URL}/v1/spravneKonanie/autocomplete" + response = requests.get(url, args.model_dump()) + response.raise_for_status() + + if response.status_code == 200: + data = response.json() + print(f"⭕\n {data}") + return {"success": True, "data": data} + else: + return {"success": False, "error": f"Unexpected status code: {response.status_code} ⚠️"} + + except Exception as e: + return {"success": False, "error": f"Error : {str(e)} 🛑"} \ No newline at end of file diff --git a/core/tools/sud.py b/core/tools/sud.py new file mode 100644 index 0000000..941e0a4 --- /dev/null +++ b/core/tools/sud.py @@ -0,0 +1,114 @@ +import requests +from pydantic import BaseModel, conint +from typing import List, Optional +from enum import Enum + +from agents import function_tool + +API_BASE_URL = "https://obcan.justice.sk/pilot/api/ress-isu-service" + +class SortDirection(str, Enum): + ASC = "ASC" + DESC = "DESC" + +class CourtAPI: + + class ListCourts(BaseModel): + query: Optional[str] = None + typSuduFacetFilter: List[str] = [] + krajFacetFilter: List[str] = [] + zahrnutZaniknuteSudy: Optional[bool] = None + sortProperty: Optional[str] = None + sortDirection: SortDirection = SortDirection.ASC + + + @function_tool + def about_courts(args: ListCourts) -> dict: + """ + Načítanie zoznamu súdov na základe filtrovacích kritérii + + :param query: Hľadané slovo alebo slovné spojenie, podľa ktorého sa vyhľadávajú súdy. + :param typSuduFacetFilter: Zoznam typov súdov (fazetový filter), napr. ["Okresný súd", "Krajský súd"]. + :param krajFacetFilter: Zoznam krajov (fazetový filter), napr. ["Bratislavský kraj", "Košický kraj"]. + :param zahrnutZaniknuteSudy: Príznak, či zahrnúť aj zaniknuté súdy. Hodnota typu boolean. + :param sortProperty: Názov atribútu, podľa ktorého sa majú záznamy zoradiť. + :param sortDirection: Smer zoradenia záznamov. Možnosti: "ASC" (predvolené) alebo "DESC". + + + :return: Slovník (JSON) obsahujúci detailné informácie s detailnými informáciami, metadátami a filtrami. + """ + try: + url = f"{API_BASE_URL}/v1/sud" + response = requests.get(url, params=args.model_dump()) + response.raise_for_status() + + if response.status_code == 200: + data = response.json() + print(f"⭕🏛️\n {data}") + return {"success": True, "data": data} + else: + return {"success": False, "error": f"Unexpected status code: {response.status_code} ⚠️"} + + except Exception as e: + return {"success": False, "error": f"Error get contract details: {str(e)} 🛑"} + + + class CourtDetail(BaseModel): + idSudu: Optional[str] = None + + + @function_tool + def get_court(args: CourtDetail) -> dict: + """ + Načítanie detailnej informácie o súde na základe jeho ID. + + :param idSudu: Identifikátor súdu. + + :return: Slovník (JSON) obsahujúci detailné informácie sudov. + """ + try: + url = f"{API_BASE_URL}/v1/sud/{args.idSudu}" + response = requests.get(url) + response.raise_for_status() + + if response.status_code == 200: + data = response.json() + data.pop("foto", None) + print(f"❗🏛️\n {data}") + return {"success": True, "data": data} + else: + return {"success": False, "error": f"Unexpected status code: {response.status_code} ⚠️"} + + except Exception as e: + return {"success": False, "error": f"Error get contract details: {str(e)} 🛑"} + + class CourtAutocomplete(BaseModel): + query: Optional[str] = None + limit: Optional[conint(ge=0)] = None + + + @function_tool + def court_autocomplete(args: CourtAutocomplete) -> dict: + """ + Autocomplete pre vyhľadávanie súdu. + + :param query: Hľadané slovo alebo slovné spojenie, podľa ktorého sa majú hľadať súdy. + :param limit: Maximálny počet návrhov súdov, ktoré sa majú vrátiť. Ak je 0, vráti všetky možné návrhy. + + :return: Slovník (JSON) obsahujúci zoznam návrhov súdov pre autocomplete. + """ + + try: + url = f"{API_BASE_URL}/v1/sud/autocomplete" + response = requests.get(url, params=args.model_dump()) + response.raise_for_status() + + if response.status_code == 200: + data = response.json() + print(f"📛🏛️\n {data}") + return {"success": True, "data": data } + else: + return { "success": False, "error": f"Unexpected status code: {response.status_code} ⚠️"} + + except Exception as e: + return { "success": False, "error": f"Error get contract details: {str(e)} 🛑" } \ No newline at end of file diff --git a/core/tools/sudca.py b/core/tools/sudca.py new file mode 100644 index 0000000..cbd2de5 --- /dev/null +++ b/core/tools/sudca.py @@ -0,0 +1,126 @@ +import requests +from pydantic import BaseModel, conint +from typing import List, Optional +from enum import Enum + +from agents import function_tool + +API_BASE_URL = "https://obcan.justice.sk/pilot/api/ress-isu-service" + +class SortDirection(str, Enum): + ASC = "ASC" + DESC = "DESC" + +class JudgeAPI: + + class JudgeParams(BaseModel): + query: Optional[str] = None + funkciaFacetFilter: Optional[List[str]] = None + typSuduFacetFilter: Optional[List[str]] = None + krajFacetFilter: Optional[List[str]] = None + okresFacetFilter: Optional[List[str]] = None + stavZapisuFacetFilter: Optional[List[str]] = None + guidSud: Optional[str] = None + page: Optional[int] = None + size: Optional[conint(ge=0)] = None + sortProperty: Optional[str] = None + sortDirection: SortDirection = SortDirection.ASC + + @function_tool + def about_judge(self, args: JudgeParams): + """ + Načítanie zoznamu sudcov na základe filtrovacích kritérii + + :param query: Hľadané slovo alebo slovné spojenie pre vyhľadávanie sudcov. + :param funkciaFacetFilter: Zoznam funkcií sudcov: ["Sudca", "Predseda", "Podpredseda", "Hosťujúci sudca", "a ine"]. + :param typSuduFacetFilter: Zoznam typov súdov: ["Okresný súd", "Krajský súd", "Mestský súd", + "Najvyšší súd SR", "Správny súd", + "Špecializovaný trestný súd"]. + :param krajFacetFilter: Zoznam krajov: ["Bratislavský kraj", "Košický kraj", "Banskobystrický kraj", + "Prešovský kraj", "Žilinský kraj", "Trnavský kraj", "Nitriansky kraj", + "Trenčiansky kraj"]. + :param okresFacetFilter: Zoznam okresov: ["Okres [mesto Slovenska]"] napr. "Okres Bratislava I", "Okres Košice I". + :param stavZapisuFacetFilter: Zoznam stavov zápisu: ["prerusenie vykonu - poberatel", "aktivity", + "odvolany", "vymazany", "prerusenie vykonu - ina funkce"]. + :param guidSud: Identifikátor súdu pre filtrovanie sudcov konkrétneho súdu, napr. "sud_101". + :param page: Číslo stránky (0 = prvá stránka) pre stránkovanie výsledkov. + :param size: Počet záznamov na stránku (predvolené 20) pre stránkovanie výsledkov. + :param sortProperty: Atribút pre zoradenie výsledkov, napr. "priezvisko", "meno". + :param sortDirection: Smer zoradenia záznamov: "ASC" (predvolené) alebo "DESC". + + :return: Slovník (JSON) obsahujúci zoznam sudcov s detailnými informáciami, metadátami a filtrami. + + :return: Slovník (JSON) obsahujúci zoznam súdov na základe filtrovacích kritérii + """ + try: + url = f"{API_BASE_URL}/v1/sudca" + response = requests.get(url, params=args.model_dump()) + response.raise_for_status() + + if response.status_code == 200: + data = response.json() + print(f"📛🧑‍⚖️\n {data}") + return {"success": True, "data": data} + else: + return {"success": False, "error": f"Unexpected status code: {response.status_code} ⚠️"} + + except Exception as e: + return {"success": False, "error": f"Error : {str(e)} 🛑"} + + @function_tool + def judge_id(self, id: str) -> dict: + """ + Načítanie detailu sudcu na základe identifikátora. + + :param id: Identifikátor sudcu (povinný parameter), napr. "sudca_123", "sudca_456". + + :return: Slovník (JSON) obsahujúci detailné informácie o sudcovi na základe identifikátora. + """ + try: + url = f"{API_BASE_URL}/v1/sudca/{id}" + response = requests.get(url) + response.raise_for_status() + + if response.status_code == 200: + data = response.json() + print(f"⛔🧑‍⚖️\n {data}") + return {"success": True, "data": data} + else: + return {"success": False, "error": f"Unexpected status code: {response.status_code} ⚠️"} + + except Exception as e: + return {"success": False, "error": f"Error : {str(e)} 🛑"} + + class JudgeAutocomplete(BaseModel): + query: Optional[str] = None + guidSud: Optional[str] = None + limit: Optional[conint(ge=1)] = None + + @function_tool + def judge_autocomplete(self, args: JudgeAutocomplete) -> dict: + """ + Autocomplete služba pre vyhľadávanie sudcov. + + :param query: Hľadané slovo alebo slovné spojenie. + :param guidSud: Identifikátor súdu pre filtrovanie sudcov konkrétneho súdu. + Príklad: "sud_175" + :param limit: Maximálny počet návrhov, ktoré sa majú vrátiť. + None → API vráti 5 dokumentov (predvolená hodnota). + Hodnota 0 spôsobí chybu API, preto použite None namiesto 0 + + :return: Slovník (JSON) obsahujúci zoznam návrhov sudcov pre autocomplete. + """ + try: + url = f"{API_BASE_URL}/v1/sudca/autocomplete" + response = requests.get(url, args.model_dump()) + response.raise_for_status() + + if response.status_code == 200: + data = response.json() + print(f"⭕🧑‍⚖️\n {data}") + return {"success": True, "data": data} + else: + return {"success": False, "error": f"Unexpected status code: {response.status_code} ⚠️"} + + except Exception as e: + return {"success": False, "error": f"Error : {str(e)} 🛑"} \ No newline at end of file diff --git a/core/tools/zmluvy.py b/core/tools/zmluvy.py new file mode 100644 index 0000000..89f7822 --- /dev/null +++ b/core/tools/zmluvy.py @@ -0,0 +1,121 @@ +import requests +from pydantic import BaseModel, conint +from typing import List, Optional +from enum import Enum + +from agents import function_tool + +API_BASE_URL = "https://obcan.justice.sk/pilot/api/ress-isu-service" + + +class SortDirection(str, Enum): + ASC = "ASC" + DESC = "DESC" + +class ContractsAPI: + + class ContractsList(BaseModel): + query : Optional[str] = None + typDokumentuFacetFilter: Optional[List[str]] = None + odberatelFacetFilter: Optional[List[str]] = None + dodavatelFacetFilter: Optional[List[str]] = None + hodnotaZmluvyFacetFilter: Optional[List[str]] = None + guidSud: Optional[str] = None + sortProperty: Optional[str] = None + sortDirection: SortDirection = SortDirection.ASC + page: Optional[int] = None + size : conint(ge=0) = 20 + + @function_tool + def about_contracts(args: ContractsList) -> dict: + """ + Načítanie zoznamu zmlúv podľa zadaných filtrov. + + :param query: Hľadaný výraz, podľa ktorého sa vyhľadávajú zmluvy. + :param typDokumentuFacetFilter: Typy dokumentov ako fazetový filter (napr. ["ZMLUVA","FAKTURA"]). + :param odberatelFacetFilter: Odberatelia pre fazetový filter. + :param dodavatelFacetFilter: Dodávatelia pre fazetový filter. + :param hodnotaZmluvyFacetFilter: Hodnoty zmlúv (filtrovacie intervaly). + :param guidSud: Identifikátor súdu (napr. "sud_101"). + :param sortProperty: Atribút na triedenie výsledkov. + :param sortDirection: ASC alebo DESC. + :param page: Číslo strany (0 = prvá strana). + :param size: Počet výsledkov na strane. + + :return: JSON so zoznamom zmlúv a metadátami. + """ + try: + url = f"{API_BASE_URL}/v1/zmluvy" + response = requests.get(url, params=args.model_dump()) + response.raise_for_status() + + if response.status_code == 200: + data = response.json() + print(f"⭕🖇️\n {data}") + return {"success": True, "data": data} + else: + return {"success": False, "error": f"Unexpected status code: {response.status_code} ⚠️"} + + except Exception as e: + return {"success": False, "error": f"Error get contract details: {str(e)} 🛑"} + + class ContractDetail(BaseModel): + idZmluvy: str + + @function_tool + def get_contract(args: ContractDetail) -> dict: + """ + Načítanie detailu zmluvy podľa jej identifikátora. + + :param idZmluvy: Jedinečný identifikátor zmluvy. + + :return: JSON slovník obsahujúci detailné informácie o zmluve. + """ + try: + url = f"{API_BASE_URL}/v1/zmluvy/{args.idZmluvy}" + response = requests.get(url) + response.raise_for_status() + + if response.status_code == 200: + data = response.json() + print(f"⛔🖇️\n {data}") + return {"success": True, "data": data } + else: + return { "success": False, "error": f"Unexpected status code: {response.status_code} ⚠️"} + + except Exception as e: + return { "success": False, "error": f"Error get contract details: {str(e)} 🛑" } + + class ContractsAutocomplete(BaseModel): + query : Optional[str] = None + guidSud: Optional[str] = None + limit: Optional[conint(ge=1)] = None + + @function_tool + def contracts_autocomplete(args: ContractsAutocomplete) -> dict: + """ + Autocomplete pre vyhľadávanie zmlúv. + + :param query: Hľadaný text alebo výraz na vyhľadanie zmlúv. + :param guidSud: Identifikátor súdu (napr. "sud_101") na filtrovanie zmlúv konkrétneho súdu. + :param limit: Maximálny počet výsledkov. + None = predvolených 5 výsledkov. + Hodnota 0 spôsobí chybu API — použite radšej None. + + :return: JSON slovník obsahujúci zoznam návrhov zmlúv pre autocomplete. + """ + + try: + url = f"{API_BASE_URL}/v1/zmluvy/autocomplete" + response = requests.get(url, params=args.model_dump()) + response.raise_for_status() + + if response.status_code == 200: + data = response.json() + print(f"📛🖇️\n {data}") + return {"success": True, "data": data} + else: + return {"success": False, "error": f"Unexpected status code: {response.status_code} ⚠️"} + + except Exception as e: + return {"success": False, "error": f"Error get contract details: {str(e)} 🛑"} diff --git a/main.py b/main.py new file mode 100644 index 0000000..0229e45 --- /dev/null +++ b/main.py @@ -0,0 +1,7 @@ +from app.app import create_app + +def main(): + create_app() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3d3ef92 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +openai-agents +python-dotenv +streamlit +requests \ No newline at end of file diff --git a/test/Examples of questions.pdf b/test/Examples of questions.pdf new file mode 100644 index 0000000..0920a55 Binary files /dev/null and b/test/Examples of questions.pdf differ diff --git a/test/latex_problem.png b/test/latex_problem.png new file mode 100644 index 0000000..607ae42 Binary files /dev/null and b/test/latex_problem.png differ