241 lines
7.9 KiB
JavaScript
241 lines
7.9 KiB
JavaScript
const router = require('express').Router();
|
|
const { Rating, Team, Expert, Criteria } = require('../models');
|
|
|
|
// GET /api/ratings - получить все оценки (с фильтрами)
|
|
router.get('/', async (req, res) => {
|
|
try {
|
|
const { expertId, teamId, eventId } = req.query;
|
|
const filter = {};
|
|
|
|
if (expertId) filter.expertId = expertId;
|
|
if (teamId) filter.teamId = teamId;
|
|
if (eventId) filter.eventId = eventId;
|
|
|
|
const ratings = await Rating.find(filter)
|
|
.populate('expertId', 'fullName')
|
|
.populate('teamId', 'name type')
|
|
.sort({ createdAt: -1 });
|
|
|
|
res.json(ratings);
|
|
} catch (error) {
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// GET /api/ratings/team/:teamId - оценки конкретной команды
|
|
router.get('/team/:teamId', async (req, res) => {
|
|
try {
|
|
const ratings = await Rating.find({ teamId: req.params.teamId })
|
|
.populate('expertId', 'fullName')
|
|
.populate('teamId', 'name type projectName');
|
|
|
|
res.json(ratings);
|
|
} catch (error) {
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// GET /api/ratings/expert/:expertId - оценки конкретного эксперта
|
|
router.get('/expert/:expertId', async (req, res) => {
|
|
try {
|
|
const ratings = await Rating.find({ expertId: req.params.expertId })
|
|
.populate('teamId', 'name type projectName');
|
|
|
|
res.json(ratings);
|
|
} catch (error) {
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// GET /api/ratings/statistics - статистика с группировкой по командам
|
|
router.get('/statistics', async (req, res) => {
|
|
try {
|
|
const { type, eventId } = req.query;
|
|
|
|
// Получаем все команды
|
|
const teamFilter = { isActive: true };
|
|
if (type) teamFilter.type = type;
|
|
if (eventId) teamFilter.eventId = eventId;
|
|
const teams = await Team.find(teamFilter);
|
|
|
|
// Получаем все оценки
|
|
const ratingFilter = {};
|
|
if (eventId) ratingFilter.eventId = eventId;
|
|
const ratings = await Rating.find(ratingFilter)
|
|
.populate('expertId', 'fullName')
|
|
.populate('teamId', 'name type projectName');
|
|
|
|
// Группируем оценки по командам
|
|
const statistics = teams.map(team => {
|
|
const teamRatings = ratings.filter(r => r.teamId && r.teamId._id.toString() === team._id.toString());
|
|
|
|
// Считаем средние оценки по критериям
|
|
const criteriaStats = {};
|
|
teamRatings.forEach(rating => {
|
|
rating.ratings.forEach(item => {
|
|
if (!criteriaStats[item.criterionName]) {
|
|
criteriaStats[item.criterionName] = {
|
|
name: item.criterionName,
|
|
scores: [],
|
|
average: 0
|
|
};
|
|
}
|
|
criteriaStats[item.criterionName].scores.push(item.score);
|
|
});
|
|
});
|
|
|
|
// Вычисляем средние значения
|
|
Object.keys(criteriaStats).forEach(key => {
|
|
const scores = criteriaStats[key].scores;
|
|
criteriaStats[key].average = scores.reduce((sum, s) => sum + s, 0) / scores.length;
|
|
});
|
|
|
|
// Считаем общий балл команды (среднее от всех экспертов)
|
|
const totalScore = teamRatings.length > 0
|
|
? teamRatings.reduce((sum, r) => sum + r.totalScore, 0) / teamRatings.length
|
|
: 0;
|
|
|
|
return {
|
|
team: {
|
|
_id: team._id,
|
|
name: team.name,
|
|
type: team.type,
|
|
projectName: team.projectName
|
|
},
|
|
ratings: teamRatings.map(r => ({
|
|
expert: r.expertId ? r.expertId.fullName : 'Unknown',
|
|
criteria: r.ratings,
|
|
totalScore: r.totalScore
|
|
})),
|
|
criteriaStats: Object.values(criteriaStats),
|
|
totalScore: totalScore,
|
|
ratingsCount: teamRatings.length
|
|
};
|
|
});
|
|
|
|
res.json(statistics);
|
|
} catch (error) {
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// GET /api/ratings/top3 - топ-3 команды и топ-3 участники отдельно
|
|
// ВАЖНО: всегда возвращаем объект вида { teams: Top3Item[], participants: Top3Item[] },
|
|
// чтобы фронтенд мог безопасно работать с data.teams / data.participants
|
|
router.get('/top3', async (req, res) => {
|
|
try {
|
|
const { type, eventId } = req.query;
|
|
|
|
// Получаем все активные команды/участников
|
|
const teamFilter = { isActive: true };
|
|
if (eventId) teamFilter.eventId = eventId;
|
|
const teams = await Team.find(teamFilter);
|
|
|
|
const ratingFilter = {};
|
|
if (eventId) ratingFilter.eventId = eventId;
|
|
const ratings = await Rating.find(ratingFilter).populate('teamId', 'name type projectName');
|
|
|
|
const calculateTop3 = (sourceTeams) => {
|
|
const teamScores = sourceTeams.map((team) => {
|
|
const teamRatings = ratings.filter(
|
|
(r) => r.teamId && r.teamId._id.toString() === team._id.toString()
|
|
);
|
|
|
|
const totalScore =
|
|
teamRatings.length > 0
|
|
? teamRatings.reduce((sum, r) => sum + r.totalScore, 0) / teamRatings.length
|
|
: 0;
|
|
|
|
return {
|
|
team: {
|
|
_id: team._id,
|
|
name: team.name,
|
|
type: team.type,
|
|
projectName: team.projectName
|
|
},
|
|
totalScore,
|
|
ratingsCount: teamRatings.length
|
|
};
|
|
});
|
|
|
|
return teamScores
|
|
.filter((t) => t.ratingsCount > 0)
|
|
.sort((a, b) => b.totalScore - a.totalScore)
|
|
.slice(0, 3);
|
|
};
|
|
|
|
const teamEntities = teams.filter((t) => t.type === 'team');
|
|
const participantEntities = teams.filter((t) => t.type === 'participant');
|
|
|
|
const teamTop3 = calculateTop3(teamEntities);
|
|
const participantTop3 = calculateTop3(participantEntities);
|
|
|
|
// Параметр type управляет только содержимым, но не форматом ответа
|
|
const response = {
|
|
teams: !type || type === 'team' ? teamTop3 : [],
|
|
participants: !type || type === 'participant' ? participantTop3 : []
|
|
};
|
|
|
|
res.json(response);
|
|
} catch (error) {
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// POST /api/ratings - создать/обновить оценку эксперта
|
|
router.post('/', async (req, res) => {
|
|
try {
|
|
const { eventId, expertId, teamId, ratings } = req.body;
|
|
|
|
if (!eventId || !expertId || !teamId || !ratings || !Array.isArray(ratings)) {
|
|
return res.status(400).json({ error: 'EventId, expert ID, team ID, and ratings array are required' });
|
|
}
|
|
|
|
// Проверяем существование эксперта и команды
|
|
const expert = await Expert.findById(expertId);
|
|
const team = await Team.findById(teamId);
|
|
|
|
if (!expert) {
|
|
return res.status(404).json({ error: 'Expert not found' });
|
|
}
|
|
|
|
if (!team) {
|
|
return res.status(404).json({ error: 'Team not found' });
|
|
}
|
|
|
|
// Проверяем, активна ли команда
|
|
if (!team.isActive) {
|
|
return res.status(400).json({ error: 'Team voting is disabled' });
|
|
}
|
|
|
|
// Ищем существующую оценку
|
|
let rating = await Rating.findOne({ eventId, expertId, teamId });
|
|
|
|
if (rating) {
|
|
// Обновляем существующую оценку
|
|
rating.ratings = ratings;
|
|
await rating.save();
|
|
} else {
|
|
// Создаем новую оценку
|
|
rating = await Rating.create({
|
|
eventId,
|
|
expertId,
|
|
teamId,
|
|
ratings
|
|
});
|
|
}
|
|
|
|
// Возвращаем с populate
|
|
rating = await Rating.findById(rating._id)
|
|
.populate('expertId', 'fullName')
|
|
.populate('teamId', 'name type projectName');
|
|
|
|
res.status(rating ? 200 : 201).json(rating);
|
|
} catch (error) {
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|
|
|