feat: CI/CD pipeline + Learning/Medicine/Travel services
- 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:
118
backend/internal/learning/plan_builder.go
Normal file
118
backend/internal/learning/plan_builder.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package learning
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/gooseek/backend/internal/db"
|
||||
"github.com/gooseek/backend/internal/llm"
|
||||
)
|
||||
|
||||
type PersonalPlan struct {
|
||||
Modules []PlanModule `json:"modules"`
|
||||
TotalHours int `json:"total_hours"`
|
||||
DifficultyAdjusted string `json:"difficulty_adjusted"`
|
||||
PersonalizationNote string `json:"personalization_notes"`
|
||||
MilestoneProject string `json:"milestone_project"`
|
||||
}
|
||||
|
||||
type PlanModule struct {
|
||||
Index int `json:"index"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Skills []string `json:"skills"`
|
||||
EstimatedHrs int `json:"estimated_hours"`
|
||||
PracticeFocus string `json:"practice_focus"`
|
||||
TaskCount int `json:"task_count"`
|
||||
IsCheckpoint bool `json:"is_checkpoint"`
|
||||
}
|
||||
|
||||
func BuildPersonalPlan(ctx context.Context, llmClient llm.Client, course *db.LearningCourse, profileJSON string) (json.RawMessage, error) {
|
||||
profileInfo := "Профиль неизвестен — план по умолчанию."
|
||||
if profileJSON != "" && profileJSON != "{}" {
|
||||
profileInfo = "Профиль ученика:\n" + truncateStr(profileJSON, 2000)
|
||||
}
|
||||
|
||||
outlineStr := string(course.BaseOutline)
|
||||
if len(outlineStr) > 4000 {
|
||||
outlineStr = outlineStr[:4000]
|
||||
}
|
||||
|
||||
prompt := fmt.Sprintf(`Ты — ведущий методолог обучения в IT с 10-летним опытом. Адаптируй базовый план курса под конкретного ученика.
|
||||
|
||||
Курс: %s
|
||||
Описание: %s
|
||||
Сложность: %s
|
||||
Длительность: %d часов
|
||||
|
||||
Базовый план:
|
||||
%s
|
||||
|
||||
%s
|
||||
|
||||
ТРЕБОВАНИЯ:
|
||||
1. Минимум теории, максимум боевой практики (как на реальных проектах в РФ)
|
||||
2. Каждый модуль = конкретное практическое задание из реального проекта
|
||||
3. Прогрессия: от простого к сложному, учитывая текущий уровень ученика
|
||||
4. Каждый 3-й модуль — checkpoint (мини-проект для проверки навыков)
|
||||
5. Финальный milestone project — полноценный проект для портфолио
|
||||
6. Учитывай стек и опыт ученика — не повторяй то, что он уже знает
|
||||
|
||||
Ответь строго JSON:
|
||||
{
|
||||
"modules": [
|
||||
{
|
||||
"index": 0,
|
||||
"title": "Название модуля",
|
||||
"description": "Что изучаем и делаем",
|
||||
"skills": ["навык1"],
|
||||
"estimated_hours": 4,
|
||||
"practice_focus": "Конкретная практическая задача из реального проекта",
|
||||
"task_count": 3,
|
||||
"is_checkpoint": false
|
||||
}
|
||||
],
|
||||
"total_hours": 40,
|
||||
"difficulty_adjusted": "intermediate",
|
||||
"personalization_notes": "Как план адаптирован под ученика",
|
||||
"milestone_project": "Описание финального проекта для портфолио"
|
||||
}`, course.Title, course.ShortDescription, course.Difficulty, course.DurationHours, outlineStr, profileInfo)
|
||||
|
||||
var plan PersonalPlan
|
||||
if err := generateAndParse(ctx, llmClient, prompt, &plan, 2); err != nil {
|
||||
return course.BaseOutline, fmt.Errorf("plan generation failed, using base outline: %w", err)
|
||||
}
|
||||
|
||||
if len(plan.Modules) == 0 {
|
||||
return course.BaseOutline, nil
|
||||
}
|
||||
|
||||
for i := range plan.Modules {
|
||||
plan.Modules[i].Index = i
|
||||
if plan.Modules[i].TaskCount == 0 {
|
||||
plan.Modules[i].TaskCount = 2
|
||||
}
|
||||
}
|
||||
|
||||
if plan.TotalHours == 0 {
|
||||
total := 0
|
||||
for _, m := range plan.Modules {
|
||||
total += m.EstimatedHrs
|
||||
}
|
||||
plan.TotalHours = total
|
||||
}
|
||||
|
||||
result, err := json.Marshal(plan)
|
||||
if err != nil {
|
||||
return course.BaseOutline, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func truncateStr(s string, maxLen int) string {
|
||||
if len(s) <= maxLen {
|
||||
return s
|
||||
}
|
||||
return s[:maxLen] + "..."
|
||||
}
|
||||
Reference in New Issue
Block a user