Files
gooseek/backend/internal/learning/plan_builder.go
home ab48a0632b
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
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
2026-03-02 20:25:44 +03:00

119 lines
3.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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] + "..."
}