feat: CI/CD pipeline + Learning/Medicine/Travel services
Some checks failed
Build and Deploy GooSeek / build-backend (push) Failing after 1m4s
Build and Deploy GooSeek / build-webui (push) Failing after 1m2s
Build and Deploy GooSeek / deploy (push) Has been skipped

- Add Gitea Actions workflow for automated build & deploy
- Add K8s manifests: webui, travel-svc, medicine-svc, sandbox-svc
- Update kustomization for localhost:5000 registry
- Add ingress for gooseek.ru and api.gooseek.ru
- Learning cabinet with onboarding, courses, sandbox integration
- Medicine service with symptom analysis and doctor matching
- Travel service with itinerary planning
- Server setup scripts (NVIDIA/CUDA, K3s, Gitea runner)

Made-with: Cursor
This commit is contained in:
home
2026-03-02 20:25:44 +03:00
parent 08bd41e75c
commit ab48a0632b
92 changed files with 15562 additions and 2198 deletions

View File

@@ -628,6 +628,141 @@ func (s *Service) BuildRouteFromPoints(ctx context.Context, trip *Trip) (*RouteD
return s.openRoute.GetDirections(ctx, points, "driving-car")
}
// ValidateItineraryRequest is the input for itinerary validation.
type ValidateItineraryRequest struct {
Days []ValidateDay `json:"days"`
POIs []ValidatePOI `json:"pois,omitempty"`
Events []ValidateEvent `json:"events,omitempty"`
}
type ValidateDay struct {
Date string `json:"date"`
Items []ValidateItem `json:"items"`
}
type ValidateItem struct {
RefType string `json:"refType"`
RefID string `json:"refId"`
Title string `json:"title"`
StartTime string `json:"startTime,omitempty"`
EndTime string `json:"endTime,omitempty"`
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
Note string `json:"note,omitempty"`
Cost float64 `json:"cost,omitempty"`
Currency string `json:"currency,omitempty"`
}
type ValidatePOI struct {
ID string `json:"id"`
Name string `json:"name"`
Category string `json:"category"`
Schedule map[string]string `json:"schedule,omitempty"`
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
}
type ValidateEvent struct {
ID string `json:"id"`
Title string `json:"title"`
DateStart string `json:"dateStart,omitempty"`
DateEnd string `json:"dateEnd,omitempty"`
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
}
type ValidationWarning struct {
DayIdx int `json:"dayIdx"`
ItemIdx int `json:"itemIdx,omitempty"`
Message string `json:"message"`
}
type ValidateItineraryResponse struct {
Valid bool `json:"valid"`
Warnings []ValidationWarning `json:"warnings"`
Suggestions []ValidationWarning `json:"suggestions"`
}
func (s *Service) ValidateItinerary(ctx context.Context, req ValidateItineraryRequest) (*ValidateItineraryResponse, error) {
if s.llmClient == nil {
return &ValidateItineraryResponse{Valid: true, Warnings: []ValidationWarning{}, Suggestions: []ValidationWarning{}}, nil
}
daysJSON, _ := json.Marshal(req.Days)
poisJSON, _ := json.Marshal(req.POIs)
eventsJSON, _ := json.Marshal(req.Events)
prompt := fmt.Sprintf(`Проверь маршрут путешествия на логистические ошибки и предложи улучшения.
Маршрут по дням: %s
Доступные POI (с расписанием): %s
Доступные события (с датами): %s
Проверь:
1. Логистику: нет ли точек в разных концах города подряд без достаточного времени на переезд
2. Расписание: если POI имеет schedule и стоит в день когда закрыт — это ошибка
3. Даты событий: если событие стоит в день вне его dateStart-dateEnd — это ошибка
4. Реалистичность: не слишком ли много активностей в день (>6 основных)
5. Время: нет ли пересечений по времени
Верни ТОЛЬКО JSON:
{
"valid": true/false,
"warnings": [{"dayIdx": 0, "itemIdx": 2, "message": "причина"}],
"suggestions": [{"dayIdx": 0, "message": "рекомендация"}]
}
Если всё хорошо — warnings пустой массив, valid=true. Suggestions — необязательные рекомендации.`, string(daysJSON), string(poisJSON), string(eventsJSON))
var fullResponse strings.Builder
err := s.llmClient.StreamChat(ctx, []ChatMessage{
{Role: "user", Content: prompt},
}, func(chunk string) {
fullResponse.WriteString(chunk)
})
if err != nil {
return nil, fmt.Errorf("LLM validation failed: %w", err)
}
responseText := fullResponse.String()
jsonStart := strings.Index(responseText, "{")
jsonEnd := strings.LastIndex(responseText, "}")
if jsonStart < 0 || jsonEnd < 0 || jsonEnd <= jsonStart {
return &ValidateItineraryResponse{
Valid: false,
Warnings: []ValidationWarning{{
DayIdx: 0,
Message: "Не удалось проверить маршрут — повторите попытку",
}},
Suggestions: []ValidationWarning{},
}, nil
}
var result ValidateItineraryResponse
if err := json.Unmarshal([]byte(responseText[jsonStart:jsonEnd+1]), &result); err != nil {
return &ValidateItineraryResponse{
Valid: false,
Warnings: []ValidationWarning{{
DayIdx: 0,
Message: "Ошибка анализа маршрута — попробуйте ещё раз",
}},
Suggestions: []ValidationWarning{},
}, nil
}
if result.Warnings == nil {
result.Warnings = []ValidationWarning{}
}
if result.Suggestions == nil {
result.Suggestions = []ValidationWarning{}
}
return &result, nil
}
func (s *Service) EnrichTripWithAI(ctx context.Context, trip *Trip) error {
if len(trip.Route) == 0 {
return nil