base service
This commit is contained in:
commit
1a0bdce02d
|
|
@ -0,0 +1,8 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AgentMigrationStateService">
|
||||||
|
<option name="migrationStatus" value="COMPLETED" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AskMigrationStateService">
|
||||||
|
<option name="migrationStatus" value="COMPLETED" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Ask2AgentMigrationStateService">
|
||||||
|
<option name="migrationStatus" value="COMPLETED" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="EditMigrationStateService">
|
||||||
|
<option name="migrationStatus" value="COMPLETED" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/vetrag.iml" filepath="$PROJECT_DIR$/.idea/vetrag.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="Go" enabled="true" />
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
llm:
|
||||||
|
extract_keywords_prompt: "Extract 3–5 key veterinary-related terms from this user message: {{.Message}}"
|
||||||
|
disambiguate_prompt: "Given these possible vet visit reasons: {{.Entries}}, choose the single best match for this user message: {{.Message}}. Reply with the id or none."
|
||||||
|
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
- id: deworming
|
||||||
|
reason: "Féregtelenítés kutyának"
|
||||||
|
keywords: ["féreg", "féregtelenítés", "parazita", "bélféreg", "kutya"]
|
||||||
|
procedures:
|
||||||
|
- name: "Alap vérvizsgálat"
|
||||||
|
price: 12000
|
||||||
|
duration_minutes: 30
|
||||||
|
- name: "Féregtelenítő kezelés"
|
||||||
|
price: 8000
|
||||||
|
duration_minutes: 15
|
||||||
|
notes: "A kezelés előtt vérvizsgálat szükséges a biztonságos gyógyszeradás miatt."
|
||||||
|
|
||||||
|
- id: vaccination
|
||||||
|
reason: "Oltás kutyának"
|
||||||
|
keywords: ["oltás", "vakcina", "oltani", "kutyabetegség", "veszettség"]
|
||||||
|
procedures:
|
||||||
|
- name: "Általános állapotfelmérés"
|
||||||
|
price: 6000
|
||||||
|
duration_minutes: 15
|
||||||
|
- name: "Oltás beadása"
|
||||||
|
price: 10000
|
||||||
|
duration_minutes: 10
|
||||||
|
notes: "A kutyák oltási programja eltérhet az életkortól és korábbi oltásoktól függően."
|
||||||
|
|
||||||
|
- id: neutering
|
||||||
|
reason: "Ivartalanítás macskának"
|
||||||
|
keywords: ["ivartalanítás", "műtét", "macska", "kandúr", "nőstény"]
|
||||||
|
procedures:
|
||||||
|
- name: "Műtéti előzetes vizsgálat"
|
||||||
|
price: 15000
|
||||||
|
duration_minutes: 30
|
||||||
|
- name: "Ivartalanító műtét"
|
||||||
|
price: 35000
|
||||||
|
duration_minutes: 90
|
||||||
|
notes: "A műtét után 2-3 nap pihenő szükséges."
|
||||||
|
|
||||||
|
- id: dental_cleaning
|
||||||
|
reason: "Fogkő eltávolítás kutyának"
|
||||||
|
keywords: ["fog", "fogkő", "fogsor", "fogtisztítás", "kutyafog"]
|
||||||
|
procedures:
|
||||||
|
- name: "Altatás előtti vizsgálat"
|
||||||
|
price: 12000
|
||||||
|
duration_minutes: 20
|
||||||
|
- name: "Fogkő eltávolítás ultrahanggal"
|
||||||
|
price: 25000
|
||||||
|
duration_minutes: 60
|
||||||
|
notes: "Az altatás kockázata miatt minden esetben szükséges előzetes vizsgálat."
|
||||||
|
|
||||||
|
- id: checkup
|
||||||
|
reason: "Általános állapotfelmérés"
|
||||||
|
keywords: ["vizsgálat", "ellenőrzés", "checkup", "állapotfelmérés"]
|
||||||
|
procedures:
|
||||||
|
- name: "Teljes fizikai vizsgálat"
|
||||||
|
price: 10000
|
||||||
|
duration_minutes: 30
|
||||||
|
notes: "Évente legalább egyszer javasolt a rutin állapotfelmérés."
|
||||||
|
|
||||||
|
- id: allergy
|
||||||
|
reason: "Allergiás tünetek vizsgálata"
|
||||||
|
keywords: ["allergia", "viszketés", "kiütés", "bőrpír", "allergiás"]
|
||||||
|
procedures:
|
||||||
|
- name: "Bőr- és vérvizsgálat"
|
||||||
|
price: 20000
|
||||||
|
duration_minutes: 45
|
||||||
|
notes: "Az allergia gyakran étel vagy környezeti tényező miatt alakul ki."
|
||||||
|
|
||||||
|
- id: ultrasound
|
||||||
|
reason: "Ultrahangos vizsgálat"
|
||||||
|
keywords: ["ultrahang", "has", "vizsgálat", "UH"]
|
||||||
|
procedures:
|
||||||
|
- name: "Ultrahang vizsgálat"
|
||||||
|
price: 18000
|
||||||
|
duration_minutes: 30
|
||||||
|
notes: "Terhesség vagy belső szervi problémák vizsgálatára gyakran használt módszer."
|
||||||
|
|
||||||
|
- id: bloodwork
|
||||||
|
reason: "Laborvizsgálat vérből"
|
||||||
|
keywords: ["vér", "vérvizsgálat", "labor", "teszt"]
|
||||||
|
procedures:
|
||||||
|
- name: "Teljes vérkép"
|
||||||
|
price: 15000
|
||||||
|
duration_minutes: 20
|
||||||
|
notes: "Sok más vizsgálat alapja a laboreredmény."
|
||||||
|
|
||||||
|
- id: xray
|
||||||
|
reason: "Röntgenfelvétel"
|
||||||
|
keywords: ["röntgen", "csont", "felvétel", "törés"]
|
||||||
|
procedures:
|
||||||
|
- name: "Röntgen vizsgálat"
|
||||||
|
price: 16000
|
||||||
|
duration_minutes: 25
|
||||||
|
notes: "Törések, csontelváltozások vizsgálatára javasolt."
|
||||||
|
|
||||||
|
- id: diarrhea
|
||||||
|
reason: "Hasmenés vizsgálata"
|
||||||
|
keywords: ["hasmenés", "hányás", "gyomor", "bél"]
|
||||||
|
procedures:
|
||||||
|
- name: "Állatorvosi konzultáció"
|
||||||
|
price: 8000
|
||||||
|
duration_minutes: 20
|
||||||
|
- name: "Székletvizsgálat"
|
||||||
|
price: 10000
|
||||||
|
duration_minutes: 30
|
||||||
|
notes: "Akut hasmenés esetén mindig javasolt a mihamarabbi vizsgálat."
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
module vetrag
|
||||||
|
|
||||||
|
go 1.25
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gin-gonic/gin v1.11.0
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/bytedance/sonic v1.14.0 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||||
|
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||||
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
|
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
|
github.com/quic-go/qpack v0.5.1 // indirect
|
||||||
|
github.com/quic-go/quic-go v0.54.0 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||||
|
go.uber.org/mock v0.5.0 // indirect
|
||||||
|
golang.org/x/arch v0.20.0 // indirect
|
||||||
|
golang.org/x/crypto v0.40.0 // indirect
|
||||||
|
golang.org/x/mod v0.25.0 // indirect
|
||||||
|
golang.org/x/net v0.42.0 // indirect
|
||||||
|
golang.org/x/sync v0.16.0 // indirect
|
||||||
|
golang.org/x/sys v0.35.0 // indirect
|
||||||
|
golang.org/x/text v0.27.0 // indirect
|
||||||
|
golang.org/x/tools v0.34.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.9 // indirect
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||||
|
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
||||||
|
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||||
|
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
|
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||||
|
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||||
|
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
|
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||||
|
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||||
|
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||||
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||||
|
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||||
|
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||||
|
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
|
||||||
|
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||||
|
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||||
|
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||||
|
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||||
|
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
||||||
|
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||||
|
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||||
|
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||||
|
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||||
|
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||||
|
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||||
|
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||||
|
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||||
|
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||||
|
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||||
|
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||||
|
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||||
|
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||||
|
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
@ -0,0 +1,304 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Procedure represents a single procedure for a visit reason
|
||||||
|
type Procedure struct {
|
||||||
|
Name string `yaml:"name" json:"name"`
|
||||||
|
Price int `yaml:"price" json:"price"`
|
||||||
|
DurationMin int `yaml:"duration_minutes" json:"duration_minutes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reason represents a visit reason entry
|
||||||
|
type Reason struct {
|
||||||
|
ID string `yaml:"id" json:"id"`
|
||||||
|
Reason string `yaml:"reason" json:"reason"`
|
||||||
|
Keywords []string `yaml:"keywords" json:"keywords"`
|
||||||
|
Procedures []Procedure `yaml:"procedures" json:"procedures"`
|
||||||
|
Notes string `yaml:"notes" json:"notes,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var reasonsDB []Reason
|
||||||
|
|
||||||
|
func loadYAMLDB(path string) error {
|
||||||
|
data, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return yaml.Unmarshal(data, &reasonsDB)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChatRequest represents the incoming chat message
|
||||||
|
type ChatRequest struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChatResponse represents the response to the frontend
|
||||||
|
type ChatResponse struct {
|
||||||
|
Match *string `json:"match"`
|
||||||
|
Procedures []Procedure `json:"procedures,omitempty"`
|
||||||
|
TotalPrice int `json:"total_price,omitempty"`
|
||||||
|
TotalDuration int `json:"total_duration,omitempty"`
|
||||||
|
Notes string `json:"notes,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LLMClient abstracts LLM API calls
|
||||||
|
type LLMClient struct {
|
||||||
|
APIKey string
|
||||||
|
BaseURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config holds all prompts and settings
|
||||||
|
type Config struct {
|
||||||
|
LLM struct {
|
||||||
|
ExtractKeywordsPrompt string `yaml:"extract_keywords_prompt"`
|
||||||
|
DisambiguatePrompt string `yaml:"disambiguate_prompt"`
|
||||||
|
} `yaml:"llm"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var appConfig Config
|
||||||
|
|
||||||
|
func loadConfig(path string) error {
|
||||||
|
data, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return yaml.Unmarshal(data, &appConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractKeywords calls LLM to extract keywords from user message
|
||||||
|
func (llm *LLMClient) ExtractKeywords(ctx context.Context, message string) ([]string, error) {
|
||||||
|
prompt, err := renderPrompt(appConfig.LLM.ExtractKeywordsPrompt, map[string]string{"Message": message})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[CONFIG] Failed to render ExtractKeywords prompt: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.Printf("[LLM] ExtractKeywords prompt: %q", prompt)
|
||||||
|
resp, err := llm.openAICompletion(ctx, prompt)
|
||||||
|
log.Printf("[LLM] ExtractKeywords response: %q, err: %v", resp, err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var keywords []string
|
||||||
|
if err := json.Unmarshal([]byte(resp), &keywords); err == nil {
|
||||||
|
return keywords, nil
|
||||||
|
}
|
||||||
|
// fallback: try splitting by comma
|
||||||
|
for _, k := range bytes.Split([]byte(resp), []byte{','}) {
|
||||||
|
kw := strings.TrimSpace(string(k))
|
||||||
|
if kw != "" {
|
||||||
|
keywords = append(keywords, kw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keywords, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisambiguateBestMatch calls LLM to pick best match from candidates
|
||||||
|
func (llm *LLMClient) DisambiguateBestMatch(ctx context.Context, message string, candidates []Reason) (string, error) {
|
||||||
|
entries, _ := json.Marshal(candidates)
|
||||||
|
prompt, err := renderPrompt(appConfig.LLM.DisambiguatePrompt, map[string]string{"Entries": string(entries), "Message": message})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[CONFIG] Failed to render Disambiguate prompt: %v", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
log.Printf("[LLM] DisambiguateBestMatch prompt: %q", prompt)
|
||||||
|
resp, err := llm.openAICompletion(ctx, prompt)
|
||||||
|
log.Printf("[LLM] DisambiguateBestMatch response: %q, err: %v", resp, err)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
id := strings.TrimSpace(resp)
|
||||||
|
if id == "none" || id == "null" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// openAICompletion is a minimal OpenAI API call (text-davinci-003 or gpt-3.5-turbo-instruct)
|
||||||
|
func (llm *LLMClient) openAICompletion(ctx context.Context, prompt string) (string, error) {
|
||||||
|
apiURL := llm.BaseURL
|
||||||
|
if apiURL == "" {
|
||||||
|
apiURL = "https://api.openai.com/v1/completions"
|
||||||
|
}
|
||||||
|
log.Printf("[LLM] openAICompletion POST %s | prompt: %q", apiURL, prompt)
|
||||||
|
body := map[string]interface{}{
|
||||||
|
"model": "text-davinci-003",
|
||||||
|
"prompt": prompt,
|
||||||
|
"max_tokens": 64,
|
||||||
|
"temperature": 0,
|
||||||
|
}
|
||||||
|
jsonBody, _ := json.Marshal(body)
|
||||||
|
req, _ := http.NewRequestWithContext(ctx, "POST", apiURL, bytes.NewBuffer(jsonBody))
|
||||||
|
if llm.APIKey != "" {
|
||||||
|
req.Header.Set("Authorization", "Bearer "+llm.APIKey)
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[LLM] openAICompletion error: %v", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
var result struct {
|
||||||
|
Choices []struct {
|
||||||
|
Text string `json:"text"`
|
||||||
|
} `json:"choices"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||||
|
log.Printf("[LLM] openAICompletion decode error: %v", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if len(result.Choices) == 0 {
|
||||||
|
log.Printf("[LLM] openAICompletion: no choices returned")
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
log.Printf("[LLM] openAICompletion: got text: %q", result.Choices[0].Text)
|
||||||
|
return result.Choices[0].Text, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// naiveKeywordExtract splits message into lowercase words (placeholder for LLM)
|
||||||
|
func naiveKeywordExtract(msg string) []string {
|
||||||
|
// TODO: Replace with LLM call
|
||||||
|
words := make(map[string]struct{})
|
||||||
|
for _, w := range strings.FieldsFunc(strings.ToLower(msg), func(r rune) bool {
|
||||||
|
return r < 'a' || r > 'z' && r < 'á' || r > 'ű'
|
||||||
|
}) {
|
||||||
|
words[w] = struct{}{}
|
||||||
|
}
|
||||||
|
res := make([]string, 0, len(words))
|
||||||
|
for w := range words {
|
||||||
|
res = append(res, w)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// findCandidates returns reasons with overlapping keywords
|
||||||
|
func findCandidates(keywords []string) []Reason {
|
||||||
|
kwSet := make(map[string]struct{})
|
||||||
|
for _, k := range keywords {
|
||||||
|
kwSet[k] = struct{}{}
|
||||||
|
}
|
||||||
|
var candidates []Reason
|
||||||
|
for _, r := range reasonsDB {
|
||||||
|
for _, k := range r.Keywords {
|
||||||
|
if _, ok := kwSet[strings.ToLower(k)]; ok {
|
||||||
|
candidates = append(candidates, r)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return candidates
|
||||||
|
}
|
||||||
|
|
||||||
|
// sumProcedures calculates total price and duration
|
||||||
|
func sumProcedures(procs []Procedure) (int, int) {
|
||||||
|
totalPrice := 0
|
||||||
|
totalDuration := 0
|
||||||
|
for _, p := range procs {
|
||||||
|
totalPrice += p.Price
|
||||||
|
totalDuration += p.DurationMin
|
||||||
|
}
|
||||||
|
return totalPrice, totalDuration
|
||||||
|
}
|
||||||
|
|
||||||
|
// renderPrompt renders a Go template with the given data
|
||||||
|
func renderPrompt(tmplStr string, data any) (string, error) {
|
||||||
|
tmpl, err := template.New("").Parse(tmplStr)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := tmpl.Execute(&buf, data); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := loadConfig("config.yaml"); err != nil {
|
||||||
|
log.Fatalf("Failed to load config.yaml: %v", err)
|
||||||
|
}
|
||||||
|
log.Printf("Loaded config: %+v", appConfig)
|
||||||
|
if err := loadYAMLDB("db.yaml"); err != nil {
|
||||||
|
log.Fatalf("Failed to load db.yaml: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Loaded %d reasons from db.yaml\n", len(reasonsDB))
|
||||||
|
|
||||||
|
llm := &LLMClient{
|
||||||
|
APIKey: os.Getenv("OPENAI_API_KEY"),
|
||||||
|
BaseURL: os.Getenv("OPENAI_BASE_URL"), // e.g. http://localhost:1234/v1/completions
|
||||||
|
}
|
||||||
|
r := gin.Default()
|
||||||
|
r.POST("/chat", func(c *gin.Context) {
|
||||||
|
var req ChatRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
log.Printf("[ERROR] Invalid request: %v", err)
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
keywords, err := llm.ExtractKeywords(ctx, req.Message)
|
||||||
|
candidates := findCandidates(keywords)
|
||||||
|
bestID := ""
|
||||||
|
if len(candidates) > 0 && err == nil {
|
||||||
|
bestID, err = llm.DisambiguateBestMatch(ctx, req.Message, candidates)
|
||||||
|
}
|
||||||
|
logRequest(req, keywords, candidates, bestID, err)
|
||||||
|
if err != nil || len(keywords) == 0 || len(candidates) == 0 || bestID == "" {
|
||||||
|
c.JSON(http.StatusOK, ChatResponse{Match: nil})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var best *Reason
|
||||||
|
for i := range candidates {
|
||||||
|
if candidates[i].ID == bestID {
|
||||||
|
best = &candidates[i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if best == nil {
|
||||||
|
c.JSON(http.StatusOK, ChatResponse{Match: nil})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
totalPrice, totalDuration := sumProcedures(best.Procedures)
|
||||||
|
log.Printf("[TRACE] Responding with match: %q, totalPrice: %d, totalDuration: %d, notes: %q", best.ID, totalPrice, totalDuration, best.Notes)
|
||||||
|
c.JSON(http.StatusOK, ChatResponse{
|
||||||
|
Match: &best.ID,
|
||||||
|
Procedures: best.Procedures,
|
||||||
|
TotalPrice: totalPrice,
|
||||||
|
TotalDuration: totalDuration,
|
||||||
|
Notes: best.Notes,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
r.Run(":8080")
|
||||||
|
}
|
||||||
|
|
||||||
|
// logRequest logs incoming chat requests and extracted info
|
||||||
|
func logRequest(req ChatRequest, keywords []string, candidates []Reason, bestID string, err error) {
|
||||||
|
log.Printf("[TRACE] %s | message: %q | keywords: %v | candidates: %v | bestID: %q | err: %v",
|
||||||
|
time.Now().Format(time.RFC3339), req.Message, keywords, getCandidateIDs(candidates), bestID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCandidateIDs(candidates []Reason) []string {
|
||||||
|
ids := make([]string, len(candidates))
|
||||||
|
for i, c := range candidates {
|
||||||
|
ids[i] = c.ID
|
||||||
|
}
|
||||||
|
return ids
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,145 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testDB struct {
|
||||||
|
file string
|
||||||
|
data string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tdb *testDB) setup() {
|
||||||
|
err := os.WriteFile(tdb.file, []byte(tdb.data), 0644)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tdb *testDB) teardown() {
|
||||||
|
_ = os.Remove(tdb.file)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChatEndpoint_MatchFound(t *testing.T) {
|
||||||
|
tdb := testDB{
|
||||||
|
file: "db.yaml",
|
||||||
|
data: `
|
||||||
|
- id: deworming
|
||||||
|
reason: Deworming for dogs
|
||||||
|
keywords: ["worms", "deworming", "parasite"]
|
||||||
|
procedures:
|
||||||
|
- name: Deworming tablet
|
||||||
|
price: 30
|
||||||
|
duration_minutes: 10
|
||||||
|
- name: Bloodwork
|
||||||
|
price: 35
|
||||||
|
duration_minutes: 35
|
||||||
|
notes: Bloodwork ensures organs are safe for treatment.
|
||||||
|
- id: vaccination
|
||||||
|
reason: Annual vaccination
|
||||||
|
keywords: ["vaccine", "vaccination", "shots"]
|
||||||
|
procedures:
|
||||||
|
- name: Vaccine injection
|
||||||
|
price: 50
|
||||||
|
duration_minutes: 15
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
tdb.setup()
|
||||||
|
defer tdb.teardown()
|
||||||
|
|
||||||
|
if err := loadYAMLDB(tdb.file); err != nil {
|
||||||
|
t.Fatalf("Failed to load test db: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r := setupRouter()
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
body := map[string]string{"message": "My dog needs deworming and bloodwork"}
|
||||||
|
jsonBody, _ := json.Marshal(body)
|
||||||
|
req, _ := http.NewRequest("POST", "/chat", bytes.NewBuffer(jsonBody))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Fatalf("Expected 200, got %d", w.Code)
|
||||||
|
}
|
||||||
|
respBody, _ := io.ReadAll(w.Body)
|
||||||
|
if !bytes.Contains(respBody, []byte("deworming")) {
|
||||||
|
t.Errorf("Expected match for deworming, got %s", string(respBody))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChatEndpoint_NoMatch(t *testing.T) {
|
||||||
|
tdb := testDB{
|
||||||
|
file: "db.yaml",
|
||||||
|
data: `
|
||||||
|
- id: vaccination
|
||||||
|
reason: Annual vaccination
|
||||||
|
keywords: ["vaccine", "vaccination", "shots"]
|
||||||
|
procedures:
|
||||||
|
- name: Vaccine injection
|
||||||
|
price: 50
|
||||||
|
duration_minutes: 15
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
tdb.setup()
|
||||||
|
defer tdb.teardown()
|
||||||
|
|
||||||
|
if err := loadYAMLDB(tdb.file); err != nil {
|
||||||
|
t.Fatalf("Failed to load test db: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r := setupRouter()
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
body := map[string]string{"message": "My dog has worms"}
|
||||||
|
jsonBody, _ := json.Marshal(body)
|
||||||
|
req, _ := http.NewRequest("POST", "/chat", bytes.NewBuffer(jsonBody))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Fatalf("Expected 200, got %d", w.Code)
|
||||||
|
}
|
||||||
|
respBody, _ := io.ReadAll(w.Body)
|
||||||
|
if !bytes.Contains(respBody, []byte(`"match":null`)) {
|
||||||
|
t.Errorf("Expected no match, got %s", string(respBody))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupRouter() *gin.Engine {
|
||||||
|
r := gin.Default()
|
||||||
|
r.POST("/chat", func(c *gin.Context) {
|
||||||
|
var req ChatRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
keywords := naiveKeywordExtract(req.Message)
|
||||||
|
candidates := findCandidates(keywords)
|
||||||
|
if len(candidates) == 0 {
|
||||||
|
c.JSON(http.StatusOK, ChatResponse{Match: nil})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
best := candidates[0]
|
||||||
|
totalPrice, totalDuration := sumProcedures(best.Procedures)
|
||||||
|
c.JSON(http.StatusOK, ChatResponse{
|
||||||
|
Match: &best.ID,
|
||||||
|
Procedures: best.Procedures,
|
||||||
|
TotalPrice: totalPrice,
|
||||||
|
TotalDuration: totalDuration,
|
||||||
|
Notes: best.Notes,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
- id: deworming
|
||||||
|
reason: "Féregtelenítés kutyának"
|
||||||
|
keywords: ["féreg", "féregtelenítés", "parazita", "bélféreg", "kutya"]
|
||||||
|
procedures:
|
||||||
|
- name: "Alap vérvizsgálat"
|
||||||
|
price: 12000
|
||||||
|
duration_minutes: 30
|
||||||
|
- name: "Féregtelenítő kezelés"
|
||||||
|
price: 8000
|
||||||
|
duration_minutes: 15
|
||||||
|
notes: "A kezelés előtt vérvizsgálat szükséges a biztonságos gyógyszeradás miatt."
|
||||||
|
|
||||||
|
- id: vaccination
|
||||||
|
reason: "Oltás kutyának"
|
||||||
|
keywords: ["oltás", "vakcina", "oltani", "kutyabetegség", "veszettség"]
|
||||||
|
procedures:
|
||||||
|
- name: "Általános állapotfelmérés"
|
||||||
|
price: 6000
|
||||||
|
duration_minutes: 15
|
||||||
|
- name: "Oltás beadása"
|
||||||
|
price: 10000
|
||||||
|
duration_minutes: 10
|
||||||
|
notes: "A kutyák oltási programja eltérhet az életkortól és korábbi oltásoktól függően."
|
||||||
|
|
||||||
|
- id: neutering
|
||||||
|
reason: "Ivartalanítás macskának"
|
||||||
|
keywords: ["ivartalanítás", "műtét", "macska", "kandúr", "nőstény"]
|
||||||
|
procedures:
|
||||||
|
- name: "Műtéti előzetes vizsgálat"
|
||||||
|
price: 15000
|
||||||
|
duration_minutes: 30
|
||||||
|
- name: "Ivartalanító műtét"
|
||||||
|
price: 35000
|
||||||
|
duration_minutes: 90
|
||||||
|
notes: "A műtét után 2-3 nap pihenő szükséges."
|
||||||
|
|
||||||
|
- id: dental_cleaning
|
||||||
|
reason: "Fogkő eltávolítás kutyának"
|
||||||
|
keywords: ["fog", "fogkő", "fogsor", "fogtisztítás", "kutyafog"]
|
||||||
|
procedures:
|
||||||
|
- name: "Altatás előtti vizsgálat"
|
||||||
|
price: 12000
|
||||||
|
duration_minutes: 20
|
||||||
|
- name: "Fogkő eltávolítás ultrahanggal"
|
||||||
|
price: 25000
|
||||||
|
duration_minutes: 60
|
||||||
|
notes: "Az altatás kockázata miatt minden esetben szükséges előzetes vizsgálat."
|
||||||
|
|
||||||
|
- id: checkup
|
||||||
|
reason: "Általános állapotfelmérés"
|
||||||
|
keywords: ["vizsgálat", "ellenőrzés", "checkup", "állapotfelmérés"]
|
||||||
|
procedures:
|
||||||
|
- name: "Teljes fizikai vizsgálat"
|
||||||
|
price: 10000
|
||||||
|
duration_minutes: 30
|
||||||
|
notes: "Évente legalább egyszer javasolt a rutin állapotfelmérés."
|
||||||
|
|
||||||
|
- id: allergy
|
||||||
|
reason: "Allergiás tünetek vizsgálata"
|
||||||
|
keywords: ["allergia", "viszketés", "kiütés", "bőrpír", "allergiás"]
|
||||||
|
procedures:
|
||||||
|
- name: "Bőr- és vérvizsgálat"
|
||||||
|
price: 20000
|
||||||
|
duration_minutes: 45
|
||||||
|
notes: "Az allergia gyakran étel vagy környezeti tényező miatt alakul ki."
|
||||||
|
|
||||||
|
- id: ultrasound
|
||||||
|
reason: "Ultrahangos vizsgálat"
|
||||||
|
keywords: ["ultrahang", "has", "vizsgálat", "UH"]
|
||||||
|
procedures:
|
||||||
|
- name: "Ultrahang vizsgálat"
|
||||||
|
price: 18000
|
||||||
|
duration_minutes: 30
|
||||||
|
notes: "Terhesség vagy belső szervi problémák vizsgálatára gyakran használt módszer."
|
||||||
|
|
||||||
|
- id: bloodwork
|
||||||
|
reason: "Laborvizsgálat vérből"
|
||||||
|
keywords: ["vér", "vérvizsgálat", "labor", "teszt"]
|
||||||
|
procedures:
|
||||||
|
- name: "Teljes vérkép"
|
||||||
|
price: 15000
|
||||||
|
duration_minutes: 20
|
||||||
|
notes: "Sok más vizsgálat alapja a laboreredmény."
|
||||||
|
|
||||||
|
- id: xray
|
||||||
|
reason: "Röntgenfelvétel"
|
||||||
|
keywords: ["röntgen", "csont", "felvétel", "törés"]
|
||||||
|
procedures:
|
||||||
|
- name: "Röntgen vizsgálat"
|
||||||
|
price: 16000
|
||||||
|
duration_minutes: 25
|
||||||
|
notes: "Törések, csontelváltozások vizsgálatára javasolt."
|
||||||
|
|
||||||
|
- id: diarrhea
|
||||||
|
reason: "Hasmenés vizsgálata"
|
||||||
|
keywords: ["hasmenés", "hányás", "gyomor", "bél"]
|
||||||
|
procedures:
|
||||||
|
- name: "Állatorvosi konzultáció"
|
||||||
|
price: 8000
|
||||||
|
duration_minutes: 20
|
||||||
|
- name: "Székletvizsgálat"
|
||||||
|
price: 10000
|
||||||
|
duration_minutes: 30
|
||||||
|
notes: "Akut hasmenés esetén mindig javasolt a mihamarabbi vizsgálat."
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export OPENAI_BASE_URL=http://localhost:1234/v1/completions
|
||||||
|
export OPENAI_API_KEY=sk-no-key-needed # (if LM Studio doesn't require a real key)
|
||||||
|
go run main.go
|
||||||
Loading…
Reference in New Issue