# club_assignment_stv_hours_auto_v8_final_output.R
library(dplyr)
library(readr)
library(tidyr)
library(stringr)
# --- Ρυθμίσεις ---
required_hours <- 10L
responses_file <- "responses.csv"
clubs_file <- "clubs.csv"
output_prefix_assignments <- "club_assignments"
auto_select_count <- 15L
min_pool_size <- 15L
set.seed(42)
# --- Διαβάζουμε δεδομένα ---
responses <- read_csv(responses_file, show_col_types = FALSE)
clubs <- read_csv(clubs_file, show_col_types = FALSE)
# --- Καθαρισμός και εναρμόνιση Clubs ---
colnames(clubs) <- tolower(trimws(colnames(clubs)))
club_name_source <- if("club_name" %in% colnames(clubs)) "club_name" else "club_id"
if("clubname" %in% colnames(clubs) & !("club_name" %in% colnames(clubs))) {
club_name_source <- "clubname"
}
if("hours_per_week" %in% colnames(clubs)) {
clubs <- clubs |> rename(hours = hours_per_week)
} else if(!("hours" %in% colnames(clubs))) {
hours_col_name <- setdiff(colnames(clubs), c("club_id", "club_name", "clubname"))[1]
if (!is.na(hours_col_name)) {
clubs <- clubs |> rename(hours = hours_col_name)
}
}
clubs <- clubs |>
mutate(
club_id = trimws(tolower(club_id)),
club_name = .data[[club_name_source]],
club_name = trimws(club_name),
hours = as.integer(hours),
hours = if_else(is.na(hours), 2L, hours)
) |>
select(club_id, club_name, hours)
# --- Καθαρισμός και εναρμόνιση Responses ---
student_col <- colnames(responses)[1]
responses <- responses |>
mutate(across(all_of(student_col), as.character))
initial_club_cols <- trimws(tolower(colnames(responses)[-1]))
colnames(responses) <- c(student_col, initial_club_cols)
responses_long_master <- responses |>
pivot_longer(cols = -all_of(student_col),
names_to = "ClubID", values_to = "Rank") |>
mutate(
ClubID = trimws(tolower(ClubID)),
Rank = as.integer(Rank)
) |>
filter(!is.na(Rank))
# --- Ενεργές Καταστάσεις ---
active_clubs <- initial_club_cols
student_hours <- data.frame(StudentID = unique(responses[[student_col]]),
HoursAssigned = 0L,
stringsAsFactors = FALSE)
colnames(student_hours)[1] <- student_col
club_assignments <- data.frame(StudentID = character(), ClubID = character(), ClubName = character(), stringsAsFactors = FALSE)
colnames(club_assignments)[1] <- student_col
round <- 1L
cat(sprintf("--- Έναρξη Διαδικασίας Κατανομής Ομίλων (Στόχος: %d Ώρες/Μαθητή, Min Pool: %d) ---\n", required_hours, min_pool_size))
repeat {
# --- 1. Υπολογισμός Πρώτων Προτιμήσεων (Τρέχουσα κατάσταση) ---
active_students <- student_hours |>
filter(HoursAssigned < required_hours) |>
pull(all_of(student_col))
current_prefs <- responses_long_master |>
filter(ClubID %in% active_clubs) |>
filter(get(student_col) %in% active_students) |>
group_by(across(all_of(student_col))) |>
filter(Rank == min(Rank, na.rm = TRUE)) |>
ungroup()
counts <- current_prefs |>
count(ClubID, name = "FirstPrefCount") |>
right_join(data.frame(ClubID = active_clubs), by = "ClubID") |>
replace_na(list(FirstPrefCount = 0))
cat(sprintf("\n*** Γύρος %d ***\n", round))
# Έλεγχος Τερματισμού
if(length(active_clubs) == 0 || length(active_students) == 0) {
students_not_reached_target <- student_hours |>
filter(HoursAssigned < required_hours)
if(length(active_clubs) == 0 && nrow(students_not_reached_target) > 0) {
cat(sprintf("\nΤΕΡΜΑΤΙΣΜΟΣ ΛΟΓΩ ΕΞΑΝΤΛΗΣΗΣ ΟΜΙΛΩΝ: %d μαθητές δεν συμπλήρωσαν τις %d ώρες.\n",
nrow(students_not_reached_target), required_hours))
} else {
cat("\nΤερματισμός: Όλοι οι μαθητές έχουν συμπληρώσει τις ώρες τους.\n")
}
break
}
# --- 2. Εντοπισμός Δημοφιλέστερου Ομίλου ---
max_count <- max(counts$FirstPrefCount)
if (max_count == 0) {
cat("Δεν υπάρχουν πλέον πρώτες προτιμήσεις για ενεργούς Ομίλους. Τερματισμός.\n")
break
}
pop_candidates <- counts |> filter(FirstPrefCount == max_count)
club_to_select <- sort(pop_candidates$ClubID)[1]
match_idx <- match(club_to_select, clubs$club_id)
club_name <- clubs$club_name[match_idx]
club_hours <- clubs$hours[match_idx]
cat(sprintf("-> Δημοφιλέστερος Όμιλος: %s (%s) με %d **πρώτες** προτιμήσεις (Ώρες: %d).\n",
club_to_select, club_name, max_count, club_hours))
# --- 3. Διεύρυνση Pool Υποψηφίων (ΛΟΓΙΚΗ ΕΛΑΧΙΣΤΟΥ 15) ---
students_for_club_pool <- current_prefs |>
filter(ClubID == club_to_select) |>
pull(all_of(student_col))
if(length(students_for_club_pool) < min_pool_size) {
cat(sprintf("-> Οι πρώτες προτιμήσεις (%d) είναι < %d. Αναζήτηση σε χαμηλότερα ranks...\n",
length(students_for_club_pool), min_pool_size))
club_rankings <- responses_long_master |>
filter(ClubID == club_to_select) |>
filter(get(student_col) %in% active_students) |>
arrange(Rank) |>
select(all_of(student_col), Rank)
max_rank_to_include <- club_rankings$Rank[min(nrow(club_rankings), min_pool_size)]
students_for_club_pool <- club_rankings |>
filter(Rank <= max_rank_to_include) |>
pull(all_of(student_col))
cat(sprintf("-> Το pool διευρύνθηκε για να συμπεριλάβει μαθητές μέχρι την %dη προτίμηση (Συνολικά: %d υποψήφιοι).\n",
max_rank_to_include, length(students_for_club_pool)))
}
# --- 4. Αυτόματη Επιλογή 15 Μαθητών (Προσομοίωση) ---
if (length(students_for_club_pool) > 0) {
num_to_select <- min(auto_select_count, length(students_for_club_pool))
final_selected_students <- sample(students_for_club_pool, size = num_to_select, replace = FALSE)
cat(sprintf("-> Επιλέγονται τυχαία %d μαθητές από το pool.\n", num_to_select))
} else {
final_selected_students <- character(0)
}
# --- 5. Καταχώρηση και Μεταφορά Ψήφων ---
if(length(final_selected_students) > 0) {
new_assignments <- data.frame(StudentID = final_selected_students,
ClubID = club_to_select,
ClubName = club_name,
stringsAsFactors = FALSE)
colnames(new_assignments)[1] <- student_col
club_assignments <- bind_rows(club_assignments, new_assignments)
for (sid in final_selected_students) {
student_hours[student_hours[[student_col]] == sid, "HoursAssigned"] <-
student_hours[student_hours[[student_col]] == sid, "HoursAssigned"] + club_hours
}
cat(sprintf("-> Καταχωρήθηκαν %d μαθητές στον Όμιλο %s.\n", length(final_selected_students), club_name))
assignment_file <- sprintf("%s_%s.csv", output_prefix_assignments, club_to_select)
write_csv(new_assignments, assignment_file)
}
# Αφαίρεση του Ομίλου από τη λίστα των ενεργών (Βάσει του Κανόνα του Χρήστη)
active_clubs <- setdiff(active_clubs, club_to_select)
# --- 6. Ενημέρωση Ranks (Μεταφορά Ψήφων) ---
responses_long_master <- responses_long_master |>
mutate(Rank = ifelse(ClubID == club_to_select, NA_integer_, Rank)) |>
filter(!is.na(Rank))
responses_long_master <- responses_long_master |>
group_by(across(all_of(student_col))) |>
mutate(Rank = rank(Rank, ties.method = "min")) |>
ungroup()
# --- 7. Αποθήκευση Ενδιάμεσων Αρχείων ---
write_csv(student_hours, "student_hours_log.csv")
round <- round + 1L
}
# --- Τελική Καταγραφή & Νέο Αρχείο (Student Club List) ---
cat("\nΟλοκληρώθηκε η διαδικασία κατανομής.\n")
write_csv(club_assignments, "club_assignments_final.csv")
# --- ΒΗΜΑ 8: ΔΗΜΙΟΥΡΓΙΑ ΑΡΧΕΙΟΥ ΜΕ ΛΙΣΤΑ ΟΜΙΛΩΝ ΑΝΑ ΜΑΘΗΤΗ ---
student_club_list <- club_assignments |>
# Ομαδοποίηση ανά μαθητή
group_by(across(all_of(student_col))) |>
# Συνένωση των ClubName σε μία συμβολοσειρά, χωρισμένη με κόμμα
summarise(
Clubs_Assigned = paste(ClubName, collapse = ", "),
.groups = 'drop'
)
# Ενσωμάτωση των συνολικών ωρών για πληρέστερη εικόνα
student_club_list <- student_club_list |>
left_join(student_hours, by = student_col) |>
select(all_of(student_col), Clubs_Assigned, HoursAssigned)
# Αποθήκευση του νέου αρχείου
write_csv(student_club_list, "student_club_list.csv")
cat("Δημιουργήθηκε το αρχείο: student_club_list.csv (Λίστα Ομίλων ανά Μαθητή)\n")
# --- ΒΗΜΑ 9: ΕΥΡΕΣΗ ΟΜΙΛΩΝ ΜΕ ΛΙΓΟΤΕΡΟΥΣ ΑΠΟ 10 ΜΑΘΗΤΕΣ ---
# Υπολογίζουμε τους μαθητές ανά Όμιλο
clubs_less_than_10 <- club_assignments |>
# Ομαδοποίηση ανά όνομα Ομίλου
group_by(ClubName) |>
# Καταμέτρηση των αναθέσεων (μαθητών)
summarise(
Students_Count = n(),
.groups = 'drop'
) |>
# Φιλτράρισμα μόνο για όσους έχουν λιγότερους από 10 μαθητές
filter(Students_Count < 10) |>
# Ταξινόμηση για ευκολότερη επισκόπηση
arrange(Students_Count, ClubName)
# Εκτύπωση αποτελεσμάτων στην κονσόλα
cat("\n\n--- ΟΜΙΛΟΙ ΜΕ ΛΙΓΟΤΕΡΟΥΣ ΑΠΟ 10 ΜΑΘΗΤΕΣ ---\n")
print(clubs_less_than_10)
# Αποθήκευση του αποτελέσματος σε αρχείο CSV
write_csv(clubs_less_than_10, "clubs_less_than_10_students.csv")
cat("\nΑποθηκεύτηκαν τα αποτελέσματα στο: clubs_less_than_10_students.csv\n")