Fix: Remove broken getKlausurApiUrl and clean up empty lines
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 42s
CI / test-go-edu-search (push) Successful in 34s
CI / test-python-klausur (push) Failing after 2m51s
CI / test-python-agent-core (push) Successful in 21s
CI / test-nodejs-website (push) Successful in 29s
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 42s
CI / test-go-edu-search (push) Successful in 34s
CI / test-python-klausur (push) Failing after 2m51s
CI / test-python-agent-core (push) Successful in 21s
CI / test-nodejs-website (push) Successful in 29s
sed replacement left orphaned hostname references in story page and empty lines in getApiBase functions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/breakpilot/edu-search-service/internal/database"
|
||||
)
|
||||
|
||||
// SubmitBatchExtractedData saves multiple AI-extracted profile data items
|
||||
// POST /api/v1/ai/extraction/submit-batch
|
||||
func (h *AIExtractionHandlers) SubmitBatchExtractedData(c *gin.Context) {
|
||||
var batch struct {
|
||||
Items []ExtractedProfileData `json:"items" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&batch); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
results := make([]gin.H, 0, len(batch.Items))
|
||||
successCount := 0
|
||||
errorCount := 0
|
||||
|
||||
for _, item := range batch.Items {
|
||||
// Get existing staff record
|
||||
staff, err := h.repo.GetStaff(c.Request.Context(), item.StaffID)
|
||||
if err != nil {
|
||||
results = append(results, gin.H{
|
||||
"staff_id": item.StaffID,
|
||||
"status": "error",
|
||||
"error": "Staff not found",
|
||||
})
|
||||
errorCount++
|
||||
continue
|
||||
}
|
||||
|
||||
// Apply updates (same logic as single submit)
|
||||
updated := false
|
||||
|
||||
if item.Email != "" && (staff.Email == nil || *staff.Email == "") {
|
||||
staff.Email = &item.Email
|
||||
updated = true
|
||||
}
|
||||
if item.Phone != "" && (staff.Phone == nil || *staff.Phone == "") {
|
||||
staff.Phone = &item.Phone
|
||||
updated = true
|
||||
}
|
||||
if item.Office != "" && (staff.Office == nil || *staff.Office == "") {
|
||||
staff.Office = &item.Office
|
||||
updated = true
|
||||
}
|
||||
if item.Position != "" && (staff.Position == nil || *staff.Position == "") {
|
||||
staff.Position = &item.Position
|
||||
updated = true
|
||||
}
|
||||
if item.PositionType != "" && (staff.PositionType == nil || *staff.PositionType == "") {
|
||||
staff.PositionType = &item.PositionType
|
||||
updated = true
|
||||
}
|
||||
if item.TeamRole != "" && (staff.TeamRole == nil || *staff.TeamRole == "") {
|
||||
staff.TeamRole = &item.TeamRole
|
||||
updated = true
|
||||
}
|
||||
if len(item.ResearchInterests) > 0 && len(staff.ResearchInterests) == 0 {
|
||||
staff.ResearchInterests = item.ResearchInterests
|
||||
updated = true
|
||||
}
|
||||
if item.ORCID != "" && (staff.ORCID == nil || *staff.ORCID == "") {
|
||||
staff.ORCID = &item.ORCID
|
||||
updated = true
|
||||
}
|
||||
|
||||
// Update last verified
|
||||
now := time.Now()
|
||||
staff.LastVerified = &now
|
||||
|
||||
if updated {
|
||||
err = h.repo.CreateStaff(c.Request.Context(), staff)
|
||||
if err != nil {
|
||||
results = append(results, gin.H{
|
||||
"staff_id": item.StaffID,
|
||||
"status": "error",
|
||||
"error": err.Error(),
|
||||
})
|
||||
errorCount++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
results = append(results, gin.H{
|
||||
"staff_id": item.StaffID,
|
||||
"status": "success",
|
||||
"updated": updated,
|
||||
})
|
||||
successCount++
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"results": results,
|
||||
"success_count": successCount,
|
||||
"error_count": errorCount,
|
||||
"total": len(batch.Items),
|
||||
})
|
||||
}
|
||||
|
||||
// InstituteHierarchyTask represents an institute page to crawl for hierarchy
|
||||
type InstituteHierarchyTask struct {
|
||||
InstituteURL string `json:"institute_url"`
|
||||
InstituteName string `json:"institute_name,omitempty"`
|
||||
UniversityID uuid.UUID `json:"university_id"`
|
||||
}
|
||||
|
||||
// GetInstitutePages returns institute pages that need hierarchy crawling
|
||||
// GET /api/v1/ai/extraction/institutes?university_id=...
|
||||
func (h *AIExtractionHandlers) GetInstitutePages(c *gin.Context) {
|
||||
var universityID *uuid.UUID
|
||||
if uniIDStr := c.Query("university_id"); uniIDStr != "" {
|
||||
id, err := uuid.Parse(uniIDStr)
|
||||
if err == nil {
|
||||
universityID = &id
|
||||
}
|
||||
}
|
||||
|
||||
// Get unique institute/department URLs from staff profiles
|
||||
params := database.StaffSearchParams{
|
||||
UniversityID: universityID,
|
||||
Limit: 1000,
|
||||
}
|
||||
|
||||
result, err := h.repo.SearchStaff(c.Request.Context(), params)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Collect unique source URLs (these are typically department pages)
|
||||
urlSet := make(map[string]bool)
|
||||
var tasks []InstituteHierarchyTask
|
||||
|
||||
for _, staff := range result.Staff {
|
||||
if staff.SourceURL != nil && *staff.SourceURL != "" {
|
||||
url := *staff.SourceURL
|
||||
if !urlSet[url] {
|
||||
urlSet[url] = true
|
||||
tasks = append(tasks, InstituteHierarchyTask{
|
||||
InstituteURL: url,
|
||||
UniversityID: staff.UniversityID,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"institutes": tasks,
|
||||
"total": len(tasks),
|
||||
})
|
||||
}
|
||||
|
||||
// InstituteHierarchyData represents hierarchy data extracted from an institute page
|
||||
type InstituteHierarchyData struct {
|
||||
InstituteURL string `json:"institute_url" binding:"required"`
|
||||
UniversityID uuid.UUID `json:"university_id" binding:"required"`
|
||||
InstituteName string `json:"institute_name,omitempty"`
|
||||
|
||||
// Leadership
|
||||
LeaderName string `json:"leader_name,omitempty"`
|
||||
LeaderTitle string `json:"leader_title,omitempty"` // e.g., "Professor", "Lehrstuhlinhaber"
|
||||
|
||||
// Staff organization
|
||||
StaffGroups []struct {
|
||||
Role string `json:"role"` // e.g., "Leitung", "Wissenschaftliche Mitarbeiter", "Sekretariat"
|
||||
Members []string `json:"members"` // Names of people in this group
|
||||
} `json:"staff_groups,omitempty"`
|
||||
|
||||
// Teaching info (Lehrveranstaltungen)
|
||||
TeachingCourses []struct {
|
||||
Title string `json:"title"`
|
||||
Teacher string `json:"teacher,omitempty"`
|
||||
} `json:"teaching_courses,omitempty"`
|
||||
}
|
||||
|
||||
// SubmitInstituteHierarchy saves hierarchy data from an institute page
|
||||
// POST /api/v1/ai/extraction/institutes/submit
|
||||
func (h *AIExtractionHandlers) SubmitInstituteHierarchy(c *gin.Context) {
|
||||
var data InstituteHierarchyData
|
||||
if err := c.ShouldBindJSON(&data); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Find or create department
|
||||
dept := &database.Department{
|
||||
UniversityID: data.UniversityID,
|
||||
Name: data.InstituteName,
|
||||
}
|
||||
if data.InstituteURL != "" {
|
||||
dept.URL = &data.InstituteURL
|
||||
}
|
||||
|
||||
err := h.repo.CreateDepartment(c.Request.Context(), dept)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create department: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Find leader and set as supervisor for all staff in this institute
|
||||
var leaderID *uuid.UUID
|
||||
if data.LeaderName != "" {
|
||||
// Search for leader
|
||||
leaderParams := database.StaffSearchParams{
|
||||
Query: data.LeaderName,
|
||||
UniversityID: &data.UniversityID,
|
||||
Limit: 1,
|
||||
}
|
||||
result, err := h.repo.SearchStaff(c.Request.Context(), leaderParams)
|
||||
if err == nil && len(result.Staff) > 0 {
|
||||
leaderID = &result.Staff[0].ID
|
||||
|
||||
// Update leader with department and role
|
||||
leader := &result.Staff[0]
|
||||
leader.DepartmentID = &dept.ID
|
||||
roleLeitung := "leitung"
|
||||
leader.TeamRole = &roleLeitung
|
||||
leader.IsProfessor = true
|
||||
if data.LeaderTitle != "" {
|
||||
leader.AcademicTitle = &data.LeaderTitle
|
||||
}
|
||||
h.repo.CreateStaff(c.Request.Context(), leader)
|
||||
}
|
||||
}
|
||||
|
||||
// Process staff groups
|
||||
updatedCount := 0
|
||||
for _, group := range data.StaffGroups {
|
||||
for _, memberName := range group.Members {
|
||||
// Find staff member
|
||||
memberParams := database.StaffSearchParams{
|
||||
Query: memberName,
|
||||
UniversityID: &data.UniversityID,
|
||||
Limit: 1,
|
||||
}
|
||||
result, err := h.repo.SearchStaff(c.Request.Context(), memberParams)
|
||||
if err != nil || len(result.Staff) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
member := &result.Staff[0]
|
||||
member.DepartmentID = &dept.ID
|
||||
member.TeamRole = &group.Role
|
||||
|
||||
// Set supervisor if leader was found and this is not the leader
|
||||
if leaderID != nil && member.ID != *leaderID {
|
||||
member.SupervisorID = leaderID
|
||||
}
|
||||
|
||||
h.repo.CreateStaff(c.Request.Context(), member)
|
||||
updatedCount++
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": "success",
|
||||
"department_id": dept.ID,
|
||||
"leader_id": leaderID,
|
||||
"members_updated": updatedCount,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user