nuevas metricas agregadas

This commit is contained in:
2025-08-27 23:05:02 -06:00
parent 293f407820
commit ffd97646ab
2 changed files with 173 additions and 8 deletions

View File

@@ -160,6 +160,17 @@ const ratioGroups = [
name: 'Denuncias',
actions: ['p1_report', 'p1_no_report'],
labels: ['Denunciar', 'No Denunciar']
},
{
name: 'Puntuaciones',
actions: ['score_p1', 'score_p2'],
labels: ['P1', 'P2']
},
{
name: 'Jugadores',
actions: ['players_with_shame', 'players_seated'],
labels: ['Con vergüenza', 'Sin vergüenza'],
isCustomRatio: true // Special handling needed
}
];
@@ -170,7 +181,16 @@ const ratioData = computed(() => {
? props.playerEventCounts
: props.globalEventCounts;
const values = group.actions.map(action => counts[action] || 0);
let values = group.actions.map(action => counts[action] || 0);
// Special handling for players ratio (shame vs no shame)
if (group.isCustomRatio && group.name === 'Jugadores') {
const playersWithShame = counts['players_with_shame'] || 0;
const totalPlayersSeated = counts['players_seated'] || 0;
const playersWithoutShame = Math.max(0, totalPlayersSeated - playersWithShame);
values = [playersWithShame, playersWithoutShame];
}
const total = values.reduce((sum, val) => sum + val, 0);
return {

View File

@@ -72,10 +72,10 @@
</Transition>
<EventChart
:event-types="EVENTS"
:event-types="ALL_CHART_TYPES"
:event-styles="EVENT_STYLES"
:global-event-counts="eventFilters.globalEventCounts.value"
:player-event-counts="playerEventCounts"
:global-event-counts="combinedGlobalCounts"
:player-event-counts="combinedPlayerCounts"
:selected-player-uuid="selectedUuid"
:player-bar-gradient="playerBarGradient"
:view-mode="viewMode"
@@ -132,8 +132,16 @@ const EVENTS = [
'p1_shame', 'p1_no_shame', 'p1_report', 'p1_no_report'
];
// Event styles matching OfferActions and OfferControls components
// New metric types for additional charts
const METRICS = [
'players_seated', 'score_p1', 'score_p2', 'players_with_shame'
];
const ALL_CHART_TYPES = [...EVENTS, ...METRICS];
// Event and metric styles matching OfferActions and OfferControls components
const EVENT_STYLES: Record<string, { icon: string; color: string; gradient: string }> = {
// Event types
'p1_propose': { icon: '✨', color: '#667eea', gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' },
'p1_no_offer': { icon: '❌', color: '#6b7280', gradient: 'linear-gradient(135deg, #94a3b8 0%, #64748b 100%)' },
'p2_accept': { icon: '✓', color: '#10b981', gradient: 'linear-gradient(135deg, #10b981 0%, #059669 100%)' },
@@ -144,14 +152,118 @@ const EVENT_STYLES: Record<string, { icon: string; color: string; gradient: stri
'p1_shame': { icon: '😶', color: '#fbbf24', gradient: 'linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%)' },
'p1_no_shame': { icon: '🙂', color: '#6b7280', gradient: 'linear-gradient(135deg, #94a3b8 0%, #64748b 100%)' },
'p1_report': { icon: '⚖️', color: '#8b5cf6', gradient: 'linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%)' },
'p1_no_report': { icon: '🤝', color: '#6b7280', gradient: 'linear-gradient(135deg, #94a3b8 0%, #64748b 100%)' }
'p1_no_report': { icon: '🤝', color: '#6b7280', gradient: 'linear-gradient(135deg, #94a3b8 0%, #64748b 100%)' },
// Metric types
'players_seated': { icon: '👥', color: '#06b6d4', gradient: 'linear-gradient(135deg, #06b6d4 0%, #0891b2 100%)' },
'score_p1': { icon: '🦃', color: '#16a34a', gradient: 'linear-gradient(135deg, #16a34a 0%, #15803d 100%)' },
'score_p2': { icon: '🌽', color: '#d97706', gradient: 'linear-gradient(135deg, #d97706 0%, #b45309 100%)' },
'players_with_shame': { icon: '😶', color: '#dc2626', gradient: 'linear-gradient(135deg, #dc2626 0%, #b91c1c 100%)' }
};
// Player score calculation functions (from PlayerStats.vue)
function calculateP1Score(pavoTokens: number, eloteTokens: number): number {
return pavoTokens * 1 + eloteTokens * 2;
}
function calculateP2Score(pavoTokens: number, eloteTokens: number): number {
return eloteTokens * 1 + pavoTokens * 2;
}
// Additional metrics computation
const additionalMetrics = ref<Record<string, number>>({
players_seated: 0,
score_p1: 0,
score_p2: 0,
players_with_shame: 0
});
const selectedPlayerMetrics = ref<Record<string, number>>({
players_seated: 0,
score_p1: 0,
score_p2: 0,
players_with_shame: 0
});
// Function to compute additional metrics from room data
function computeMetrics(roomDetails: any) {
let playersSeated = 0;
let totalP1Score = 0;
let totalP2Score = 0;
let playersWithShame = 0;
Object.values(roomDetails || {}).forEach((room: any) => {
const roomPlayers = room?.players || [];
roomPlayers.forEach((player: any) => {
// Count seated players (have a name)
if (player?.name && player.name.trim()) {
playersSeated++;
const pavoTokens = player.pavoTokens || 0;
const eloteTokens = player.eloteTokens || 0;
const shameTokens = player.shameTokens || 0;
const role = player.role;
// Add scores based on role
if (role === 'P1') {
totalP1Score += calculateP1Score(pavoTokens, eloteTokens);
} else if (role === 'P2') {
totalP2Score += calculateP2Score(pavoTokens, eloteTokens);
}
// Count players with shame
if (shameTokens > 0) {
playersWithShame++;
}
}
});
});
additionalMetrics.value = {
players_seated: playersSeated,
score_p1: totalP1Score,
score_p2: totalP2Score,
players_with_shame: playersWithShame
};
// Update selected player metrics if one is selected (using setTimeout to ensure selectedUuid is available)
setTimeout(() => {
if (selectedUuid.value) {
computeSelectedPlayerMetrics(selectedUuid.value);
}
}, 0);
}
// Function to compute metrics for a selected player
function computeSelectedPlayerMetrics(uuid: string) {
// Individual player metrics are simpler - just show if they're seated, their score, etc.
// For now, set basic values. In a real implementation, we'd need to query current player state
selectedPlayerMetrics.value = {
players_seated: 1, // If player is selected, they're seated
score_p1: 0, // Would need current player data to calculate
score_p2: 0, // Would need current player data to calculate
players_with_shame: 0 // Would need current player data to determine
};
}
// Combined counts for charts (events + metrics)
const combinedGlobalCounts = computed(() => ({
...eventFilters.globalEventCounts.value,
...additionalMetrics.value
}));
const combinedPlayerCounts = computed(() => ({
...playerEventCounts.value,
...selectedPlayerMetrics.value
}));
// Watch for changes in filters and data source
watch([eventFilters.dataSource, eventFilters.roundFilter, eventFilters.gameFilter], () => {
eventFilters.applyFilters(EVENTS);
});
// selectedUuid watch will be added after selectedUuid is declared
const rooms = ref<RoomInfo[]>([]);
const players = ref<{ uuid: string; name: string; color?: string }[]>([]);
const search = ref('');
@@ -192,6 +304,13 @@ const playerLoading = ref(false);
const playerEventCounts = ref<Record<string, number>>((Object.fromEntries(EVENTS.map(k => [k, 0])) as Record<string, number>));
const playersActionsByUuid = ref<Record<string, Record<string, number>>>({});
// Watch for selected player changes to update their metrics
watch(selectedUuid, (newUuid) => {
if (newUuid) {
computeSelectedPlayerMetrics(newUuid);
}
});
function initials(name: string): string {
const n = (name || '').trim();
if (!n) return '🙂';
@@ -273,6 +392,9 @@ function setupStreams() {
eventFilters.updateActiveRoomsData(detailedEvents, counts);
// Compute additional metrics
computeMetrics(details);
// Apply filters and update display
if (eventFilters.dataSource.value === 'active-rooms') {
eventFilters.applyFilters(EVENTS);
@@ -462,7 +584,7 @@ function downloadCSV() {
const currentEvents = eventFilters.currentSourceEvents.value;
// Create CSV headers
const headers = ['Event', 'Count', 'Round', 'GameVariant', 'PlayerUuid', 'PlayerName', 'DataSource'];
const headers = ['Type', 'Event', 'Count', 'Round', 'GameVariant', 'PlayerUuid', 'PlayerName', 'DataSource'];
// Create CSV rows - one row per individual event occurrence
const rows: string[][] = [];
@@ -471,6 +593,7 @@ function downloadCSV() {
currentEvents.forEach(event => {
if (EVENTS.includes(event.kind)) {
rows.push([
'event',
event.kind,
'1',
event.round?.toString() || '',
@@ -491,6 +614,7 @@ function downloadCSV() {
// Add one row per occurrence for proper import compatibility
for (let i = 0; i < count; i++) {
rows.push([
'event',
eventType,
'1',
'', // Round info not available in aggregated player data
@@ -504,6 +628,27 @@ function downloadCSV() {
});
});
// Add metric data
const currentMetrics = eventFilters.dataSource.value === 'aggregated'
? additionalMetrics.value
: additionalMetrics.value; // Same source for now
METRICS.forEach(metricType => {
const count = currentMetrics[metricType] || 0;
if (count > 0) {
rows.push([
'metric',
metricType,
count.toString(),
'',
'',
'',
'',
eventFilters.dataSource.value
]);
}
});
// Convert to CSV string
const csvContent = [
headers.join(','),
@@ -520,7 +665,7 @@ function downloadCSV() {
const timestamp = new Date().toISOString().slice(0, 19).replace(/[T:]/g, '-');
const dataSourceLabel = eventFilters.dataSource.value === 'aggregated' ? 'agregados' : 'activos';
const filterLabel = eventFilters.hasActiveFilters.value ? `_${eventFilters.filterSummary.value.replace(/\s+/g, '_')}` : '';
const filename = `eventos_${dataSourceLabel}${filterLabel}_${timestamp}.csv`;
const filename = `datos_${dataSourceLabel}${filterLabel}_${timestamp}.csv`;
link.setAttribute('download', filename);
link.style.visibility = 'hidden';