rename reason -> db

This commit is contained in:
lehel 2025-10-01 10:12:26 +02:00
parent a2e9e6e273
commit 8e0fc65bb2
No known key found for this signature in database
GPG Key ID: 9C4F9D6111EE5CFA
8 changed files with 101 additions and 101 deletions

View File

@ -19,13 +19,13 @@ type ChatServiceAPI interface {
// ChatService handles chat interactions and orchestrates LLM and DB calls. // ChatService handles chat interactions and orchestrates LLM and DB calls.
type ChatService struct { type ChatService struct {
LLM LLMClientAPI LLM LLMClientAPI
reasonsDB ReasonDBAPI visitsDB VisitDBAPI
} }
var _ ChatServiceAPI = (*ChatService)(nil) var _ ChatServiceAPI = (*ChatService)(nil)
func NewChatService(llm LLMClientAPI, db ReasonDBAPI) ChatServiceAPI { func NewChatService(llm LLMClientAPI, db VisitDBAPI) ChatServiceAPI {
return &ChatService{LLM: llm, reasonsDB: db} return &ChatService{LLM: llm, visitsDB: db}
} }
// HandleChat is the main entrypoint for chat requests. It delegates to modular helpers. // HandleChat is the main entrypoint for chat requests. It delegates to modular helpers.
@ -40,7 +40,7 @@ func (cs *ChatService) HandleChat(c *gin.Context) {
cs.respondWithError(c, req, keywords, err) cs.respondWithError(c, req, keywords, err)
return return
} }
best, err := cs.findBestReason(ctx, req, keywords) best, err := cs.findBestVisit(ctx, req, keywords)
resp := cs.buildResponse(best) resp := cs.buildResponse(best)
c.JSON(http.StatusOK, resp) c.JSON(http.StatusOK, resp)
} }
@ -65,10 +65,10 @@ func (cs *ChatService) extractKeywords(ctx context.Context, message string) ([]s
return cs.keywordsToStrings(kwResp["keyword"]), nil return cs.keywordsToStrings(kwResp["keyword"]), nil
} }
// findBestReason finds candidate reasons and disambiguates the best match. // findBestVisit finds candidate visits and disambiguates the best match.
func (cs *ChatService) findBestReason(ctx context.Context, req ChatRequest, keywords []string) (*Reason, error) { func (cs *ChatService) findBestVisit(ctx context.Context, req ChatRequest, keywords []string) (*Visit, error) {
cs.logKeywords(keywords, req.Message) cs.logKeywords(keywords, req.Message)
candidates, err := cs.reasonsDB.FindCandidates(keywords) candidates, err := cs.visitsDB.FindCandidates(keywords)
cs.logCandidates(candidates, err) cs.logCandidates(candidates, err)
if err != nil { if err != nil {
return nil, err return nil, err
@ -78,15 +78,15 @@ func (cs *ChatService) findBestReason(ctx context.Context, req ChatRequest, keyw
bestID, err = cs.LLM.DisambiguateBestMatch(ctx, req.Message, candidates) bestID, err = cs.LLM.DisambiguateBestMatch(ctx, req.Message, candidates)
cs.logBestID(bestID, err) cs.logBestID(bestID, err)
} }
reason, err := cs.reasonsDB.FindById(bestID) visit, err := cs.visitsDB.FindById(bestID)
if err != nil { if err != nil {
return nil, fmt.Errorf("FindById: %w", err) return nil, fmt.Errorf("FindById: %w", err)
} }
return &reason, nil return &visit, nil
} }
// buildResponse constructs the ChatResponse from the best Reason. // buildResponse constructs the ChatResponse from the best Visit.
func (cs *ChatService) buildResponse(best *Reason) ChatResponse { func (cs *ChatService) buildResponse(best *Visit) ChatResponse {
if best == nil { if best == nil {
resp := ChatResponse{Match: nil} resp := ChatResponse{Match: nil}
logrus.WithFields(logrus.Fields{"response": resp}).Info("Build response: no match") logrus.WithFields(logrus.Fields{"response": resp}).Info("Build response: no match")
@ -132,15 +132,15 @@ func (cs *ChatService) logKeywords(keywords []string, message string) {
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
"keywords": keywords, "keywords": keywords,
"message": message, "message": message,
}).Info("Finding visit reason candidates") }).Info("Finding visit candidates")
} }
// logCandidates logs candidate reasons. // logCandidates logs candidate visits.
func (cs *ChatService) logCandidates(candidates []Reason, err error) { func (cs *ChatService) logCandidates(candidates []Visit, err error) {
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
"candidates": candidates, "candidates": candidates,
"error": err, "error": err,
}).Info("Candidate reasons found") }).Info("Candidate visits found")
} }
// logBestID logs the best candidate ID. // logBestID logs the best candidate ID.
@ -152,7 +152,7 @@ func (cs *ChatService) logBestID(bestID string, err error) {
} }
// logChat logs the chat request and result details. // logChat logs the chat request and result details.
func (cs *ChatService) logChat(req ChatRequest, keywords interface{}, candidates []Reason, bestID string, err error) { func (cs *ChatService) logChat(req ChatRequest, keywords interface{}, candidates []Visit, bestID string, err error) {
var kwMap map[string]interface{} var kwMap map[string]interface{}
switch v := keywords.(type) { switch v := keywords.(type) {
case []string: case []string:
@ -164,7 +164,7 @@ func (cs *ChatService) logChat(req ChatRequest, keywords interface{}, candidates
} }
logRequest(req, kwMap, candidates, bestID, err) logRequest(req, kwMap, candidates, bestID, err)
if candidates != nil && bestID != "" { if candidates != nil && bestID != "" {
var best *Reason var best *Visit
for i := range candidates { for i := range candidates {
if candidates[i].ID == bestID { if candidates[i].ID == bestID {
best = &candidates[i] best = &candidates[i]

View File

@ -24,26 +24,26 @@ var _ LLMClientAPI = (*mockLLM)(nil)
func (m *mockLLM) ExtractKeywords(ctx context.Context, msg string) (map[string]interface{}, error) { func (m *mockLLM) ExtractKeywords(ctx context.Context, msg string) (map[string]interface{}, error) {
return m.keywordsResp, m.keywordsErr return m.keywordsResp, m.keywordsErr
} }
func (m *mockLLM) DisambiguateBestMatch(ctx context.Context, msg string, candidates []Reason) (string, error) { func (m *mockLLM) DisambiguateBestMatch(ctx context.Context, msg string, candidates []Visit) (string, error) {
return m.disambigID, m.disambigErr return m.disambigID, m.disambigErr
} }
// --- Test ReasonDB --- // --- Test VisitDB ---
type testReasonDB struct { type testVisitDB struct {
candidates []Reason candidates []Visit
findErr error findErr error
byID map[string]Reason byID map[string]Visit
} }
var _ ReasonDBAPI = (*testReasonDB)(nil) var _ VisitDBAPI = (*testVisitDB)(nil)
func (db *testReasonDB) FindCandidates(keywords []string) ([]Reason, error) { func (db *testVisitDB) FindCandidates(keywords []string) ([]Visit, error) {
return db.candidates, db.findErr return db.candidates, db.findErr
} }
func (db *testReasonDB) FindById(id string) (Reason, error) { func (db *testVisitDB) FindById(id string) (Visit, error) {
r, ok := db.byID[id] r, ok := db.byID[id]
if !ok { if !ok {
return Reason{}, context.DeadlineExceeded return Visit{}, context.DeadlineExceeded
} }
return r, nil return r, nil
} }
@ -55,14 +55,14 @@ func TestChatService_MatchFound(t *testing.T) {
keywordsResp: map[string]interface{}{"keyword": []string{"worms", "deworming"}}, keywordsResp: map[string]interface{}{"keyword": []string{"worms", "deworming"}},
disambigID: "deworming", disambigID: "deworming",
} }
reason := Reason{ visit := Visit{
ID: "deworming", ID: "deworming",
Procedures: []Procedure{{Name: "Deworming tablet", Price: 30, DurationMin: 10}}, Procedures: []Procedure{{Name: "Deworming tablet", Price: 30, DurationMin: 10}},
Notes: "Bloodwork ensures organs are safe for treatment.", Notes: "Bloodwork ensures organs are safe for treatment.",
} }
var db ReasonDBAPI = &testReasonDB{ var db VisitDBAPI = &testVisitDB{
candidates: []Reason{reason}, candidates: []Visit{visit},
byID: map[string]Reason{"deworming": reason}, byID: map[string]Visit{"deworming": visit},
} }
var cs ChatServiceAPI = NewChatService(llm, db) var cs ChatServiceAPI = NewChatService(llm, db)
r := gin.New() r := gin.New()
@ -88,8 +88,8 @@ func TestChatService_MatchFound(t *testing.T) {
if len(resp.Procedures) != 1 || resp.Procedures[0].Name != "Deworming tablet" { if len(resp.Procedures) != 1 || resp.Procedures[0].Name != "Deworming tablet" {
t.Errorf("Expected procedure 'Deworming tablet', got %+v", resp.Procedures) t.Errorf("Expected procedure 'Deworming tablet', got %+v", resp.Procedures)
} }
if resp.Notes != reason.Notes { if resp.Notes != visit.Notes {
t.Errorf("Expected notes '%s', got '%s'", reason.Notes, resp.Notes) t.Errorf("Expected notes '%s', got '%s'", visit.Notes, resp.Notes)
} }
} }
@ -99,9 +99,9 @@ func TestChatService_NoMatch(t *testing.T) {
keywordsResp: map[string]interface{}{"keyword": []string{"unknown"}}, keywordsResp: map[string]interface{}{"keyword": []string{"unknown"}},
disambigID: "", disambigID: "",
} }
db := &testReasonDB{ db := &testVisitDB{
candidates: []Reason{}, candidates: []Visit{},
byID: map[string]Reason{}, byID: map[string]Visit{},
} }
cs := NewChatService(llm, db) cs := NewChatService(llm, db)
r := gin.New() r := gin.New()
@ -131,7 +131,7 @@ func TestChatService_LLMError(t *testing.T) {
llm := &mockLLM{ llm := &mockLLM{
keywordsErr: context.DeadlineExceeded, keywordsErr: context.DeadlineExceeded,
} }
db := &testReasonDB{} db := &testVisitDB{}
cs := NewChatService(llm, db) cs := NewChatService(llm, db)
r := gin.New() r := gin.New()
r.POST("/chat", cs.HandleChat) r.POST("/chat", cs.HandleChat)

66
db.go
View File

@ -12,79 +12,79 @@ import (
"github.com/blevesearch/bleve/v2" "github.com/blevesearch/bleve/v2"
) )
type ReasonDB struct { type VisitDB struct {
reasonsDB []Reason visitsDB []Visit
reasonsIdx bleve.Index visitsIdx bleve.Index
} }
func NewReasonDB() ReasonDB { func NewVisitDB() VisitDB {
db := ReasonDB{} db := VisitDB{}
db.init() db.init()
return db return db
} }
func (rdb *ReasonDB) FindById(reasonId string) (Reason, error) { func (vdb *VisitDB) FindById(visitId string) (Visit, error) {
for _, reason := range rdb.reasonsDB { for _, visit := range vdb.visitsDB {
if reason.ID == reasonId { if visit.ID == visitId {
return reason, nil return visit, nil
} }
} }
return Reason{}, errors.New("reason not found") return Visit{}, errors.New("visit not found")
} }
func (rdb *ReasonDB) init() { func (vdb *VisitDB) init() {
idxPath := "reasons.bleve" idxPath := "visits.bleve"
if _, err := os.Stat(idxPath); err == nil { if _, err := os.Stat(idxPath); err == nil {
rdb.reasonsIdx, err = bleve.Open(idxPath) vdb.visitsIdx, err = bleve.Open(idxPath)
if err != nil { if err != nil {
panic(err) panic(err)
} }
} else if os.IsNotExist(err) { } else if os.IsNotExist(err) {
rdb.reasonsIdx, err = bleve.New(idxPath, bleve.NewIndexMapping()) vdb.visitsIdx, err = bleve.New(idxPath, bleve.NewIndexMapping())
if err != nil { if err != nil {
panic(err) panic(err)
} }
} else { } else {
panic(err) panic(err)
} }
err := rdb.loadYAMLDB("db.yaml") err := vdb.loadYAMLDB("db.yaml")
if err != nil { if err != nil {
logrus.Fatalf("Failed to load db.yaml: %v", err) logrus.Fatalf("Failed to load db.yaml: %v", err)
panic(err) panic(err)
} }
} }
func (rdb *ReasonDB) loadYAMLDB(path string) error { func (vdb *VisitDB) loadYAMLDB(path string) error {
data, err := ioutil.ReadFile(path) data, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
return err return err
} }
if err := yaml.Unmarshal(data, &rdb.reasonsDB); err != nil { if err := yaml.Unmarshal(data, &vdb.visitsDB); err != nil {
return err return err
} }
return rdb.indexReasons(rdb.reasonsDB) return vdb.indexVisits(vdb.visitsDB)
} }
func (rdb *ReasonDB) indexReasons(reasons []Reason) error { func (vdb *VisitDB) indexVisits(visits []Visit) error {
batch := rdb.reasonsIdx.NewBatch() batch := vdb.visitsIdx.NewBatch()
for _, reason := range reasons { for _, visit := range visits {
if err := batch.Index(reason.ID, reason); err != nil { if err := batch.Index(visit.ID, visit); err != nil {
return err return err
} }
} }
return rdb.reasonsIdx.Batch(batch) return vdb.visitsIdx.Batch(batch)
} }
// FindCandidates returns reasons with overlapping keywords // FindCandidates returns visits with overlapping keywords
func (rdb *ReasonDB) FindCandidates(keywords []string) ([]Reason, error) { func (vdb *VisitDB) FindCandidates(keywords []string) ([]Visit, error) {
query := bleve.NewMatchQuery(strings.Join(keywords, " ")) query := bleve.NewMatchQuery(strings.Join(keywords, " "))
search := bleve.NewSearchRequest(query) search := bleve.NewSearchRequest(query)
searchResults, err := rdb.reasonsIdx.Search(search) searchResults, err := vdb.visitsIdx.Search(search)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var candidates []Reason var candidates []Visit
for _, hit := range searchResults.Hits { for _, hit := range searchResults.Hits {
for _, r := range rdb.reasonsDB { for _, r := range vdb.visitsDB {
if r.ID == hit.ID { if r.ID == hit.ID {
candidates = append(candidates, r) candidates = append(candidates, r)
break break
@ -105,12 +105,12 @@ func sumProcedures(procs []Procedure) (int, int) {
return totalPrice, totalDuration return totalPrice, totalDuration
} }
// ReasonDBAPI allows mocking ReasonDB in other places // VisitDBAPI allows mocking VisitDB in other places
// Only public methods should be included // Only public methods should be included
type ReasonDBAPI interface { type VisitDBAPI interface {
FindById(reasonId string) (Reason, error) FindById(visitId string) (Visit, error)
FindCandidates(keywords []string) ([]Reason, error) FindCandidates(keywords []string) ([]Visit, error)
} }
var _ ReasonDBAPI = (*ReasonDB)(nil) var _ VisitDBAPI = (*VisitDB)(nil)

20
db.yaml
View File

@ -1,5 +1,5 @@
- id: deworming - id: deworming
reason: "Féregtelenítés kutyának" visit: "Féregtelenítés kutyának"
keywords: ["worm", "deworming", "parasite", "intestinal worm", "dog"] keywords: ["worm", "deworming", "parasite", "intestinal worm", "dog"]
procedures: procedures:
- name: "Alap vérvizsgálat" - name: "Alap vérvizsgálat"
@ -11,7 +11,7 @@
notes: "A kezelés előtt vérvizsgálat szükséges a biztonságos gyógyszeradás miatt." notes: "A kezelés előtt vérvizsgálat szükséges a biztonságos gyógyszeradás miatt."
- id: vaccination - id: vaccination
reason: "Oltás kutyának" visit: "Oltás kutyának"
keywords: ["vaccination", "vaccine", "to vaccinate", "dog disease", "rabies"] keywords: ["vaccination", "vaccine", "to vaccinate", "dog disease", "rabies"]
procedures: procedures:
- name: "Általános állapotfelmérés" - name: "Általános állapotfelmérés"
@ -23,7 +23,7 @@
notes: "A kutyák oltási programja eltérhet az életkortól és korábbi oltásoktól függően." notes: "A kutyák oltási programja eltérhet az életkortól és korábbi oltásoktól függően."
- id: neutering - id: neutering
reason: "Ivartalanítás macskának" visit: "Ivartalanítás macskának"
keywords: ["neutering", "surgery", "cat", "tomcat", "female cat"] keywords: ["neutering", "surgery", "cat", "tomcat", "female cat"]
procedures: procedures:
- name: "Műtéti előzetes vizsgálat" - name: "Műtéti előzetes vizsgálat"
@ -35,7 +35,7 @@
notes: "A műtét után 2-3 nap pihenő szükséges." notes: "A műtét után 2-3 nap pihenő szükséges."
- id: dental_cleaning - id: dental_cleaning
reason: "Fogkő eltávolítás kutyának" visit: "Fogkő eltávolítás kutyának"
keywords: ["tooth", "tartar", "dentition", "tooth cleaning", "dog teeth","plaque"] keywords: ["tooth", "tartar", "dentition", "tooth cleaning", "dog teeth","plaque"]
procedures: procedures:
- name: "Altatás előtti vizsgálat" - name: "Altatás előtti vizsgálat"
@ -47,7 +47,7 @@
notes: "Az altatás kockázata miatt minden esetben szükséges előzetes vizsgálat." notes: "Az altatás kockázata miatt minden esetben szükséges előzetes vizsgálat."
- id: checkup - id: checkup
reason: "Általános állapotfelmérés" visit: "Általános állapotfelmérés"
keywords: ["examination", "checkup", "check-up", "general assessment"] keywords: ["examination", "checkup", "check-up", "general assessment"]
procedures: procedures:
- name: "Teljes fizikai vizsgálat" - name: "Teljes fizikai vizsgálat"
@ -56,7 +56,7 @@
notes: "Évente legalább egyszer javasolt a rutin állapotfelmérés." notes: "Évente legalább egyszer javasolt a rutin állapotfelmérés."
- id: allergy - id: allergy
reason: "Allergiás tünetek vizsgálata" visit: "Allergiás tünetek vizsgálata"
keywords: ["allergy", "itching", "rash", "redness", "allergic"] keywords: ["allergy", "itching", "rash", "redness", "allergic"]
procedures: procedures:
- name: "Bőr- és vérvizsgálat" - name: "Bőr- és vérvizsgálat"
@ -65,7 +65,7 @@
notes: "Az allergia gyakran étel vagy környezeti tényező miatt alakul ki." notes: "Az allergia gyakran étel vagy környezeti tényező miatt alakul ki."
- id: ultrasound - id: ultrasound
reason: "Ultrahangos vizsgálat" visit: "Ultrahangos vizsgálat"
keywords: ["ultrasound", "abdomen", "examination", "US"] keywords: ["ultrasound", "abdomen", "examination", "US"]
procedures: procedures:
- name: "Ultrahang vizsgálat" - name: "Ultrahang vizsgálat"
@ -74,7 +74,7 @@
notes: "Terhesség vagy belső szervi problémák vizsgálatára gyakran használt módszer." notes: "Terhesség vagy belső szervi problémák vizsgálatára gyakran használt módszer."
- id: bloodwork - id: bloodwork
reason: "Laborvizsgálat vérből" visit: "Laborvizsgálat vérből"
keywords: ["blood", "blood test", "lab", "test"] keywords: ["blood", "blood test", "lab", "test"]
procedures: procedures:
- name: "Teljes vérkép" - name: "Teljes vérkép"
@ -83,7 +83,7 @@
notes: "Sok más vizsgálat alapja a laboreredmény." notes: "Sok más vizsgálat alapja a laboreredmény."
- id: xray - id: xray
reason: "Röntgenfelvétel" visit: "Röntgenfelvétel"
keywords: ["x-ray", "bone", "scan", "fracture"] keywords: ["x-ray", "bone", "scan", "fracture"]
procedures: procedures:
- name: "Röntgen vizsgálat" - name: "Röntgen vizsgálat"
@ -92,7 +92,7 @@
notes: "Törések, csontelváltozások vizsgálatára javasolt." notes: "Törések, csontelváltozások vizsgálatára javasolt."
- id: diarrhea - id: diarrhea
reason: "Hasmenés vizsgálata" visit: "Hasmenés vizsgálata"
keywords: ["diarrhea", "vomiting", "stomach", "intestine"] keywords: ["diarrhea", "vomiting", "stomach", "intestine"]
procedures: procedures:
- name: "Állatorvosi konzultáció" - name: "Állatorvosi konzultáció"

6
llm.go
View File

@ -71,7 +71,7 @@ func (llm *LLMClient) ExtractKeywords(ctx context.Context, message string) (map[
} }
// DisambiguateBestMatch calls LLM to pick best match from candidates // DisambiguateBestMatch calls LLM to pick best match from candidates
func (llm *LLMClient) DisambiguateBestMatch(ctx context.Context, message string, candidates []Reason) (string, error) { func (llm *LLMClient) DisambiguateBestMatch(ctx context.Context, message string, candidates []Visit) (string, error) {
format := map[string]interface{}{ format := map[string]interface{}{
"type": "object", "type": "object",
"properties": map[string]interface{}{ "properties": map[string]interface{}{
@ -138,7 +138,7 @@ func (llm *LLMClient) openAICompletion(ctx context.Context, prompt string, forma
return "", err return "", err
} }
if result.Message.Content == "" { if result.Message.Content == "" {
logrus.Warn("[LLM] openAICompletion: no content returned") logrus.Warn("[LLM] openAICompletion: no content returned %v body:[%v]", resp.Status, resp.Body)
return "", nil return "", nil
} }
logrus.WithField("content", result.Message.Content).Info("[LLM] openAICompletion: got content") logrus.WithField("content", result.Message.Content).Info("[LLM] openAICompletion: got content")
@ -150,7 +150,7 @@ func (llm *LLMClient) openAICompletion(ctx context.Context, prompt string, forma
type LLMClientAPI interface { type LLMClientAPI interface {
ExtractKeywords(ctx context.Context, message string) (map[string]interface{}, error) ExtractKeywords(ctx context.Context, message string) (map[string]interface{}, error)
DisambiguateBestMatch(ctx context.Context, message string, candidates []Reason) (string, error) DisambiguateBestMatch(ctx context.Context, message string, candidates []Visit) (string, error)
} }
var _ LLMClientAPI = (*LLMClient)(nil) var _ LLMClientAPI = (*LLMClient)(nil)

12
log.go
View File

@ -5,7 +5,7 @@ import (
) )
// logRequest logs incoming chat requests and extracted info // logRequest logs incoming chat requests and extracted info
func logRequest(req ChatRequest, keywords map[string]interface{}, candidates []Reason, bestID string, err error) { func logRequest(req ChatRequest, keywords map[string]interface{}, candidates []Visit, bestID string, err error) {
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
"message": req.Message, "message": req.Message,
"keywords": keywords, "keywords": keywords,
@ -15,7 +15,7 @@ func logRequest(req ChatRequest, keywords map[string]interface{}, candidates []R
}).Info("Chat request trace") }).Info("Chat request trace")
} }
func getCandidateIDs(candidates []Reason) []string { func getCandidateIDs(candidates []Visit) []string {
ids := make([]string, len(candidates)) ids := make([]string, len(candidates))
for i, c := range candidates { for i, c := range candidates {
ids[i] = c.ID ids[i] = c.ID
@ -23,17 +23,17 @@ func getCandidateIDs(candidates []Reason) []string {
return ids return ids
} }
// Procedure represents a single procedure for a visit reason // Procedure represents a single procedure for a visit
type Procedure struct { type Procedure struct {
Name string `yaml:"name" json:"name"` Name string `yaml:"name" json:"name"`
Price int `yaml:"price" json:"price"` Price int `yaml:"price" json:"price"`
DurationMin int `yaml:"duration_minutes" json:"duration_minutes"` DurationMin int `yaml:"duration_minutes" json:"duration_minutes"`
} }
// Reason represents a visit reason entry // Visit represents a visit entry
type Reason struct { type Visit struct {
ID string `yaml:"id" json:"id"` ID string `yaml:"id" json:"id"`
Reason string `yaml:"reason" json:"reason"` Visit string `yaml:"visit" json:"visit"`
Keywords []string `yaml:"keywords" json:"keywords"` Keywords []string `yaml:"keywords" json:"keywords"`
Procedures []Procedure `yaml:"procedures" json:"procedures"` Procedures []Procedure `yaml:"procedures" json:"procedures"`
Notes string `yaml:"notes" json:"notes,omitempty"` Notes string `yaml:"notes" json:"notes,omitempty"`

View File

@ -15,7 +15,7 @@ func main() {
logrus.Fatalf("Failed to load config.yaml: %v", err) logrus.Fatalf("Failed to load config.yaml: %v", err)
} }
logrus.Infof("Loaded config: %+v", appConfig) logrus.Infof("Loaded config: %+v", appConfig)
reasonDB := NewReasonDB() visitDB := NewVisitDB()
if err := loadUITemplate("ui.html"); err != nil { if err := loadUITemplate("ui.html"); err != nil {
logrus.Fatalf("Failed to load ui.html: %v", err) logrus.Fatalf("Failed to load ui.html: %v", err)
@ -25,7 +25,7 @@ func main() {
os.Getenv("OPENAI_BASE_URL"), os.Getenv("OPENAI_BASE_URL"),
os.Getenv("OPENAI_MODEL"), os.Getenv("OPENAI_MODEL"),
) )
chatService := NewChatService(llm, &reasonDB) chatService := NewChatService(llm, &visitDB)
r := gin.Default() r := gin.Default()
r.GET("/", func(c *gin.Context) { r.GET("/", func(c *gin.Context) {
c.Status(200) c.Status(200)

View File

@ -1,5 +1,5 @@
- id: deworming - id: deworming
reason: "Féregtelenítés kutyának" visit: "Féregtelenítés kutyának"
keywords: ["féreg", "féregtelenítés", "parazita", "bélféreg", "kutya"] keywords: ["féreg", "féregtelenítés", "parazita", "bélféreg", "kutya"]
procedures: procedures:
- name: "Alap vérvizsgálat" - name: "Alap vérvizsgálat"
@ -11,7 +11,7 @@
notes: "A kezelés előtt vérvizsgálat szükséges a biztonságos gyógyszeradás miatt." notes: "A kezelés előtt vérvizsgálat szükséges a biztonságos gyógyszeradás miatt."
- id: vaccination - id: vaccination
reason: "Oltás kutyának" visit: "Oltás kutyának"
keywords: ["oltás", "vakcina", "oltani", "kutyabetegség", "veszettség"] keywords: ["oltás", "vakcina", "oltani", "kutyabetegség", "veszettség"]
procedures: procedures:
- name: "Általános állapotfelmérés" - name: "Általános állapotfelmérés"
@ -23,7 +23,7 @@
notes: "A kutyák oltási programja eltérhet az életkortól és korábbi oltásoktól függően." notes: "A kutyák oltási programja eltérhet az életkortól és korábbi oltásoktól függően."
- id: neutering - id: neutering
reason: "Ivartalanítás macskának" visit: "Ivartalanítás macskának"
keywords: ["ivartalanítás", "műtét", "macska", "kandúr", "nőstény"] keywords: ["ivartalanítás", "műtét", "macska", "kandúr", "nőstény"]
procedures: procedures:
- name: "Műtéti előzetes vizsgálat" - name: "Műtéti előzetes vizsgálat"
@ -35,7 +35,7 @@
notes: "A műtét után 2-3 nap pihenő szükséges." notes: "A műtét után 2-3 nap pihenő szükséges."
- id: dental_cleaning - id: dental_cleaning
reason: "Fogkő eltávolítás kutyának" visit: "Fogkő eltávolítás kutyának"
keywords: ["fog", "fogkő", "fogsor", "fogtisztítás", "kutyafog"] keywords: ["fog", "fogkő", "fogsor", "fogtisztítás", "kutyafog"]
procedures: procedures:
- name: "Altatás előtti vizsgálat" - name: "Altatás előtti vizsgálat"
@ -47,7 +47,7 @@
notes: "Az altatás kockázata miatt minden esetben szükséges előzetes vizsgálat." notes: "Az altatás kockázata miatt minden esetben szükséges előzetes vizsgálat."
- id: checkup - id: checkup
reason: "Általános állapotfelmérés" visit: "Általános állapotfelmérés"
keywords: ["vizsgálat", "ellenőrzés", "checkup", "állapotfelmérés"] keywords: ["vizsgálat", "ellenőrzés", "checkup", "állapotfelmérés"]
procedures: procedures:
- name: "Teljes fizikai vizsgálat" - name: "Teljes fizikai vizsgálat"
@ -56,7 +56,7 @@
notes: "Évente legalább egyszer javasolt a rutin állapotfelmérés." notes: "Évente legalább egyszer javasolt a rutin állapotfelmérés."
- id: allergy - id: allergy
reason: "Allergiás tünetek vizsgálata" visit: "Allergiás tünetek vizsgálata"
keywords: ["allergia", "viszketés", "kiütés", "bőrpír", "allergiás"] keywords: ["allergia", "viszketés", "kiütés", "bőrpír", "allergiás"]
procedures: procedures:
- name: "Bőr- és vérvizsgálat" - name: "Bőr- és vérvizsgálat"
@ -65,7 +65,7 @@
notes: "Az allergia gyakran étel vagy környezeti tényező miatt alakul ki." notes: "Az allergia gyakran étel vagy környezeti tényező miatt alakul ki."
- id: ultrasound - id: ultrasound
reason: "Ultrahangos vizsgálat" visit: "Ultrahangos vizsgálat"
keywords: ["ultrahang", "has", "vizsgálat", "UH"] keywords: ["ultrahang", "has", "vizsgálat", "UH"]
procedures: procedures:
- name: "Ultrahang vizsgálat" - name: "Ultrahang vizsgálat"
@ -74,7 +74,7 @@
notes: "Terhesség vagy belső szervi problémák vizsgálatára gyakran használt módszer." notes: "Terhesség vagy belső szervi problémák vizsgálatára gyakran használt módszer."
- id: bloodwork - id: bloodwork
reason: "Laborvizsgálat vérből" visit: "Laborvizsgálat vérből"
keywords: ["vér", "vérvizsgálat", "labor", "teszt"] keywords: ["vér", "vérvizsgálat", "labor", "teszt"]
procedures: procedures:
- name: "Teljes vérkép" - name: "Teljes vérkép"
@ -83,7 +83,7 @@
notes: "Sok más vizsgálat alapja a laboreredmény." notes: "Sok más vizsgálat alapja a laboreredmény."
- id: xray - id: xray
reason: "Röntgenfelvétel" visit: "Röntgenfelvétel"
keywords: ["röntgen", "csont", "felvétel", "törés"] keywords: ["röntgen", "csont", "felvétel", "törés"]
procedures: procedures:
- name: "Röntgen vizsgálat" - name: "Röntgen vizsgálat"
@ -92,7 +92,7 @@
notes: "Törések, csontelváltozások vizsgálatára javasolt." notes: "Törések, csontelváltozások vizsgálatára javasolt."
- id: diarrhea - id: diarrhea
reason: "Hasmenés vizsgálata" visit: "Hasmenés vizsgálata"
keywords: ["hasmenés", "hányás", "gyomor", "bél"] keywords: ["hasmenés", "hányás", "gyomor", "bél"]
procedures: procedures:
- name: "Állatorvosi konzultáció" - name: "Állatorvosi konzultáció"