Refactor change...

This commit is contained in:
2025-11-09 12:55:10 +01:00
parent ca1ea820a2
commit ba7e1626e3
42 changed files with 937 additions and 59638 deletions
+40
View File
@@ -0,0 +1,40 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
*.egg-info/
dist/
build/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# Data files (generated at runtime)
data/tournament_state.json
data/tournament_results.json
data/league_state.json
data/players.json
# Archives (user-generated)
data/tournament_archives/
data/league_archives/
# Environment
.env
.env.local
# OS
.DS_Store
Thumbs.db
# Temporary launcher scripts
_run_server.py
app_runner.py
+93
View File
@@ -0,0 +1,93 @@
# TV_APP V1.0.0 - Tournament Management System
A Flask-based web application for managing tournaments with multi-camera streaming support.
## Quick Start
### Prerequisites
- Python 3.7+
### Installation & Run
```bash
# Create virtual environment
python3 -m venv venv
# Activate venv
source venv/bin/activate # Linux/Mac
# or
venv\Scripts\activate # Windows
# Install dependencies
pip install -r requirements.txt
# Run the app
python3 tv_app.py
```
The app will be available at: **http://localhost:5000**
## Project Structure
```
TV_APP_V1.0.0/
├── tv_app.py # Main Flask application (2,364 lines)
├── requirements.txt # Python dependencies
├── README.md # This file
├── app/ # Support modules
│ ├── __init__.py
│ ├── config.py # Configuration
│ ├── models.py # Data models
│ ├── storage.py # Persistent storage
│ └── utils.py # Utilities
├── templates/ # HTML templates
├── static/ # CSS, JS, images
├── data/ # Runtime data (JSON state)
└── locales/ # Translations (EN, SL)
```
## Features
✅ Tournament management with live scoring
✅ Multi-camera streaming integration
✅ Mobile interface for remote access
✅ Tournament archiving & history
✅ League management
✅ Multi-language support (English & Slovenian)
✅ JSON-based persistent storage
## Key URLs
- **Main Dashboard**: http://localhost:5000/
- **Mobile Interface**: http://localhost:5000/mobile
- **Archive**: http://localhost:5000/archive
- **Tournament View**: http://localhost:5000/tournament
- **Results**: http://localhost:5000/results
## Dependencies
- Flask 3.0.0
- Flask-SocketIO 5.3.4
- python-socketio 5.9.0
- python-engineio 4.7.1
- python-dotenv 1.0.0
## Development
The app structure is simple and maintainable:
- All Flask routes in `tv_app.py`
- Support modules in `app/` package
- Ready to split into blueprints if needed
## Notes
- Real-time WebSocket synchronization is disabled (not working reliably)
- Each instance operates independently
- All data is saved to JSON files in `data/` folder
- Fully functional and production-ready
## How to Stop
Press `Ctrl+C` in the terminal where the app is running.
Binary file not shown.
+6
View File
@@ -0,0 +1,6 @@
"""
TV_APP V1.0.0 - App Package
This package contains support modules for the main Flask application.
All routes are in tv_app.py in the project root.
"""
+13
View File
@@ -0,0 +1,13 @@
"""
Configuration for TV_APP V1.0.0
"""
import os
class Config:
"""Base configuration class"""
SECRET_KEY = os.getenv('SECRET_KEY', 'your-secret-key-for-sessions')
DEBUG = os.getenv('DEBUG', 'False').lower() == 'true'
HOST = os.getenv('HOST', '0.0.0.0')
PORT = int(os.getenv('PORT', 5000))
+280
View File
@@ -0,0 +1,280 @@
"""
Data models and business logic for TV_APP
Handles all calculations and data structure creation
"""
from datetime import datetime
import random
NUM_CAMERAS = 6
class Tournament:
"""Tournament creation and management"""
@staticmethod
def create_league(enabled_players, tournament_type):
"""Create a new league with 5 tournaments"""
league_id = f"league_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
league_data = {
'league_id': league_id,
'created_at': datetime.now().isoformat(),
'tournament_type': tournament_type,
'total_tournaments': 5,
'current_tournament': 0,
'participants': {},
'completed_tournaments': [],
'league_finished': False
}
# Initialize participants
for player in enabled_players:
league_data['participants'][str(player['id'])] = {
'name': player['name'],
'joker_used': False,
'tournament_results': [],
'total_score': 0,
'final_score': 0,
'tournaments_participated': 0
}
return league_data
@staticmethod
def create_draft(enabled_players, tournament_type='20_targets', league_tournament_number=None):
"""Create draft groups of 6 players and organize rounds"""
if len(enabled_players) < 1:
return None
# Shuffle players for random grouping
players_copy = enabled_players.copy()
random.shuffle(players_copy)
# Create groups of up to 6
groups = []
for i in range(0, len(players_copy), NUM_CAMERAS):
group = players_copy[i:i + NUM_CAMERAS]
groups.append(group)
# Create rounds
rounds = []
for i, group in enumerate(groups):
rounds.append({
'round_number': i + 1,
'players': group,
'status': 'pending' if i == 0 else 'waiting'
})
tournament_data = {
'rounds': rounds,
'created_at': datetime.now().isoformat(),
'total_players': len(enabled_players),
'total_rounds': len(rounds),
'current_round': 1,
'tournament_type': tournament_type
}
if league_tournament_number:
tournament_data['league_tournament_number'] = league_tournament_number
return tournament_data
@staticmethod
def create_results_structure(tournament_data):
"""Create results structure for a tournament"""
if not tournament_data:
return None
tournament_type = tournament_data.get('tournament_type', '20_targets')
# Determine target count and shots per target
if tournament_type == '40_targets':
num_targets = 40
shots_per_target = 2
elif tournament_type == '4_targets':
num_targets = 4
shots_per_target = 5
else: # 20_targets (default)
num_targets = 20
shots_per_target = 2
results = {
'tournament_id': tournament_data.get('created_at', datetime.now().isoformat()),
'tournament_type': tournament_type,
'participants': {},
'tournament_finished': False,
'created_at': datetime.now().isoformat()
}
# Add league info if present
if 'league_tournament_number' in tournament_data:
results['league_tournament_number'] = tournament_data['league_tournament_number']
# Create structure for each participant
all_players = []
for round_data in tournament_data['rounds']:
all_players.extend(round_data['players'])
for player in all_players:
player_id = str(player['id'])
# Create target structure based on tournament type
targets = {}
for i in range(1, num_targets + 1):
target = {}
for j in range(1, shots_per_target + 1):
target[f'shot{j}'] = None
targets[str(i)] = target
results['participants'][player_id] = {
'name': player['name'],
'targets': targets,
'total_score': 0,
'completed': False
}
return results
class Scoring:
"""Score calculation and ranking"""
@staticmethod
def calculate_total_score(targets):
"""Calculate total score from targets, treating None as 0"""
total = 0
for target in targets.values():
for shot_key, shot_value in target.items():
if shot_key.startswith('shot') and shot_value is not None:
total += shot_value
return total
@staticmethod
def is_participant_completed(targets):
"""Check if a participant has completed all targets"""
for target in targets.values():
for shot_key, shot_value in target.items():
if shot_key.startswith('shot') and shot_value is None:
return False
return True
@staticmethod
def calculate_league_final_scores(league_data):
"""Calculate final league scores using best 4 tournaments"""
for participant_id, participant in league_data['participants'].items():
tournament_scores = []
# Get all tournament scores where player participated
for result in participant['tournament_results']:
if result['participated']:
tournament_scores.append(result['score'])
# Sort scores descending and take best 4
tournament_scores.sort(reverse=True)
best_scores = tournament_scores[:4] if len(tournament_scores) > 4 else tournament_scores
participant['final_score'] = sum(best_scores)
participant['tournaments_participated'] = len(tournament_scores)
@staticmethod
def get_league_final_rankings(league_data):
"""Get final league rankings sorted by final score"""
participants = []
for player_id, data in league_data['participants'].items():
# Calculate total 10s across all tournaments
total_tens = sum(
result.get('tens_count', 0) for result in data['tournament_results']
if result.get('participated', False)
)
participants.append({
'id': player_id,
'name': data['name'],
'final_score': data['final_score'],
'total_score': data['total_score'],
'tournaments_participated': data['tournaments_participated'],
'joker_used': data['joker_used'],
'tournament_results': data['tournament_results'],
'total_tens': total_tens
})
# Sort by final score (best 4 tournaments) descending, then by total 10s
participants.sort(key=lambda x: (x['final_score'], x['total_tens']), reverse=True)
# Add rankings
for i, participant in enumerate(participants):
participant['rank'] = i + 1
return participants
@staticmethod
def calculate_current_league_standings(league_data):
"""Calculate current league standings during active league"""
participants = []
for player_id, data in league_data['participants'].items():
tournament_scores = []
completed_tournaments = 0
total_tens = 0
for result in data['tournament_results']:
if result['participated']:
tournament_scores.append(result['score'])
completed_tournaments += 1
total_tens += result.get('tens_count', 0)
# Current score is sum of all completed tournaments
current_total = sum(tournament_scores)
# For display, show what the final score would be if we took best 4 now
tournament_scores.sort(reverse=True)
projected_final = sum(tournament_scores[:4]) if len(tournament_scores) >= 4 else sum(tournament_scores)
participants.append({
'id': player_id,
'name': data['name'],
'current_total': current_total,
'projected_final': projected_final,
'tournaments_completed': completed_tournaments,
'joker_used': data['joker_used'],
'tournament_results': data['tournament_results'],
'total_tens': total_tens
})
# Sort by current total score (descending), then by total 10s
participants.sort(key=lambda x: (x['current_total'], x['total_tens']), reverse=True)
# Add rankings
for i, participant in enumerate(participants):
participant['rank'] = i + 1
return participants
class RoundManager:
"""Tournament round management"""
@staticmethod
def get_current_round_data(tournament_state):
"""Get current round data from tournament"""
if not tournament_state:
return None
current_round_num = tournament_state.get('current_round', 1)
# Find the current round
for round_data in tournament_state['rounds']:
if round_data['round_number'] == current_round_num:
return round_data
return None
@staticmethod
def get_tournament_format_description(tournament_type):
"""Get tournament format description"""
formats = {
'20_targets': '20 Targets, 2 Shots Per Target',
'40_targets': '40 Targets, 2 Shots Per Target',
'4_targets': '4 Targets, 5 Shots Per Target'
}
return formats.get(tournament_type, 'Unknown Format')
+312
View File
@@ -0,0 +1,312 @@
"""
File I/O and data persistence for TV_APP
Handles all JSON file operations and data archiving
"""
import json
import os
import glob
from datetime import datetime
# File paths - organized in data directory
SETTINGS_FILE = 'data/camera_settings.json'
PLAYERS_FILE = 'data/players.json'
TOURNAMENT_FILE = 'data/tournament_state.json'
RESULTS_FILE = 'data/tournament_results.json'
LEAGUE_FILE = 'data/league_state.json'
ARCHIVE_DIR = 'data/tournament_archives'
LEAGUE_ARCHIVE_DIR = 'data/league_archives'
# Default settings
DEFAULT_SETTINGS = {
'camera_titles': {
'1': 'Camera 1',
'2': 'Camera 2',
'3': 'Camera 3',
'4': 'Camera 4',
'5': 'Camera 5',
'6': 'Camera 6'
},
'display_options': {
'show_titles': True,
'title_size': 1.1
}
}
class FileStorage:
"""Handle JSON file read/write operations"""
@staticmethod
def _ensure_directory(directory):
"""Ensure directory exists"""
if not os.path.exists(directory):
os.makedirs(directory)
@staticmethod
def _read_json(filepath):
"""Read JSON file safely"""
try:
if os.path.exists(filepath):
with open(filepath, 'r', encoding='utf-8') as f:
return json.load(f)
except (json.JSONDecodeError, IOError) as e:
print(f"Error reading {filepath}: {e}")
return None
@staticmethod
def _write_json(filepath, data):
"""Write JSON file safely"""
try:
directory = os.path.dirname(filepath)
FileStorage._ensure_directory(directory)
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
return True
except IOError as e:
print(f"Error writing {filepath}: {e}")
return False
class SettingsStorage(FileStorage):
"""Manage camera and display settings"""
@staticmethod
def load_settings():
"""Load settings from JSON file, create with defaults if not exists"""
settings = FileStorage._read_json(SETTINGS_FILE)
if settings is None:
return DEFAULT_SETTINGS.copy()
# Ensure all required keys exist (backwards compatibility)
for key in DEFAULT_SETTINGS:
if key not in settings:
settings[key] = DEFAULT_SETTINGS[key]
for camera_id in DEFAULT_SETTINGS['camera_titles']:
if camera_id not in settings.get('camera_titles', {}):
settings['camera_titles'][camera_id] = DEFAULT_SETTINGS['camera_titles'][camera_id]
return settings
@staticmethod
def save_settings(settings):
"""Save settings to JSON file"""
return FileStorage._write_json(SETTINGS_FILE, settings)
class PlayerStorage(FileStorage):
"""Manage player data"""
DEFAULT_PLAYERS = {
'players': [
{'id': i, 'name': f'Player {i}', 'enabled': True}
for i in range(1, 7) # 6 cameras
]
}
@staticmethod
def load_players():
"""Load players from JSON file, create with defaults if not exists"""
players = FileStorage._read_json(PLAYERS_FILE)
if players is None:
PlayerStorage.save_players(PlayerStorage.DEFAULT_PLAYERS)
return PlayerStorage.DEFAULT_PLAYERS.copy()
return players
@staticmethod
def save_players(players_data):
"""Save players to JSON file"""
return FileStorage._write_json(PLAYERS_FILE, players_data)
class TournamentStorage(FileStorage):
"""Manage tournament state"""
@staticmethod
def load_tournament_state():
"""Load tournament state from JSON file"""
return FileStorage._read_json(TOURNAMENT_FILE)
@staticmethod
def save_tournament_state(tournament_data):
"""Save tournament state to JSON file"""
return FileStorage._write_json(TOURNAMENT_FILE, tournament_data)
class ResultsStorage(FileStorage):
"""Manage tournament results"""
@staticmethod
def load_results():
"""Load results from JSON file"""
return FileStorage._read_json(RESULTS_FILE)
@staticmethod
def save_results(results_data):
"""Save results to JSON file"""
return FileStorage._write_json(RESULTS_FILE, results_data)
class LeagueStorage(FileStorage):
"""Manage league state"""
@staticmethod
def load_league_state():
"""Load league state from JSON file"""
return FileStorage._read_json(LEAGUE_FILE)
@staticmethod
def save_league_state(league_data):
"""Save league state to JSON file"""
return FileStorage._write_json(LEAGUE_FILE, league_data)
class ArchiveStorage(FileStorage):
"""Manage tournament and league archives"""
@staticmethod
def archive_tournament(tournament_data, results_data):
"""Archive completed tournament data"""
try:
FileStorage._ensure_directory(ARCHIVE_DIR)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
archive_filename = f"tournament_{timestamp}.json"
archive_path = os.path.join(ARCHIVE_DIR, archive_filename)
archive_data = {
'tournament': tournament_data,
'results': results_data,
'archived_at': datetime.now().isoformat()
}
success = FileStorage._write_json(archive_path, archive_data)
if success:
print(f"Tournament archived to: {archive_path}")
return success
except Exception as e:
print(f"Error archiving tournament: {e}")
return False
@staticmethod
def archive_league(league_data):
"""Archive completed league data"""
try:
FileStorage._ensure_directory(LEAGUE_ARCHIVE_DIR)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
archive_filename = f"league_{timestamp}.json"
archive_path = os.path.join(LEAGUE_ARCHIVE_DIR, archive_filename)
archive_data = {
'league': league_data,
'archived_at': datetime.now().isoformat()
}
success = FileStorage._write_json(archive_path, archive_data)
if success:
print(f"League archived to: {archive_path}")
return success
except Exception as e:
print(f"Error archiving league: {e}")
return False
@staticmethod
def get_archived_tournaments():
"""Get list of standalone archived tournaments"""
try:
if not os.path.exists(ARCHIVE_DIR):
return []
archives = []
for file_path in glob.glob(os.path.join(ARCHIVE_DIR, "tournament_*.json")):
try:
data = FileStorage._read_json(file_path)
if not data:
continue
filename = os.path.basename(file_path)
archived_at = data.get('archived_at', 'Unknown')
tournament_data = data.get('tournament', {})
results_data = data.get('results', {})
# Skip tournaments that are part of a league
if tournament_data.get('league_tournament_number') or results_data.get('league_tournament_number'):
continue
archive_info = {
'filename': filename,
'filepath': file_path,
'archived_at': archived_at,
'created_at': tournament_data.get('created_at', 'Unknown'),
'tournament_type': tournament_data.get('tournament_type', '20_targets'),
'total_players': tournament_data.get('total_players', 0),
'total_rounds': tournament_data.get('total_rounds', 0),
'tournament_finished': results_data.get('tournament_finished', False),
'participants_count': len(results_data.get('participants', {}))
}
archives.append(archive_info)
except (json.JSONDecodeError, IOError) as e:
print(f"Error reading archive {file_path}: {e}")
continue
# Sort by archived date (newest first)
archives.sort(key=lambda x: x['archived_at'], reverse=True)
return archives
except Exception as e:
print(f"Error getting archived tournaments: {e}")
return []
@staticmethod
def get_archived_leagues():
"""Get list of all archived leagues"""
try:
if not os.path.exists(LEAGUE_ARCHIVE_DIR):
return []
archives = []
for file_path in glob.glob(os.path.join(LEAGUE_ARCHIVE_DIR, "league_*.json")):
try:
data = FileStorage._read_json(file_path)
if not data:
continue
filename = os.path.basename(file_path)
archived_at = data.get('archived_at', 'Unknown')
league_data = data.get('league', {})
archive_info = {
'filename': filename,
'filepath': file_path,
'archived_at': archived_at,
'created_at': league_data.get('created_at', 'Unknown'),
'league_id': league_data.get('league_id', 'Unknown'),
'tournament_type': league_data.get('tournament_type', '20_targets'),
'total_tournaments': league_data.get('total_tournaments', 5),
'participants_count': len(league_data.get('participants', {})),
'league_finished': league_data.get('league_finished', False),
'completed_tournaments': len(league_data.get('completed_tournaments', []))
}
archives.append(archive_info)
except (json.JSONDecodeError, IOError) as e:
print(f"Error reading league archive {file_path}: {e}")
continue
# Sort by archived date (newest first)
archives.sort(key=lambda x: x['archived_at'], reverse=True)
return archives
except Exception as e:
print(f"Error getting archived leagues: {e}")
return []
@staticmethod
def load_archive_file(filepath):
"""Load a specific archive file"""
return FileStorage._read_json(filepath)
+78
View File
@@ -0,0 +1,78 @@
"""
Utility functions for TV_APP
Helper functions for translations, device detection, and common calculations
"""
import json
import os
import re
from flask import request, session
DEFAULT_LANGUAGE = 'sl'
def load_translations(language='sl'):
"""Load translations for the specified language"""
try:
locale_file = os.path.join('locales', f'{language}.json')
if os.path.exists(locale_file):
with open(locale_file, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
print(f"Error loading translations for {language}: {e}")
# Fallback to default language
try:
default_file = os.path.join('locales', f'{DEFAULT_LANGUAGE}.json')
with open(default_file, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
print(f"Error loading default translations: {e}")
return {}
def get_current_language():
"""Get current language from session or default"""
return session.get('language', DEFAULT_LANGUAGE)
def get_translations():
"""Get translations for current language"""
return load_translations(get_current_language())
def is_mobile_device():
"""Check if the request is coming from a mobile device"""
user_agent = request.headers.get('User-Agent', '').lower()
mobile_patterns = [
r'android', r'iphone', r'ipad', r'ipod', r'blackberry',
r'iemobile', r'opera mini', r'mobile', r'tablet'
]
return any(re.search(pattern, user_agent) for pattern in mobile_patterns)
def calculate_tens_from_targets(targets):
"""Calculate the number of 10s from a targets dictionary"""
tens_count = 0
if not targets:
return 0
for target in targets.values():
if isinstance(target, dict):
for shot_key, shot_value in target.items():
if shot_key.startswith('shot') and shot_value == 10:
tens_count += 1
return tens_count
def validate_player_data(player_data):
"""Validate player data structure"""
required_fields = ['id', 'name', 'enabled']
return all(field in player_data for field in required_fields)
def validate_settings(settings):
"""Validate settings structure"""
required_sections = ['camera_titles', 'display_options']
return all(section in settings for section in required_sections)
File diff suppressed because it is too large Load Diff
-406
View File
@@ -1,406 +0,0 @@
{
"league": {
"league_id": "league_20251031_184134",
"created_at": "2025-10-31T18:41:34.562185",
"tournament_type": "4_targets",
"total_tournaments": 5,
"current_tournament": 0,
"participants": {
"1": {
"name": "Domen Pleterski",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"2": {
"name": "Nik Pleterski",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"3": {
"name": "Ivan Tandler",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"4": {
"name": "Mateja Pleterski",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"5": {
"name": "Jo\u017ee Verhnjak",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"6": {
"name": "Mateja Senica",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"7": {
"name": "Branko Poker\u017enik",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"8": {
"name": "Franc \u017digart",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"9": {
"name": "Janez Bo\u017ei\u010d",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"10": {
"name": "Mitja \u010ceh",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"11": {
"name": "Rado Kefer",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"12": {
"name": "Matej Kvasnik",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"13": {
"name": "Angelca Mrak",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"14": {
"name": "Karli Proje",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"15": {
"name": "Jan Pleterski",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"16": {
"name": "Silvo Poro\u010dnik",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"17": {
"name": "Du\u0161an Onuk",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"18": {
"name": "Matja\u017e Pleterski",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"19": {
"name": "Franc Rizmal",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"20": {
"name": "Jo\u017ee Preglav",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"21": {
"name": "Marko Blimen",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"22": {
"name": "Doris Fesel",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"23": {
"name": "Robi Krautberger",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"24": {
"name": "Jo\u017ee Verdinek",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"25": {
"name": "Andrej Herman",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"26": {
"name": "Jakob Herman",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"27": {
"name": "Janez Mrak",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"28": {
"name": "An\u017ee Kolar",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"29": {
"name": "Alen Kolar",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"30": {
"name": "Maja Hirtl",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"31": {
"name": "Dejan Ku\u010dnik",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"32": {
"name": "David Strni\u0161a",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"33": {
"name": "Namir Uzunovi\u0107",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"34": {
"name": "Jo\u017ee Planin\u0161ec",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"35": {
"name": "Vanja Kolar",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"36": {
"name": "Klara Wankmuller",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"37": {
"name": "Milan Stramec",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"38": {
"name": "Bojan Sudar",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"39": {
"name": "Tia Sudar",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"40": {
"name": "Jaka Cvar",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"41": {
"name": "Tadej \u0160truc",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"42": {
"name": "Jure Glaser",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"43": {
"name": "Marko Pokr\u017enik",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"44": {
"name": "Anka Ka\u010dnik",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"45": {
"name": "Lidija Blimen",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"46": {
"name": "Tijana \u0160tumpfl",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"47": {
"name": "Ljuba Mr\u0161ak",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"48": {
"name": "Janja Salcman",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
},
"49": {
"name": "Jolanda Verhnjak",
"joker_used": false,
"tournament_results": [],
"total_score": 0,
"final_score": 0,
"tournaments_participated": 0
}
},
"completed_tournaments": [],
"league_finished": false
},
"archived_at": "2025-10-31T18:41:38.976988"
}
-249
View File
@@ -1,249 +0,0 @@
{
"players": [
{
"id": 1,
"name": "Domen Pleterski",
"enabled": true
},
{
"id": 2,
"name": "Nik Pleterski",
"enabled": true
},
{
"id": 3,
"name": "Ivan Tandler",
"enabled": true
},
{
"id": 4,
"name": "Mateja Pleterski",
"enabled": true
},
{
"id": 5,
"name": "Jo\u017ee Verhnjak",
"enabled": true
},
{
"id": 6,
"name": "Mateja Senica",
"enabled": true
},
{
"id": 7,
"name": "Branko Poker\u017enik",
"enabled": true
},
{
"id": 8,
"name": "Franc \u017digart",
"enabled": true
},
{
"id": 9,
"name": "Janez Bo\u017ei\u010d",
"enabled": true
},
{
"id": 10,
"name": "Mitja \u010ceh",
"enabled": true
},
{
"id": 11,
"name": "Rado Kefer",
"enabled": true
},
{
"id": 12,
"name": "Matej Kvasnik",
"enabled": true
},
{
"id": 13,
"name": "Angelca Mrak",
"enabled": true
},
{
"id": 14,
"name": "Karli Proje",
"enabled": true
},
{
"id": 15,
"name": "Jan Pleterski",
"enabled": true
},
{
"id": 16,
"name": "Silvo Poro\u010dnik",
"enabled": true
},
{
"id": 17,
"name": "Du\u0161an Onuk",
"enabled": true
},
{
"id": 18,
"name": "Matja\u017e Pleterski",
"enabled": true
},
{
"id": 19,
"name": "Franc Rizmal",
"enabled": true
},
{
"id": 20,
"name": "Jo\u017ee Preglav",
"enabled": true
},
{
"id": 21,
"name": "Marko Blimen",
"enabled": true
},
{
"id": 22,
"name": "Doris Fesel",
"enabled": true
},
{
"id": 23,
"name": "Robi Krautberger",
"enabled": true
},
{
"id": 24,
"name": "Jo\u017ee Verdinek",
"enabled": true
},
{
"id": 25,
"name": "Andrej Herman",
"enabled": true
},
{
"id": 26,
"name": "Jakob Herman",
"enabled": true
},
{
"id": 27,
"name": "Janez Mrak",
"enabled": true
},
{
"id": 28,
"name": "An\u017ee Kolar",
"enabled": true
},
{
"id": 29,
"name": "Alen Kolar",
"enabled": true
},
{
"id": 30,
"name": "Maja Hirtl",
"enabled": true
},
{
"id": 31,
"name": "Dejan Ku\u010dnik",
"enabled": true
},
{
"id": 32,
"name": "David Strni\u0161a",
"enabled": true
},
{
"id": 33,
"name": "Namir Uzunovi\u0107",
"enabled": true
},
{
"id": 34,
"name": "Jo\u017ee Planin\u0161ec",
"enabled": true
},
{
"id": 35,
"name": "Vanja Kolar",
"enabled": true
},
{
"id": 36,
"name": "Klara Wankmuller",
"enabled": true
},
{
"id": 37,
"name": "Milan Stramec",
"enabled": true
},
{
"id": 38,
"name": "Bojan Sudar",
"enabled": true
},
{
"id": 39,
"name": "Tia Sudar",
"enabled": true
},
{
"id": 40,
"name": "Jaka Cvar",
"enabled": true
},
{
"id": 41,
"name": "Tadej \u0160truc",
"enabled": true
},
{
"id": 42,
"name": "Jure Glaser",
"enabled": true
},
{
"id": 43,
"name": "Marko Pokr\u017enik",
"enabled": true
},
{
"id": 44,
"name": "Anka Ka\u010dnik",
"enabled": true
},
{
"id": 45,
"name": "Lidija Blimen",
"enabled": true
},
{
"id": 46,
"name": "Tijana \u0160tumpfl",
"enabled": true
},
{
"id": 47,
"name": "Ljuba Mr\u0161ak",
"enabled": true
},
{
"id": 48,
"name": "Janja Salcman",
"enabled": true
},
{
"id": 49,
"name": "Jolanda Verhnjak",
"enabled": true
}
]
}
+2
View File
@@ -0,0 +1,2 @@
Flask==3.0.0
python-dotenv==1.0.0
-3
View File
@@ -1,3 +0,0 @@
{
"header_text_size": "14px"
}
-132
View File
@@ -1,132 +0,0 @@
#!/usr/bin/env python3
"""
TV_APP V1.0.0 - One-Click Launcher
Automatically installs dependencies and starts the web application
"""
import os
import sys
import subprocess
import webbrowser
import time
import importlib.util
def check_python_version():
"""Check if Python 3.7+ is available"""
if sys.version_info < (3, 7):
print("ERROR: Python 3.7+ is required")
print(f"You have Python {sys.version}")
return False
return True
def check_and_install_flask():
"""Check if Flask is installed, install if not"""
flask_spec = importlib.util.find_spec("flask")
if flask_spec is not None:
import flask
print(f"✓ Flask is installed: v{flask.__version__}")
return True
print("⚠ Flask not found. Installing...")
try:
subprocess.check_call([sys.executable, "-m", "pip", "install", "flask", "-q"])
print("✓ Flask installed successfully")
return True
except subprocess.CalledProcessError:
print("ERROR: Failed to install Flask")
return False
def main():
"""Main installation and startup routine"""
print("\n" + "="*70)
print(" TV_APP V1.0.0 - Installation & Launcher")
print("="*70 + "\n")
# Check Python version
print("[1/4] Checking Python version...")
if not check_python_version():
print("\nPlease install Python 3.7 or higher")
input("Press Enter to exit...")
return False
import flask
print(f"✓ Python {sys.version.split()[0]} found\n")
# Check Flask
print("[2/4] Checking Flask installation...")
if not check_and_install_flask():
input("Press Enter to exit...")
return False
print()
# Find the app directory (handle symlinks and copies)
print("[3/4] Locating application directory...")
# Start with script location
script_dir = os.path.dirname(os.path.abspath(__file__))
# Resolve symlinks
if os.path.islink(__file__):
script_dir = os.path.dirname(os.path.abspath(os.readlink(__file__)))
app_path = os.path.join(script_dir, "app.py")
# If app.py not found, search for it
if not os.path.exists(app_path):
print(f" Searching for app.py near {script_dir}...")
# Check parent directory
parent_dir = os.path.dirname(script_dir)
app_path = os.path.join(parent_dir, "app.py")
if os.path.exists(app_path):
script_dir = parent_dir
else:
print(f"ERROR: app.py not found")
print(f" Checked: {script_dir}")
print(f" Checked: {parent_dir}")
input("Press Enter to exit...")
return False
print(f"✓ Application found: {app_path}\n")
# Start the app
print("[4/4] Starting web application...\n")
print("="*70)
print(" TV_APP is starting...")
print("="*70)
print("\nThe application will be available at: http://localhost:5000")
print("Press Ctrl+C to stop the server\n")
time.sleep(2)
# Open browser
try:
webbrowser.open("http://localhost:5000", new=1)
except:
pass
# Change to app directory and run
os.chdir(script_dir)
try:
# Import and run the Flask app
import app
app.app.run(host='0.0.0.0', port=5000, debug=True, use_reloader=False)
except ImportError as e:
print(f"ERROR: Could not import app.py: {e}")
input("Press Enter to exit...")
return False
except KeyboardInterrupt:
print("\n\nServer stopped by user")
return True
except Exception as e:
print(f"ERROR: {e}")
input("Press Enter to exit...")
return False
return True
if __name__ == "__main__":
success = main()
sys.exit(0 if success else 1)
+6
View File
@@ -0,0 +1,6 @@
/**
* WebSocket Client - DEPRECATED
* This file is no longer used. WebSocket/SocketIO support has been removed.
*/
console.log('WebSocket support has been removed from this application.');
-39
View File
@@ -1,39 +0,0 @@
{
"1": {
"number": 1,
"title": "Stream 1",
"url": "http://192.168.0.103:9081",
"active": true
},
"2": {
"number": 2,
"title": "Stream 2",
"url": "http://192.168.0.103:9082",
"active": true
},
"3": {
"number": 3,
"title": "Stream 3",
"url": "http://192.168.0.103:9083",
"active": true
},
"4": {
"number": 4,
"title": "Stream 4",
"url": "http://192.168.0.103:9084",
"active": true
},
"5": {
"number": 5,
"title": "Stream 5",
"url": "http://192.168.0.103:9085",
"active": true
},
"6": {
"number": 6,
"title": "Stream 6",
"url": "http://192.168.0.103:9086",
"active": true
},
"_last_updated": 1757351904.1407912
}
-3
View File
@@ -1,3 +0,0 @@
{
"git.ignoreLimitWarning": true
}
@@ -1,759 +0,0 @@
{
"tournament": {
"rounds": [
{
"round_number": 1,
"players": [
{
"id": 4,
"name": "Mateja Pleterski",
"enabled": true
},
{
"id": 20,
"name": "Jo\u017ee Preglav",
"enabled": true
},
{
"id": 3,
"name": "Ivan Tandler",
"enabled": true
},
{
"id": 17,
"name": "Du\u0161an Onuk",
"enabled": true
},
{
"id": 9,
"name": "Janez Bo\u017ei\u010d",
"enabled": true
},
{
"id": 44,
"name": "Anka Ka\u010dnik",
"enabled": true
}
],
"status": "pending"
},
{
"round_number": 2,
"players": [
{
"id": 48,
"name": "Janja Salcman",
"enabled": true
},
{
"id": 15,
"name": "Jan Pleterski",
"enabled": true
},
{
"id": 45,
"name": "Lidija Blimen",
"enabled": true
},
{
"id": 21,
"name": "Marko Blimen",
"enabled": true
},
{
"id": 49,
"name": "Jolanda Verhnjak",
"enabled": true
},
{
"id": 11,
"name": "Rado Kefer",
"enabled": true
}
],
"status": "waiting"
},
{
"round_number": 3,
"players": [
{
"id": 12,
"name": "Matej Kvasnik",
"enabled": true
},
{
"id": 47,
"name": "Ljuba Mr\u0161ak",
"enabled": true
},
{
"id": 46,
"name": "Tijana \u0160tumpfl",
"enabled": true
},
{
"id": 1,
"name": "Domen Pleterski",
"enabled": true
},
{
"id": 7,
"name": "Branko Poker\u017enik",
"enabled": true
},
{
"id": 5,
"name": "Jo\u017ee Verhnjak",
"enabled": true
}
],
"status": "waiting"
}
],
"created_at": "2025-08-30T13:09:30.384521",
"total_players": 18,
"total_rounds": 3,
"current_round": 1,
"tournament_type": "4_targets"
},
"results": {
"tournament_id": "2025-08-30T13:09:30.384521",
"tournament_type": "4_targets",
"participants": {
"4": {
"name": "Mateja Pleterski",
"targets": {
"1": {
"shot1": 0,
"shot2": 0,
"shot3": 0,
"shot4": 0,
"shot5": 0
},
"2": {
"shot1": 0,
"shot2": 0,
"shot3": 0,
"shot4": 0,
"shot5": 0
},
"3": {
"shot1": 0,
"shot2": 0,
"shot3": 0,
"shot4": 0,
"shot5": 0
},
"4": {
"shot1": 0,
"shot2": 0,
"shot3": 0,
"shot4": 0,
"shot5": 0
}
},
"total_score": 0,
"completed": true
},
"20": {
"name": "Jo\u017ee Preglav",
"targets": {
"1": {
"shot1": 10,
"shot2": 8,
"shot3": 8,
"shot4": 7,
"shot5": 5
},
"2": {
"shot1": 9,
"shot2": 7,
"shot3": 7,
"shot4": 5,
"shot5": 4
},
"3": {
"shot1": 9,
"shot2": 9,
"shot3": 7,
"shot4": 6,
"shot5": 6
},
"4": {
"shot1": 8,
"shot2": 8,
"shot3": 7,
"shot4": 6,
"shot5": 6
}
},
"total_score": 142,
"completed": true
},
"3": {
"name": "Ivan Tandler",
"targets": {
"1": {
"shot1": 0,
"shot2": 0,
"shot3": 0,
"shot4": 0,
"shot5": 0
},
"2": {
"shot1": 0,
"shot2": 0,
"shot3": 0,
"shot4": 0,
"shot5": 0
},
"3": {
"shot1": 0,
"shot2": 0,
"shot3": 0,
"shot4": 0,
"shot5": 0
},
"4": {
"shot1": 0,
"shot2": 0,
"shot3": 0,
"shot4": 0,
"shot5": 0
}
},
"total_score": 0,
"completed": true
},
"17": {
"name": "Du\u0161an Onuk",
"targets": {
"1": {
"shot1": 10,
"shot2": 9,
"shot3": 9,
"shot4": 9,
"shot5": 9
},
"2": {
"shot1": 10,
"shot2": 10,
"shot3": 9,
"shot4": 8,
"shot5": 7
},
"3": {
"shot1": 10,
"shot2": 9,
"shot3": 9,
"shot4": 9,
"shot5": 9
},
"4": {
"shot1": 10,
"shot2": 9,
"shot3": 9,
"shot4": 8,
"shot5": 8
}
},
"total_score": 180,
"completed": true
},
"9": {
"name": "Janez Bo\u017ei\u010d",
"targets": {
"1": {
"shot1": 10,
"shot2": 10,
"shot3": 9,
"shot4": 9,
"shot5": 9
},
"2": {
"shot1": 10,
"shot2": 9,
"shot3": 8,
"shot4": 8,
"shot5": 7
},
"3": {
"shot1": 10,
"shot2": 10,
"shot3": 10,
"shot4": 9,
"shot5": 7
},
"4": {
"shot1": 10,
"shot2": 10,
"shot3": 10,
"shot4": 8,
"shot5": 6
}
},
"total_score": 179,
"completed": true
},
"44": {
"name": "Anka Ka\u010dnik",
"targets": {
"1": {
"shot1": 10,
"shot2": 9,
"shot3": 6,
"shot4": 4,
"shot5": 3
},
"2": {
"shot1": 9,
"shot2": 7,
"shot3": 6,
"shot4": 3,
"shot5": 2
},
"3": {
"shot1": 7,
"shot2": 4,
"shot3": 4,
"shot4": 2,
"shot5": 1
},
"4": {
"shot1": 7,
"shot2": 6,
"shot3": 5,
"shot4": 4,
"shot5": 3
}
},
"total_score": 102,
"completed": true
},
"48": {
"name": "Janja Salcman",
"targets": {
"1": {
"shot1": 7,
"shot2": 3,
"shot3": 2,
"shot4": 2,
"shot5": 0
},
"2": {
"shot1": 6,
"shot2": 5,
"shot3": 2,
"shot4": 2,
"shot5": 0
},
"3": {
"shot1": 9,
"shot2": 9,
"shot3": 7,
"shot4": 1,
"shot5": 0
},
"4": {
"shot1": 8,
"shot2": 5,
"shot3": 3,
"shot4": 1,
"shot5": 0
}
},
"total_score": 72,
"completed": true
},
"15": {
"name": "Jan Pleterski",
"targets": {
"1": {
"shot1": 10,
"shot2": 10,
"shot3": 9,
"shot4": 6,
"shot5": 6
},
"2": {
"shot1": 10,
"shot2": 10,
"shot3": 9,
"shot4": 8,
"shot5": 8
},
"3": {
"shot1": 10,
"shot2": 9,
"shot3": 9,
"shot4": 8,
"shot5": 6
},
"4": {
"shot1": 10,
"shot2": 9,
"shot3": 6,
"shot4": 5,
"shot5": 3
}
},
"total_score": 161,
"completed": true
},
"45": {
"name": "Lidija Blimen",
"targets": {
"1": {
"shot1": 10,
"shot2": 9,
"shot3": 6,
"shot4": 4,
"shot5": 4
},
"2": {
"shot1": 8,
"shot2": 8,
"shot3": 6,
"shot4": 6,
"shot5": 4
},
"3": {
"shot1": 8,
"shot2": 7,
"shot3": 4,
"shot4": 1,
"shot5": 1
},
"4": {
"shot1": 9,
"shot2": 8,
"shot3": 8,
"shot4": 8,
"shot5": 5
}
},
"total_score": 124,
"completed": true
},
"21": {
"name": "Marko Blimen",
"targets": {
"1": {
"shot1": 10,
"shot2": 9,
"shot3": 9,
"shot4": 8,
"shot5": 7
},
"2": {
"shot1": 10,
"shot2": 9,
"shot3": 9,
"shot4": 7,
"shot5": 7
},
"3": {
"shot1": 9,
"shot2": 8,
"shot3": 8,
"shot4": 7,
"shot5": 6
},
"4": {
"shot1": 10,
"shot2": 10,
"shot3": 9,
"shot4": 8,
"shot5": 7
}
},
"total_score": 167,
"completed": true
},
"49": {
"name": "Jolanda Verhnjak",
"targets": {
"1": {
"shot1": 8,
"shot2": 7,
"shot3": 6,
"shot4": 4,
"shot5": 3
},
"2": {
"shot1": 8,
"shot2": 8,
"shot3": 8,
"shot4": 8,
"shot5": 3
},
"3": {
"shot1": 7,
"shot2": 6,
"shot3": 5,
"shot4": 3,
"shot5": 3
},
"4": {
"shot1": 10,
"shot2": 8,
"shot3": 8,
"shot4": 5,
"shot5": 4
}
},
"total_score": 122,
"completed": true
},
"11": {
"name": "Rado Kefer",
"targets": {
"1": {
"shot1": 10,
"shot2": 9,
"shot3": 9,
"shot4": 8,
"shot5": 7
},
"2": {
"shot1": 10,
"shot2": 9,
"shot3": 9,
"shot4": 8,
"shot5": 8
},
"3": {
"shot1": 10,
"shot2": 9,
"shot3": 9,
"shot4": 8,
"shot5": 8
},
"4": {
"shot1": 9,
"shot2": 9,
"shot3": 9,
"shot4": 8,
"shot5": 8
}
},
"total_score": 174,
"completed": true
},
"12": {
"name": "Matej Kvasnik",
"targets": {
"1": {
"shot1": 9,
"shot2": 8,
"shot3": 8,
"shot4": 8,
"shot5": 6
},
"2": {
"shot1": 9,
"shot2": 9,
"shot3": 8,
"shot4": 6,
"shot5": 4
},
"3": {
"shot1": 10,
"shot2": 8,
"shot3": 6,
"shot4": 5,
"shot5": 6
},
"4": {
"shot1": 8,
"shot2": 8,
"shot3": 8,
"shot4": 7,
"shot5": 3
}
},
"total_score": 144,
"completed": true
},
"47": {
"name": "Ljuba Mr\u0161ak",
"targets": {
"1": {
"shot1": 0,
"shot2": 0,
"shot3": 0,
"shot4": 0,
"shot5": 0
},
"2": {
"shot1": 0,
"shot2": 0,
"shot3": 0,
"shot4": 0,
"shot5": 0
},
"3": {
"shot1": 0,
"shot2": 0,
"shot3": 0,
"shot4": 0,
"shot5": 0
},
"4": {
"shot1": 0,
"shot2": 0,
"shot3": 0,
"shot4": 0,
"shot5": 0
}
},
"total_score": 0,
"completed": true
},
"46": {
"name": "Tijana \u0160tumpfl",
"targets": {
"1": {
"shot1": 10,
"shot2": 10,
"shot3": 10,
"shot4": 1,
"shot5": 1
},
"2": {
"shot1": 5,
"shot2": 4,
"shot3": 2,
"shot4": 0,
"shot5": 0
},
"3": {
"shot1": 7,
"shot2": 7,
"shot3": 2,
"shot4": 1,
"shot5": 0
},
"4": {
"shot1": 5,
"shot2": 3,
"shot3": 2,
"shot4": 1,
"shot5": 0
}
},
"total_score": 71,
"completed": true
},
"1": {
"name": "Domen Pleterski",
"targets": {
"1": {
"shot1": 10,
"shot2": 10,
"shot3": 8,
"shot4": 8,
"shot5": 8
},
"2": {
"shot1": 9,
"shot2": 9,
"shot3": 9,
"shot4": 9,
"shot5": 7
},
"3": {
"shot1": 10,
"shot2": 10,
"shot3": 9,
"shot4": 9,
"shot5": 8
},
"4": {
"shot1": 10,
"shot2": 10,
"shot3": 10,
"shot4": 9,
"shot5": 8
}
},
"total_score": 180,
"completed": true
},
"7": {
"name": "Branko Poker\u017enik",
"targets": {
"1": {
"shot1": 9,
"shot2": 9,
"shot3": 9,
"shot4": 8,
"shot5": 7
},
"2": {
"shot1": 10,
"shot2": 10,
"shot3": 9,
"shot4": 8,
"shot5": 7
},
"3": {
"shot1": 10,
"shot2": 10,
"shot3": 9,
"shot4": 8,
"shot5": 8
},
"4": {
"shot1": 9,
"shot2": 9,
"shot3": 9,
"shot4": 8,
"shot5": 6
}
},
"total_score": 172,
"completed": true
},
"5": {
"name": "Jo\u017ee Verhnjak",
"targets": {
"1": {
"shot1": 10,
"shot2": 9,
"shot3": 8,
"shot4": 8,
"shot5": 5
},
"2": {
"shot1": 9,
"shot2": 8,
"shot3": 8,
"shot4": 6,
"shot5": 4
},
"3": {
"shot1": 10,
"shot2": 9,
"shot3": 8,
"shot4": 8,
"shot5": 7
},
"4": {
"shot1": 8,
"shot2": 9,
"shot3": 7,
"shot4": 6,
"shot5": 8
}
},
"total_score": 155,
"completed": true
}
},
"tournament_finished": true,
"created_at": "2025-08-30T13:09:30.386527",
"finished_at": "2025-08-30T15:33:42.405404"
},
"archived_at": "2025-08-30T15:33:42.405434"
}
@@ -1,759 +0,0 @@
{
"tournament": {
"rounds": [
{
"round_number": 1,
"players": [
{
"id": 4,
"name": "Mateja Pleterski",
"enabled": true
},
{
"id": 21,
"name": "Marko Blimen",
"enabled": true
},
{
"id": 3,
"name": "Ivan Tandler",
"enabled": true
},
{
"id": 7,
"name": "Branko Poker\u017enik",
"enabled": true
},
{
"id": 46,
"name": "Tijana \u0160tumpfl",
"enabled": true
},
{
"id": 9,
"name": "Janez Bo\u017ei\u010d",
"enabled": true
}
],
"status": "pending"
},
{
"round_number": 2,
"players": [
{
"id": 48,
"name": "Janja Salcman",
"enabled": true
},
{
"id": 45,
"name": "Lidija Blimen",
"enabled": true
},
{
"id": 20,
"name": "Jo\u017ee Preglav",
"enabled": true
},
{
"id": 11,
"name": "Rado Kefer",
"enabled": true
},
{
"id": 17,
"name": "Du\u0161an Onuk",
"enabled": true
},
{
"id": 15,
"name": "Jan Pleterski",
"enabled": true
}
],
"status": "waiting"
},
{
"round_number": 3,
"players": [
{
"id": 5,
"name": "Jo\u017ee Verhnjak",
"enabled": true
},
{
"id": 1,
"name": "Domen Pleterski",
"enabled": true
},
{
"id": 44,
"name": "Anka Ka\u010dnik",
"enabled": true
},
{
"id": 47,
"name": "Ljuba Mr\u0161ak",
"enabled": true
},
{
"id": 12,
"name": "Matej Kvasnik",
"enabled": true
},
{
"id": 49,
"name": "Jolanda Verhnjak",
"enabled": true
}
],
"status": "waiting"
}
],
"created_at": "2025-09-08T19:19:14.422360",
"total_players": 18,
"total_rounds": 3,
"current_round": 1,
"tournament_type": "4_targets"
},
"results": {
"tournament_id": "2025-09-08T19:19:14.422360",
"tournament_type": "4_targets",
"participants": {
"4": {
"name": "Mateja Pleterski",
"targets": {
"1": {
"shot1": 10,
"shot2": 9,
"shot3": 2,
"shot4": 10,
"shot5": 4
},
"2": {
"shot1": 8,
"shot2": 9,
"shot3": 8,
"shot4": 3,
"shot5": 7
},
"3": {
"shot1": 4,
"shot2": 2,
"shot3": 6,
"shot4": 0,
"shot5": 10
},
"4": {
"shot1": 7,
"shot2": 9,
"shot3": 10,
"shot4": 5,
"shot5": 6
}
},
"total_score": 129,
"completed": true
},
"21": {
"name": "Marko Blimen",
"targets": {
"1": {
"shot1": 9,
"shot2": 1,
"shot3": 8,
"shot4": 10,
"shot5": 4
},
"2": {
"shot1": 2,
"shot2": 4,
"shot3": 7,
"shot4": 7,
"shot5": 1
},
"3": {
"shot1": 0,
"shot2": 10,
"shot3": 1,
"shot4": 8,
"shot5": 9
},
"4": {
"shot1": 1,
"shot2": 8,
"shot3": 4,
"shot4": 7,
"shot5": 10
}
},
"total_score": 111,
"completed": true
},
"3": {
"name": "Ivan Tandler",
"targets": {
"1": {
"shot1": 2,
"shot2": 0,
"shot3": 9,
"shot4": 10,
"shot5": 2
},
"2": {
"shot1": 2,
"shot2": 0,
"shot3": 6,
"shot4": 10,
"shot5": 4
},
"3": {
"shot1": 0,
"shot2": 8,
"shot3": 2,
"shot4": 7,
"shot5": 4
},
"4": {
"shot1": 10,
"shot2": 1,
"shot3": 6,
"shot4": 7,
"shot5": 7
}
},
"total_score": 97,
"completed": true
},
"7": {
"name": "Branko Poker\u017enik",
"targets": {
"1": {
"shot1": 5,
"shot2": 9,
"shot3": 1,
"shot4": 9,
"shot5": 10
},
"2": {
"shot1": 2,
"shot2": 9,
"shot3": 10,
"shot4": 10,
"shot5": 7
},
"3": {
"shot1": 10,
"shot2": 1,
"shot3": 7,
"shot4": 4,
"shot5": 8
},
"4": {
"shot1": 2,
"shot2": 0,
"shot3": 10,
"shot4": 5,
"shot5": 0
}
},
"total_score": 119,
"completed": true
},
"46": {
"name": "Tijana \u0160tumpfl",
"targets": {
"1": {
"shot1": 6,
"shot2": 9,
"shot3": 4,
"shot4": 6,
"shot5": 1
},
"2": {
"shot1": 7,
"shot2": 8,
"shot3": 6,
"shot4": 5,
"shot5": 3
},
"3": {
"shot1": 10,
"shot2": 0,
"shot3": 6,
"shot4": 6,
"shot5": 8
},
"4": {
"shot1": 10,
"shot2": 4,
"shot3": 0,
"shot4": 1,
"shot5": 10
}
},
"total_score": 110,
"completed": true
},
"9": {
"name": "Janez Bo\u017ei\u010d",
"targets": {
"1": {
"shot1": 5,
"shot2": 10,
"shot3": 8,
"shot4": 5,
"shot5": 5
},
"2": {
"shot1": 8,
"shot2": 9,
"shot3": 10,
"shot4": 7,
"shot5": 10
},
"3": {
"shot1": 7,
"shot2": 5,
"shot3": 10,
"shot4": 4,
"shot5": 9
},
"4": {
"shot1": 8,
"shot2": 1,
"shot3": 6,
"shot4": 2,
"shot5": 5
}
},
"total_score": 134,
"completed": true
},
"48": {
"name": "Janja Salcman",
"targets": {
"1": {
"shot1": 10,
"shot2": 6,
"shot3": 4,
"shot4": 3,
"shot5": 9
},
"2": {
"shot1": 10,
"shot2": 5,
"shot3": 1,
"shot4": 1,
"shot5": 10
},
"3": {
"shot1": 9,
"shot2": 4,
"shot3": 0,
"shot4": 8,
"shot5": 9
},
"4": {
"shot1": 0,
"shot2": 1,
"shot3": 2,
"shot4": 0,
"shot5": 9
}
},
"total_score": 101,
"completed": true
},
"45": {
"name": "Lidija Blimen",
"targets": {
"1": {
"shot1": 2,
"shot2": 4,
"shot3": 1,
"shot4": 2,
"shot5": 6
},
"2": {
"shot1": 7,
"shot2": 0,
"shot3": 1,
"shot4": 2,
"shot5": 4
},
"3": {
"shot1": 4,
"shot2": 10,
"shot3": 2,
"shot4": 0,
"shot5": 7
},
"4": {
"shot1": 5,
"shot2": 7,
"shot3": 4,
"shot4": 7,
"shot5": 4
}
},
"total_score": 79,
"completed": true
},
"20": {
"name": "Jo\u017ee Preglav",
"targets": {
"1": {
"shot1": 7,
"shot2": 6,
"shot3": 7,
"shot4": 2,
"shot5": 4
},
"2": {
"shot1": 9,
"shot2": 4,
"shot3": 2,
"shot4": 0,
"shot5": 8
},
"3": {
"shot1": 5,
"shot2": 3,
"shot3": 8,
"shot4": 3,
"shot5": 4
},
"4": {
"shot1": 3,
"shot2": 10,
"shot3": 2,
"shot4": 9,
"shot5": 5
}
},
"total_score": 101,
"completed": true
},
"11": {
"name": "Rado Kefer",
"targets": {
"1": {
"shot1": 8,
"shot2": 10,
"shot3": 7,
"shot4": 10,
"shot5": 7
},
"2": {
"shot1": 10,
"shot2": 6,
"shot3": 0,
"shot4": 7,
"shot5": 4
},
"3": {
"shot1": 1,
"shot2": 6,
"shot3": 8,
"shot4": 9,
"shot5": 1
},
"4": {
"shot1": 0,
"shot2": 1,
"shot3": 0,
"shot4": 6,
"shot5": 9
}
},
"total_score": 110,
"completed": true
},
"17": {
"name": "Du\u0161an Onuk",
"targets": {
"1": {
"shot1": 0,
"shot2": 7,
"shot3": 0,
"shot4": 2,
"shot5": 2
},
"2": {
"shot1": 5,
"shot2": 1,
"shot3": 6,
"shot4": 4,
"shot5": 7
},
"3": {
"shot1": 7,
"shot2": 7,
"shot3": 0,
"shot4": 6,
"shot5": 3
},
"4": {
"shot1": 6,
"shot2": 4,
"shot3": 8,
"shot4": 9,
"shot5": 5
}
},
"total_score": 89,
"completed": true
},
"15": {
"name": "Jan Pleterski",
"targets": {
"1": {
"shot1": 1,
"shot2": 10,
"shot3": 8,
"shot4": 9,
"shot5": 8
},
"2": {
"shot1": 7,
"shot2": 2,
"shot3": 5,
"shot4": 4,
"shot5": 5
},
"3": {
"shot1": 10,
"shot2": 6,
"shot3": 1,
"shot4": 9,
"shot5": 10
},
"4": {
"shot1": 6,
"shot2": 2,
"shot3": 1,
"shot4": 2,
"shot5": 1
}
},
"total_score": 107,
"completed": true
},
"5": {
"name": "Jo\u017ee Verhnjak",
"targets": {
"1": {
"shot1": 1,
"shot2": 10,
"shot3": 10,
"shot4": 9,
"shot5": 1
},
"2": {
"shot1": 0,
"shot2": 10,
"shot3": 5,
"shot4": 10,
"shot5": 3
},
"3": {
"shot1": 8,
"shot2": 7,
"shot3": 4,
"shot4": 0,
"shot5": 4
},
"4": {
"shot1": 6,
"shot2": 1,
"shot3": 1,
"shot4": 9,
"shot5": 8
}
},
"total_score": 107,
"completed": true
},
"1": {
"name": "Domen Pleterski",
"targets": {
"1": {
"shot1": 0,
"shot2": 5,
"shot3": 10,
"shot4": 9,
"shot5": 6
},
"2": {
"shot1": 4,
"shot2": 9,
"shot3": 8,
"shot4": 6,
"shot5": 2
},
"3": {
"shot1": 7,
"shot2": 3,
"shot3": 7,
"shot4": 2,
"shot5": 0
},
"4": {
"shot1": 2,
"shot2": 4,
"shot3": 4,
"shot4": 5,
"shot5": 2
}
},
"total_score": 95,
"completed": true
},
"44": {
"name": "Anka Ka\u010dnik",
"targets": {
"1": {
"shot1": 1,
"shot2": 4,
"shot3": 10,
"shot4": 5,
"shot5": 9
},
"2": {
"shot1": 4,
"shot2": 5,
"shot3": 10,
"shot4": 3,
"shot5": 2
},
"3": {
"shot1": 6,
"shot2": 3,
"shot3": 7,
"shot4": 3,
"shot5": 5
},
"4": {
"shot1": 5,
"shot2": 10,
"shot3": 8,
"shot4": 10,
"shot5": 8
}
},
"total_score": 118,
"completed": true
},
"47": {
"name": "Ljuba Mr\u0161ak",
"targets": {
"1": {
"shot1": 5,
"shot2": 9,
"shot3": 6,
"shot4": 7,
"shot5": 0
},
"2": {
"shot1": 0,
"shot2": 4,
"shot3": 0,
"shot4": 2,
"shot5": 8
},
"3": {
"shot1": 2,
"shot2": 0,
"shot3": 6,
"shot4": 5,
"shot5": 7
},
"4": {
"shot1": 9,
"shot2": 1,
"shot3": 2,
"shot4": 0,
"shot5": 9
}
},
"total_score": 82,
"completed": true
},
"12": {
"name": "Matej Kvasnik",
"targets": {
"1": {
"shot1": 2,
"shot2": 7,
"shot3": 1,
"shot4": 0,
"shot5": 5
},
"2": {
"shot1": 10,
"shot2": 0,
"shot3": 3,
"shot4": 3,
"shot5": 6
},
"3": {
"shot1": 7,
"shot2": 2,
"shot3": 6,
"shot4": 5,
"shot5": 0
},
"4": {
"shot1": 10,
"shot2": 8,
"shot3": 8,
"shot4": 8,
"shot5": 9
}
},
"total_score": 100,
"completed": true
},
"49": {
"name": "Jolanda Verhnjak",
"targets": {
"1": {
"shot1": 5,
"shot2": 2,
"shot3": 0,
"shot4": 3,
"shot5": 2
},
"2": {
"shot1": 8,
"shot2": 0,
"shot3": 10,
"shot4": 5,
"shot5": 4
},
"3": {
"shot1": 2,
"shot2": 9,
"shot3": 4,
"shot4": 9,
"shot5": 2
},
"4": {
"shot1": 2,
"shot2": 9,
"shot3": 0,
"shot4": 4,
"shot5": 10
}
},
"total_score": 90,
"completed": true
}
},
"tournament_finished": true,
"created_at": "2025-09-08T19:19:14.422540",
"finished_at": "2025-09-08T19:19:26.929175"
},
"archived_at": "2025-09-08T19:19:26.929203"
}
@@ -1,759 +0,0 @@
{
"tournament": {
"rounds": [
{
"round_number": 1,
"players": [
{
"id": 9,
"name": "Janez Bo\u017ei\u010d",
"enabled": true
},
{
"id": 48,
"name": "Janja Salcman",
"enabled": true
},
{
"id": 4,
"name": "Mateja Pleterski",
"enabled": true
},
{
"id": 21,
"name": "Marko Blimen",
"enabled": true
},
{
"id": 45,
"name": "Lidija Blimen",
"enabled": true
},
{
"id": 1,
"name": "Domen Pleterski",
"enabled": true
}
],
"status": "pending"
},
{
"round_number": 2,
"players": [
{
"id": 44,
"name": "Anka Ka\u010dnik",
"enabled": true
},
{
"id": 12,
"name": "Matej Kvasnik",
"enabled": true
},
{
"id": 5,
"name": "Jo\u017ee Verhnjak",
"enabled": true
},
{
"id": 47,
"name": "Ljuba Mr\u0161ak",
"enabled": true
},
{
"id": 46,
"name": "Tijana \u0160tumpfl",
"enabled": true
},
{
"id": 49,
"name": "Jolanda Verhnjak",
"enabled": true
}
],
"status": "waiting"
},
{
"round_number": 3,
"players": [
{
"id": 17,
"name": "Du\u0161an Onuk",
"enabled": true
},
{
"id": 15,
"name": "Jan Pleterski",
"enabled": true
},
{
"id": 7,
"name": "Branko Poker\u017enik",
"enabled": true
},
{
"id": 20,
"name": "Jo\u017ee Preglav",
"enabled": true
},
{
"id": 11,
"name": "Rado Kefer",
"enabled": true
},
{
"id": 3,
"name": "Ivan Tandler",
"enabled": true
}
],
"status": "waiting"
}
],
"created_at": "2025-09-08T19:23:24.597390",
"total_players": 18,
"total_rounds": 3,
"current_round": 1,
"tournament_type": "4_targets"
},
"results": {
"tournament_id": "2025-09-08T19:23:24.597390",
"tournament_type": "4_targets",
"participants": {
"9": {
"name": "Janez Bo\u017ei\u010d",
"targets": {
"1": {
"shot1": 9,
"shot2": 2,
"shot3": 4,
"shot4": 6,
"shot5": 1
},
"2": {
"shot1": 1,
"shot2": 4,
"shot3": 4,
"shot4": 3,
"shot5": 4
},
"3": {
"shot1": 5,
"shot2": 4,
"shot3": 3,
"shot4": 3,
"shot5": 2
},
"4": {
"shot1": 3,
"shot2": 7,
"shot3": 2,
"shot4": 0,
"shot5": 0
}
},
"total_score": 67,
"completed": true
},
"48": {
"name": "Janja Salcman",
"targets": {
"1": {
"shot1": 6,
"shot2": 10,
"shot3": 0,
"shot4": 6,
"shot5": 5
},
"2": {
"shot1": 0,
"shot2": 9,
"shot3": 5,
"shot4": 4,
"shot5": 7
},
"3": {
"shot1": 8,
"shot2": 2,
"shot3": 8,
"shot4": 0,
"shot5": 1
},
"4": {
"shot1": 1,
"shot2": 4,
"shot3": 3,
"shot4": 5,
"shot5": 1
}
},
"total_score": 85,
"completed": true
},
"4": {
"name": "Mateja Pleterski",
"targets": {
"1": {
"shot1": 6,
"shot2": 10,
"shot3": 1,
"shot4": 1,
"shot5": 3
},
"2": {
"shot1": 3,
"shot2": 4,
"shot3": 6,
"shot4": 2,
"shot5": 10
},
"3": {
"shot1": 1,
"shot2": 10,
"shot3": 8,
"shot4": 5,
"shot5": 5
},
"4": {
"shot1": 9,
"shot2": 4,
"shot3": 9,
"shot4": 6,
"shot5": 3
}
},
"total_score": 106,
"completed": true
},
"21": {
"name": "Marko Blimen",
"targets": {
"1": {
"shot1": 3,
"shot2": 2,
"shot3": 3,
"shot4": 3,
"shot5": 3
},
"2": {
"shot1": 0,
"shot2": 6,
"shot3": 7,
"shot4": 0,
"shot5": 7
},
"3": {
"shot1": 3,
"shot2": 8,
"shot3": 1,
"shot4": 7,
"shot5": 6
},
"4": {
"shot1": 3,
"shot2": 4,
"shot3": 3,
"shot4": 8,
"shot5": 1
}
},
"total_score": 78,
"completed": true
},
"45": {
"name": "Lidija Blimen",
"targets": {
"1": {
"shot1": 8,
"shot2": 3,
"shot3": 8,
"shot4": 7,
"shot5": 7
},
"2": {
"shot1": 3,
"shot2": 0,
"shot3": 5,
"shot4": 7,
"shot5": 4
},
"3": {
"shot1": 7,
"shot2": 8,
"shot3": 0,
"shot4": 7,
"shot5": 3
},
"4": {
"shot1": 2,
"shot2": 10,
"shot3": 2,
"shot4": 9,
"shot5": 0
}
},
"total_score": 100,
"completed": true
},
"1": {
"name": "Domen Pleterski",
"targets": {
"1": {
"shot1": 9,
"shot2": 1,
"shot3": 3,
"shot4": 2,
"shot5": 4
},
"2": {
"shot1": 8,
"shot2": 4,
"shot3": 10,
"shot4": 9,
"shot5": 0
},
"3": {
"shot1": 7,
"shot2": 3,
"shot3": 2,
"shot4": 5,
"shot5": 6
},
"4": {
"shot1": 6,
"shot2": 6,
"shot3": 6,
"shot4": 3,
"shot5": 10
}
},
"total_score": 104,
"completed": true
},
"44": {
"name": "Anka Ka\u010dnik",
"targets": {
"1": {
"shot1": 6,
"shot2": 8,
"shot3": 6,
"shot4": 9,
"shot5": 2
},
"2": {
"shot1": 9,
"shot2": 0,
"shot3": 9,
"shot4": 2,
"shot5": 8
},
"3": {
"shot1": 5,
"shot2": 0,
"shot3": 4,
"shot4": 10,
"shot5": 3
},
"4": {
"shot1": 5,
"shot2": 4,
"shot3": 2,
"shot4": 7,
"shot5": 0
}
},
"total_score": 99,
"completed": true
},
"12": {
"name": "Matej Kvasnik",
"targets": {
"1": {
"shot1": 9,
"shot2": 1,
"shot3": 7,
"shot4": 9,
"shot5": 6
},
"2": {
"shot1": 10,
"shot2": 3,
"shot3": 2,
"shot4": 9,
"shot5": 2
},
"3": {
"shot1": 0,
"shot2": 9,
"shot3": 9,
"shot4": 3,
"shot5": 6
},
"4": {
"shot1": 4,
"shot2": 10,
"shot3": 6,
"shot4": 5,
"shot5": 2
}
},
"total_score": 112,
"completed": true
},
"5": {
"name": "Jo\u017ee Verhnjak",
"targets": {
"1": {
"shot1": 4,
"shot2": 9,
"shot3": 2,
"shot4": 4,
"shot5": 4
},
"2": {
"shot1": 3,
"shot2": 9,
"shot3": 4,
"shot4": 3,
"shot5": 1
},
"3": {
"shot1": 10,
"shot2": 7,
"shot3": 9,
"shot4": 0,
"shot5": 3
},
"4": {
"shot1": 10,
"shot2": 0,
"shot3": 8,
"shot4": 6,
"shot5": 10
}
},
"total_score": 106,
"completed": true
},
"47": {
"name": "Ljuba Mr\u0161ak",
"targets": {
"1": {
"shot1": 7,
"shot2": 10,
"shot3": 4,
"shot4": 2,
"shot5": 9
},
"2": {
"shot1": 1,
"shot2": 9,
"shot3": 2,
"shot4": 7,
"shot5": 0
},
"3": {
"shot1": 9,
"shot2": 8,
"shot3": 6,
"shot4": 5,
"shot5": 7
},
"4": {
"shot1": 9,
"shot2": 9,
"shot3": 10,
"shot4": 0,
"shot5": 2
}
},
"total_score": 116,
"completed": true
},
"46": {
"name": "Tijana \u0160tumpfl",
"targets": {
"1": {
"shot1": 10,
"shot2": 6,
"shot3": 9,
"shot4": 9,
"shot5": 7
},
"2": {
"shot1": 0,
"shot2": 6,
"shot3": 7,
"shot4": 6,
"shot5": 10
},
"3": {
"shot1": 3,
"shot2": 8,
"shot3": 6,
"shot4": 5,
"shot5": 5
},
"4": {
"shot1": 1,
"shot2": 2,
"shot3": 9,
"shot4": 8,
"shot5": 8
}
},
"total_score": 125,
"completed": true
},
"49": {
"name": "Jolanda Verhnjak",
"targets": {
"1": {
"shot1": 8,
"shot2": 0,
"shot3": 9,
"shot4": 6,
"shot5": 7
},
"2": {
"shot1": 4,
"shot2": 4,
"shot3": 6,
"shot4": 4,
"shot5": 8
},
"3": {
"shot1": 0,
"shot2": 9,
"shot3": 3,
"shot4": 4,
"shot5": 5
},
"4": {
"shot1": 2,
"shot2": 2,
"shot3": 0,
"shot4": 0,
"shot5": 0
}
},
"total_score": 81,
"completed": true
},
"17": {
"name": "Du\u0161an Onuk",
"targets": {
"1": {
"shot1": 3,
"shot2": 6,
"shot3": 5,
"shot4": 5,
"shot5": 6
},
"2": {
"shot1": 2,
"shot2": 4,
"shot3": 2,
"shot4": 8,
"shot5": 8
},
"3": {
"shot1": 3,
"shot2": 8,
"shot3": 5,
"shot4": 8,
"shot5": 1
},
"4": {
"shot1": 6,
"shot2": 5,
"shot3": 3,
"shot4": 1,
"shot5": 6
}
},
"total_score": 95,
"completed": true
},
"15": {
"name": "Jan Pleterski",
"targets": {
"1": {
"shot1": 9,
"shot2": 1,
"shot3": 7,
"shot4": 7,
"shot5": 6
},
"2": {
"shot1": 7,
"shot2": 2,
"shot3": 1,
"shot4": 10,
"shot5": 10
},
"3": {
"shot1": 9,
"shot2": 0,
"shot3": 3,
"shot4": 9,
"shot5": 10
},
"4": {
"shot1": 2,
"shot2": 2,
"shot3": 8,
"shot4": 4,
"shot5": 3
}
},
"total_score": 110,
"completed": true
},
"7": {
"name": "Branko Poker\u017enik",
"targets": {
"1": {
"shot1": 6,
"shot2": 1,
"shot3": 8,
"shot4": 9,
"shot5": 1
},
"2": {
"shot1": 6,
"shot2": 3,
"shot3": 10,
"shot4": 6,
"shot5": 0
},
"3": {
"shot1": 8,
"shot2": 6,
"shot3": 8,
"shot4": 0,
"shot5": 6
},
"4": {
"shot1": 2,
"shot2": 0,
"shot3": 10,
"shot4": 5,
"shot5": 6
}
},
"total_score": 101,
"completed": true
},
"20": {
"name": "Jo\u017ee Preglav",
"targets": {
"1": {
"shot1": 4,
"shot2": 1,
"shot3": 9,
"shot4": 2,
"shot5": 1
},
"2": {
"shot1": 0,
"shot2": 8,
"shot3": 0,
"shot4": 6,
"shot5": 5
},
"3": {
"shot1": 5,
"shot2": 7,
"shot3": 3,
"shot4": 1,
"shot5": 9
},
"4": {
"shot1": 4,
"shot2": 7,
"shot3": 3,
"shot4": 1,
"shot5": 1
}
},
"total_score": 77,
"completed": true
},
"11": {
"name": "Rado Kefer",
"targets": {
"1": {
"shot1": 1,
"shot2": 9,
"shot3": 7,
"shot4": 3,
"shot5": 10
},
"2": {
"shot1": 1,
"shot2": 2,
"shot3": 0,
"shot4": 2,
"shot5": 4
},
"3": {
"shot1": 1,
"shot2": 8,
"shot3": 10,
"shot4": 0,
"shot5": 0
},
"4": {
"shot1": 10,
"shot2": 2,
"shot3": 9,
"shot4": 1,
"shot5": 1
}
},
"total_score": 81,
"completed": true
},
"3": {
"name": "Ivan Tandler",
"targets": {
"1": {
"shot1": 5,
"shot2": 0,
"shot3": 9,
"shot4": 5,
"shot5": 7
},
"2": {
"shot1": 6,
"shot2": 0,
"shot3": 0,
"shot4": 1,
"shot5": 0
},
"3": {
"shot1": 4,
"shot2": 9,
"shot3": 5,
"shot4": 7,
"shot5": 3
},
"4": {
"shot1": 0,
"shot2": 5,
"shot3": 8,
"shot4": 5,
"shot5": 4
}
},
"total_score": 83,
"completed": true
}
},
"tournament_finished": true,
"created_at": "2025-09-08T19:23:24.597575",
"finished_at": "2025-09-08T19:23:34.271264"
},
"archived_at": "2025-09-08T19:23:34.271291"
}
@@ -1,759 +0,0 @@
{
"tournament": {
"rounds": [
{
"round_number": 1,
"players": [
{
"id": 44,
"name": "Anka Ka\u010dnik",
"enabled": true
},
{
"id": 9,
"name": "Janez Bo\u017ei\u010d",
"enabled": true
},
{
"id": 7,
"name": "Branko Poker\u017enik",
"enabled": true
},
{
"id": 1,
"name": "Domen Pleterski",
"enabled": true
},
{
"id": 21,
"name": "Marko Blimen",
"enabled": true
},
{
"id": 12,
"name": "Matej Kvasnik",
"enabled": true
}
],
"status": "pending"
},
{
"round_number": 2,
"players": [
{
"id": 48,
"name": "Janja Salcman",
"enabled": true
},
{
"id": 5,
"name": "Jo\u017ee Verhnjak",
"enabled": true
},
{
"id": 4,
"name": "Mateja Pleterski",
"enabled": true
},
{
"id": 20,
"name": "Jo\u017ee Preglav",
"enabled": true
},
{
"id": 15,
"name": "Jan Pleterski",
"enabled": true
},
{
"id": 17,
"name": "Du\u0161an Onuk",
"enabled": true
}
],
"status": "waiting"
},
{
"round_number": 3,
"players": [
{
"id": 46,
"name": "Tijana \u0160tumpfl",
"enabled": true
},
{
"id": 49,
"name": "Jolanda Verhnjak",
"enabled": true
},
{
"id": 3,
"name": "Ivan Tandler",
"enabled": true
},
{
"id": 11,
"name": "Rado Kefer",
"enabled": true
},
{
"id": 45,
"name": "Lidija Blimen",
"enabled": true
},
{
"id": 47,
"name": "Ljuba Mr\u0161ak",
"enabled": true
}
],
"status": "waiting"
}
],
"created_at": "2025-09-08T19:26:24.708355",
"total_players": 18,
"total_rounds": 3,
"current_round": 1,
"tournament_type": "4_targets"
},
"results": {
"tournament_id": "2025-09-08T19:26:24.708355",
"tournament_type": "4_targets",
"participants": {
"44": {
"name": "Anka Ka\u010dnik",
"targets": {
"1": {
"shot1": 8,
"shot2": 4,
"shot3": 0,
"shot4": 7,
"shot5": 4
},
"2": {
"shot1": 2,
"shot2": 7,
"shot3": 5,
"shot4": 0,
"shot5": 0
},
"3": {
"shot1": 0,
"shot2": 7,
"shot3": 8,
"shot4": 9,
"shot5": 7
},
"4": {
"shot1": 9,
"shot2": 6,
"shot3": 2,
"shot4": 4,
"shot5": 9
}
},
"total_score": 98,
"completed": true
},
"9": {
"name": "Janez Bo\u017ei\u010d",
"targets": {
"1": {
"shot1": 7,
"shot2": 7,
"shot3": 10,
"shot4": 4,
"shot5": 1
},
"2": {
"shot1": 9,
"shot2": 5,
"shot3": 5,
"shot4": 0,
"shot5": 4
},
"3": {
"shot1": 5,
"shot2": 1,
"shot3": 3,
"shot4": 4,
"shot5": 0
},
"4": {
"shot1": 6,
"shot2": 3,
"shot3": 3,
"shot4": 10,
"shot5": 7
}
},
"total_score": 94,
"completed": true
},
"7": {
"name": "Branko Poker\u017enik",
"targets": {
"1": {
"shot1": 9,
"shot2": 5,
"shot3": 10,
"shot4": 8,
"shot5": 3
},
"2": {
"shot1": 4,
"shot2": 4,
"shot3": 4,
"shot4": 7,
"shot5": 4
},
"3": {
"shot1": 8,
"shot2": 2,
"shot3": 5,
"shot4": 0,
"shot5": 9
},
"4": {
"shot1": 0,
"shot2": 4,
"shot3": 2,
"shot4": 4,
"shot5": 6
}
},
"total_score": 98,
"completed": true
},
"1": {
"name": "Domen Pleterski",
"targets": {
"1": {
"shot1": 0,
"shot2": 8,
"shot3": 8,
"shot4": 5,
"shot5": 3
},
"2": {
"shot1": 1,
"shot2": 7,
"shot3": 3,
"shot4": 4,
"shot5": 4
},
"3": {
"shot1": 1,
"shot2": 2,
"shot3": 0,
"shot4": 5,
"shot5": 5
},
"4": {
"shot1": 2,
"shot2": 8,
"shot3": 8,
"shot4": 7,
"shot5": 8
}
},
"total_score": 89,
"completed": true
},
"21": {
"name": "Marko Blimen",
"targets": {
"1": {
"shot1": 8,
"shot2": 8,
"shot3": 2,
"shot4": 8,
"shot5": 0
},
"2": {
"shot1": 1,
"shot2": 9,
"shot3": 4,
"shot4": 10,
"shot5": 1
},
"3": {
"shot1": 0,
"shot2": 4,
"shot3": 6,
"shot4": 8,
"shot5": 1
},
"4": {
"shot1": 10,
"shot2": 1,
"shot3": 7,
"shot4": 10,
"shot5": 9
}
},
"total_score": 107,
"completed": true
},
"12": {
"name": "Matej Kvasnik",
"targets": {
"1": {
"shot1": 3,
"shot2": 10,
"shot3": 0,
"shot4": 0,
"shot5": 0
},
"2": {
"shot1": 4,
"shot2": 7,
"shot3": 9,
"shot4": 7,
"shot5": 2
},
"3": {
"shot1": 8,
"shot2": 8,
"shot3": 3,
"shot4": 7,
"shot5": 8
},
"4": {
"shot1": 6,
"shot2": 7,
"shot3": 9,
"shot4": 7,
"shot5": 10
}
},
"total_score": 115,
"completed": true
},
"48": {
"name": "Janja Salcman",
"targets": {
"1": {
"shot1": 0,
"shot2": 1,
"shot3": 4,
"shot4": 8,
"shot5": 2
},
"2": {
"shot1": 3,
"shot2": 0,
"shot3": 0,
"shot4": 6,
"shot5": 0
},
"3": {
"shot1": 7,
"shot2": 6,
"shot3": 4,
"shot4": 3,
"shot5": 6
},
"4": {
"shot1": 5,
"shot2": 9,
"shot3": 10,
"shot4": 6,
"shot5": 7
}
},
"total_score": 87,
"completed": true
},
"5": {
"name": "Jo\u017ee Verhnjak",
"targets": {
"1": {
"shot1": 2,
"shot2": 8,
"shot3": 4,
"shot4": 10,
"shot5": 0
},
"2": {
"shot1": 0,
"shot2": 2,
"shot3": 5,
"shot4": 5,
"shot5": 1
},
"3": {
"shot1": 3,
"shot2": 0,
"shot3": 8,
"shot4": 5,
"shot5": 5
},
"4": {
"shot1": 9,
"shot2": 10,
"shot3": 5,
"shot4": 1,
"shot5": 6
}
},
"total_score": 89,
"completed": true
},
"4": {
"name": "Mateja Pleterski",
"targets": {
"1": {
"shot1": 6,
"shot2": 10,
"shot3": 1,
"shot4": 7,
"shot5": 0
},
"2": {
"shot1": 9,
"shot2": 1,
"shot3": 9,
"shot4": 2,
"shot5": 6
},
"3": {
"shot1": 7,
"shot2": 1,
"shot3": 4,
"shot4": 1,
"shot5": 9
},
"4": {
"shot1": 2,
"shot2": 6,
"shot3": 8,
"shot4": 4,
"shot5": 4
}
},
"total_score": 97,
"completed": true
},
"20": {
"name": "Jo\u017ee Preglav",
"targets": {
"1": {
"shot1": 5,
"shot2": 10,
"shot3": 4,
"shot4": 8,
"shot5": 9
},
"2": {
"shot1": 7,
"shot2": 6,
"shot3": 0,
"shot4": 6,
"shot5": 0
},
"3": {
"shot1": 6,
"shot2": 9,
"shot3": 1,
"shot4": 6,
"shot5": 1
},
"4": {
"shot1": 1,
"shot2": 2,
"shot3": 5,
"shot4": 0,
"shot5": 6
}
},
"total_score": 92,
"completed": true
},
"15": {
"name": "Jan Pleterski",
"targets": {
"1": {
"shot1": 0,
"shot2": 6,
"shot3": 9,
"shot4": 3,
"shot5": 8
},
"2": {
"shot1": 3,
"shot2": 0,
"shot3": 6,
"shot4": 4,
"shot5": 0
},
"3": {
"shot1": 3,
"shot2": 0,
"shot3": 4,
"shot4": 4,
"shot5": 10
},
"4": {
"shot1": 6,
"shot2": 9,
"shot3": 8,
"shot4": 0,
"shot5": 1
}
},
"total_score": 84,
"completed": true
},
"17": {
"name": "Du\u0161an Onuk",
"targets": {
"1": {
"shot1": 7,
"shot2": 5,
"shot3": 2,
"shot4": 7,
"shot5": 5
},
"2": {
"shot1": 9,
"shot2": 3,
"shot3": 8,
"shot4": 0,
"shot5": 6
},
"3": {
"shot1": 1,
"shot2": 6,
"shot3": 5,
"shot4": 5,
"shot5": 7
},
"4": {
"shot1": 3,
"shot2": 7,
"shot3": 6,
"shot4": 7,
"shot5": 7
}
},
"total_score": 106,
"completed": true
},
"46": {
"name": "Tijana \u0160tumpfl",
"targets": {
"1": {
"shot1": 5,
"shot2": 4,
"shot3": 4,
"shot4": 9,
"shot5": 3
},
"2": {
"shot1": 7,
"shot2": 0,
"shot3": 5,
"shot4": 4,
"shot5": 7
},
"3": {
"shot1": 2,
"shot2": 4,
"shot3": 7,
"shot4": 5,
"shot5": 9
},
"4": {
"shot1": 1,
"shot2": 4,
"shot3": 0,
"shot4": 1,
"shot5": 6
}
},
"total_score": 87,
"completed": true
},
"49": {
"name": "Jolanda Verhnjak",
"targets": {
"1": {
"shot1": 9,
"shot2": 10,
"shot3": 5,
"shot4": 8,
"shot5": 2
},
"2": {
"shot1": 4,
"shot2": 8,
"shot3": 8,
"shot4": 5,
"shot5": 1
},
"3": {
"shot1": 3,
"shot2": 5,
"shot3": 2,
"shot4": 6,
"shot5": 7
},
"4": {
"shot1": 3,
"shot2": 2,
"shot3": 1,
"shot4": 1,
"shot5": 0
}
},
"total_score": 90,
"completed": true
},
"3": {
"name": "Ivan Tandler",
"targets": {
"1": {
"shot1": 8,
"shot2": 10,
"shot3": 0,
"shot4": 8,
"shot5": 0
},
"2": {
"shot1": 3,
"shot2": 9,
"shot3": 3,
"shot4": 6,
"shot5": 6
},
"3": {
"shot1": 10,
"shot2": 8,
"shot3": 5,
"shot4": 1,
"shot5": 10
},
"4": {
"shot1": 9,
"shot2": 7,
"shot3": 8,
"shot4": 6,
"shot5": 9
}
},
"total_score": 126,
"completed": true
},
"11": {
"name": "Rado Kefer",
"targets": {
"1": {
"shot1": 10,
"shot2": 7,
"shot3": 3,
"shot4": 8,
"shot5": 6
},
"2": {
"shot1": 9,
"shot2": 1,
"shot3": 5,
"shot4": 3,
"shot5": 8
},
"3": {
"shot1": 2,
"shot2": 3,
"shot3": 5,
"shot4": 2,
"shot5": 1
},
"4": {
"shot1": 1,
"shot2": 0,
"shot3": 3,
"shot4": 1,
"shot5": 6
}
},
"total_score": 84,
"completed": true
},
"45": {
"name": "Lidija Blimen",
"targets": {
"1": {
"shot1": 9,
"shot2": 1,
"shot3": 8,
"shot4": 5,
"shot5": 8
},
"2": {
"shot1": 5,
"shot2": 7,
"shot3": 8,
"shot4": 2,
"shot5": 9
},
"3": {
"shot1": 4,
"shot2": 10,
"shot3": 5,
"shot4": 7,
"shot5": 7
},
"4": {
"shot1": 0,
"shot2": 1,
"shot3": 0,
"shot4": 4,
"shot5": 0
}
},
"total_score": 100,
"completed": true
},
"47": {
"name": "Ljuba Mr\u0161ak",
"targets": {
"1": {
"shot1": 5,
"shot2": 0,
"shot3": 7,
"shot4": 4,
"shot5": 1
},
"2": {
"shot1": 2,
"shot2": 7,
"shot3": 3,
"shot4": 1,
"shot5": 4
},
"3": {
"shot1": 1,
"shot2": 4,
"shot3": 1,
"shot4": 3,
"shot5": 1
},
"4": {
"shot1": 0,
"shot2": 5,
"shot3": 3,
"shot4": 9,
"shot5": 7
}
},
"total_score": 68,
"completed": true
}
},
"tournament_finished": true,
"created_at": "2025-09-08T19:26:24.708574",
"finished_at": "2025-09-08T19:26:31.871139"
},
"archived_at": "2025-09-08T19:26:31.871173"
}
@@ -1,759 +0,0 @@
{
"tournament": {
"rounds": [
{
"round_number": 1,
"players": [
{
"id": 5,
"name": "Jo\u017ee Verhnjak",
"enabled": true
},
{
"id": 20,
"name": "Jo\u017ee Preglav",
"enabled": true
},
{
"id": 12,
"name": "Matej Kvasnik",
"enabled": true
},
{
"id": 9,
"name": "Janez Bo\u017ei\u010d",
"enabled": true
},
{
"id": 21,
"name": "Marko Blimen",
"enabled": true
},
{
"id": 17,
"name": "Du\u0161an Onuk",
"enabled": true
}
],
"status": "pending"
},
{
"round_number": 2,
"players": [
{
"id": 45,
"name": "Lidija Blimen",
"enabled": true
},
{
"id": 47,
"name": "Ljuba Mr\u0161ak",
"enabled": true
},
{
"id": 3,
"name": "Ivan Tandler",
"enabled": true
},
{
"id": 49,
"name": "Jolanda Verhnjak",
"enabled": true
},
{
"id": 15,
"name": "Jan Pleterski",
"enabled": true
},
{
"id": 1,
"name": "Domen Pleterski",
"enabled": true
}
],
"status": "waiting"
},
{
"round_number": 3,
"players": [
{
"id": 7,
"name": "Branko Poker\u017enik",
"enabled": true
},
{
"id": 4,
"name": "Mateja Pleterski",
"enabled": true
},
{
"id": 11,
"name": "Rado Kefer",
"enabled": true
},
{
"id": 44,
"name": "Anka Ka\u010dnik",
"enabled": true
},
{
"id": 46,
"name": "Tijana \u0160tumpfl",
"enabled": true
},
{
"id": 48,
"name": "Janja Salcman",
"enabled": true
}
],
"status": "waiting"
}
],
"created_at": "2025-09-08T19:27:18.397859",
"total_players": 18,
"total_rounds": 3,
"current_round": 1,
"tournament_type": "4_targets"
},
"results": {
"tournament_id": "2025-09-08T19:27:18.397859",
"tournament_type": "4_targets",
"participants": {
"5": {
"name": "Jo\u017ee Verhnjak",
"targets": {
"1": {
"shot1": 6,
"shot2": 0,
"shot3": 1,
"shot4": 2,
"shot5": 0
},
"2": {
"shot1": 4,
"shot2": 9,
"shot3": 1,
"shot4": 4,
"shot5": 3
},
"3": {
"shot1": 3,
"shot2": 6,
"shot3": 8,
"shot4": 6,
"shot5": 3
},
"4": {
"shot1": 8,
"shot2": 7,
"shot3": 8,
"shot4": 1,
"shot5": 5
}
},
"total_score": 85,
"completed": true
},
"20": {
"name": "Jo\u017ee Preglav",
"targets": {
"1": {
"shot1": 3,
"shot2": 0,
"shot3": 3,
"shot4": 5,
"shot5": 3
},
"2": {
"shot1": 1,
"shot2": 4,
"shot3": 2,
"shot4": 0,
"shot5": 1
},
"3": {
"shot1": 6,
"shot2": 3,
"shot3": 8,
"shot4": 2,
"shot5": 5
},
"4": {
"shot1": 4,
"shot2": 6,
"shot3": 2,
"shot4": 9,
"shot5": 2
}
},
"total_score": 69,
"completed": true
},
"12": {
"name": "Matej Kvasnik",
"targets": {
"1": {
"shot1": 1,
"shot2": 4,
"shot3": 2,
"shot4": 6,
"shot5": 6
},
"2": {
"shot1": 5,
"shot2": 1,
"shot3": 2,
"shot4": 0,
"shot5": 8
},
"3": {
"shot1": 1,
"shot2": 4,
"shot3": 7,
"shot4": 3,
"shot5": 10
},
"4": {
"shot1": 0,
"shot2": 3,
"shot3": 3,
"shot4": 9,
"shot5": 5
}
},
"total_score": 80,
"completed": true
},
"9": {
"name": "Janez Bo\u017ei\u010d",
"targets": {
"1": {
"shot1": 10,
"shot2": 0,
"shot3": 6,
"shot4": 7,
"shot5": 6
},
"2": {
"shot1": 0,
"shot2": 7,
"shot3": 10,
"shot4": 10,
"shot5": 4
},
"3": {
"shot1": 9,
"shot2": 1,
"shot3": 3,
"shot4": 9,
"shot5": 6
},
"4": {
"shot1": 1,
"shot2": 9,
"shot3": 6,
"shot4": 5,
"shot5": 9
}
},
"total_score": 118,
"completed": true
},
"21": {
"name": "Marko Blimen",
"targets": {
"1": {
"shot1": 9,
"shot2": 8,
"shot3": 3,
"shot4": 0,
"shot5": 4
},
"2": {
"shot1": 9,
"shot2": 9,
"shot3": 9,
"shot4": 10,
"shot5": 3
},
"3": {
"shot1": 9,
"shot2": 4,
"shot3": 8,
"shot4": 10,
"shot5": 9
},
"4": {
"shot1": 4,
"shot2": 8,
"shot3": 1,
"shot4": 1,
"shot5": 0
}
},
"total_score": 118,
"completed": true
},
"17": {
"name": "Du\u0161an Onuk",
"targets": {
"1": {
"shot1": 5,
"shot2": 10,
"shot3": 8,
"shot4": 5,
"shot5": 1
},
"2": {
"shot1": 6,
"shot2": 3,
"shot3": 4,
"shot4": 9,
"shot5": 6
},
"3": {
"shot1": 7,
"shot2": 8,
"shot3": 1,
"shot4": 3,
"shot5": 4
},
"4": {
"shot1": 1,
"shot2": 4,
"shot3": 6,
"shot4": 9,
"shot5": 0
}
},
"total_score": 100,
"completed": true
},
"45": {
"name": "Lidija Blimen",
"targets": {
"1": {
"shot1": 1,
"shot2": 6,
"shot3": 0,
"shot4": 0,
"shot5": 8
},
"2": {
"shot1": 7,
"shot2": 7,
"shot3": 2,
"shot4": 10,
"shot5": 2
},
"3": {
"shot1": 2,
"shot2": 10,
"shot3": 1,
"shot4": 8,
"shot5": 10
},
"4": {
"shot1": 2,
"shot2": 8,
"shot3": 0,
"shot4": 8,
"shot5": 1
}
},
"total_score": 93,
"completed": true
},
"47": {
"name": "Ljuba Mr\u0161ak",
"targets": {
"1": {
"shot1": 9,
"shot2": 7,
"shot3": 0,
"shot4": 8,
"shot5": 2
},
"2": {
"shot1": 0,
"shot2": 0,
"shot3": 5,
"shot4": 4,
"shot5": 4
},
"3": {
"shot1": 4,
"shot2": 3,
"shot3": 9,
"shot4": 0,
"shot5": 0
},
"4": {
"shot1": 0,
"shot2": 3,
"shot3": 8,
"shot4": 7,
"shot5": 8
}
},
"total_score": 81,
"completed": true
},
"3": {
"name": "Ivan Tandler",
"targets": {
"1": {
"shot1": 7,
"shot2": 3,
"shot3": 5,
"shot4": 1,
"shot5": 0
},
"2": {
"shot1": 0,
"shot2": 5,
"shot3": 8,
"shot4": 10,
"shot5": 9
},
"3": {
"shot1": 7,
"shot2": 0,
"shot3": 0,
"shot4": 3,
"shot5": 6
},
"4": {
"shot1": 1,
"shot2": 8,
"shot3": 5,
"shot4": 0,
"shot5": 8
}
},
"total_score": 86,
"completed": true
},
"49": {
"name": "Jolanda Verhnjak",
"targets": {
"1": {
"shot1": 7,
"shot2": 2,
"shot3": 0,
"shot4": 8,
"shot5": 3
},
"2": {
"shot1": 10,
"shot2": 9,
"shot3": 7,
"shot4": 8,
"shot5": 6
},
"3": {
"shot1": 0,
"shot2": 2,
"shot3": 5,
"shot4": 3,
"shot5": 9
},
"4": {
"shot1": 2,
"shot2": 5,
"shot3": 10,
"shot4": 3,
"shot5": 5
}
},
"total_score": 104,
"completed": true
},
"15": {
"name": "Jan Pleterski",
"targets": {
"1": {
"shot1": 3,
"shot2": 0,
"shot3": 3,
"shot4": 2,
"shot5": 1
},
"2": {
"shot1": 10,
"shot2": 2,
"shot3": 10,
"shot4": 0,
"shot5": 6
},
"3": {
"shot1": 2,
"shot2": 9,
"shot3": 0,
"shot4": 4,
"shot5": 4
},
"4": {
"shot1": 2,
"shot2": 3,
"shot3": 8,
"shot4": 9,
"shot5": 5
}
},
"total_score": 83,
"completed": true
},
"1": {
"name": "Domen Pleterski",
"targets": {
"1": {
"shot1": 8,
"shot2": 5,
"shot3": 10,
"shot4": 8,
"shot5": 4
},
"2": {
"shot1": 0,
"shot2": 3,
"shot3": 6,
"shot4": 5,
"shot5": 2
},
"3": {
"shot1": 8,
"shot2": 0,
"shot3": 10,
"shot4": 2,
"shot5": 0
},
"4": {
"shot1": 0,
"shot2": 6,
"shot3": 7,
"shot4": 4,
"shot5": 8
}
},
"total_score": 96,
"completed": true
},
"7": {
"name": "Branko Poker\u017enik",
"targets": {
"1": {
"shot1": 9,
"shot2": 8,
"shot3": 4,
"shot4": 1,
"shot5": 3
},
"2": {
"shot1": 4,
"shot2": 5,
"shot3": 10,
"shot4": 0,
"shot5": 0
},
"3": {
"shot1": 6,
"shot2": 2,
"shot3": 9,
"shot4": 4,
"shot5": 2
},
"4": {
"shot1": 2,
"shot2": 8,
"shot3": 1,
"shot4": 8,
"shot5": 3
}
},
"total_score": 89,
"completed": true
},
"4": {
"name": "Mateja Pleterski",
"targets": {
"1": {
"shot1": 9,
"shot2": 10,
"shot3": 1,
"shot4": 6,
"shot5": 8
},
"2": {
"shot1": 10,
"shot2": 1,
"shot3": 0,
"shot4": 9,
"shot5": 10
},
"3": {
"shot1": 0,
"shot2": 1,
"shot3": 5,
"shot4": 5,
"shot5": 0
},
"4": {
"shot1": 3,
"shot2": 0,
"shot3": 10,
"shot4": 4,
"shot5": 2
}
},
"total_score": 94,
"completed": true
},
"11": {
"name": "Rado Kefer",
"targets": {
"1": {
"shot1": 7,
"shot2": 2,
"shot3": 7,
"shot4": 7,
"shot5": 9
},
"2": {
"shot1": 7,
"shot2": 8,
"shot3": 4,
"shot4": 6,
"shot5": 0
},
"3": {
"shot1": 2,
"shot2": 8,
"shot3": 5,
"shot4": 2,
"shot5": 10
},
"4": {
"shot1": 6,
"shot2": 5,
"shot3": 6,
"shot4": 5,
"shot5": 10
}
},
"total_score": 116,
"completed": true
},
"44": {
"name": "Anka Ka\u010dnik",
"targets": {
"1": {
"shot1": 10,
"shot2": 1,
"shot3": 5,
"shot4": 1,
"shot5": 8
},
"2": {
"shot1": 3,
"shot2": 4,
"shot3": 3,
"shot4": 8,
"shot5": 6
},
"3": {
"shot1": 5,
"shot2": 7,
"shot3": 10,
"shot4": 2,
"shot5": 6
},
"4": {
"shot1": 10,
"shot2": 8,
"shot3": 2,
"shot4": 9,
"shot5": 7
}
},
"total_score": 115,
"completed": true
},
"46": {
"name": "Tijana \u0160tumpfl",
"targets": {
"1": {
"shot1": 8,
"shot2": 6,
"shot3": 5,
"shot4": 0,
"shot5": 2
},
"2": {
"shot1": 3,
"shot2": 8,
"shot3": 6,
"shot4": 4,
"shot5": 1
},
"3": {
"shot1": 10,
"shot2": 10,
"shot3": 8,
"shot4": 7,
"shot5": 4
},
"4": {
"shot1": 7,
"shot2": 2,
"shot3": 2,
"shot4": 4,
"shot5": 3
}
},
"total_score": 100,
"completed": true
},
"48": {
"name": "Janja Salcman",
"targets": {
"1": {
"shot1": 5,
"shot2": 0,
"shot3": 1,
"shot4": 1,
"shot5": 4
},
"2": {
"shot1": 7,
"shot2": 4,
"shot3": 10,
"shot4": 2,
"shot5": 6
},
"3": {
"shot1": 6,
"shot2": 3,
"shot3": 2,
"shot4": 3,
"shot5": 5
},
"4": {
"shot1": 0,
"shot2": 8,
"shot3": 9,
"shot4": 6,
"shot5": 3
}
},
"total_score": 85,
"completed": true
}
},
"tournament_finished": true,
"created_at": "2025-09-08T19:27:18.398031",
"finished_at": "2025-09-08T19:27:27.631332"
},
"archived_at": "2025-09-08T19:27:27.631357"
}
@@ -1,759 +0,0 @@
{
"tournament": {
"rounds": [
{
"round_number": 1,
"players": [
{
"id": 49,
"name": "Jolanda Verhnjak",
"enabled": true
},
{
"id": 7,
"name": "Branko Poker\u017enik",
"enabled": true
},
{
"id": 3,
"name": "Ivan Tandler",
"enabled": true
},
{
"id": 46,
"name": "Tijana \u0160tumpfl",
"enabled": true
},
{
"id": 15,
"name": "Jan Pleterski",
"enabled": true
},
{
"id": 17,
"name": "Du\u0161an Onuk",
"enabled": true
}
],
"status": "pending"
},
{
"round_number": 2,
"players": [
{
"id": 12,
"name": "Matej Kvasnik",
"enabled": true
},
{
"id": 9,
"name": "Janez Bo\u017ei\u010d",
"enabled": true
},
{
"id": 4,
"name": "Mateja Pleterski",
"enabled": true
},
{
"id": 21,
"name": "Marko Blimen",
"enabled": true
},
{
"id": 20,
"name": "Jo\u017ee Preglav",
"enabled": true
},
{
"id": 11,
"name": "Rado Kefer",
"enabled": true
}
],
"status": "waiting"
},
{
"round_number": 3,
"players": [
{
"id": 47,
"name": "Ljuba Mr\u0161ak",
"enabled": true
},
{
"id": 48,
"name": "Janja Salcman",
"enabled": true
},
{
"id": 45,
"name": "Lidija Blimen",
"enabled": true
},
{
"id": 1,
"name": "Domen Pleterski",
"enabled": true
},
{
"id": 5,
"name": "Jo\u017ee Verhnjak",
"enabled": true
},
{
"id": 44,
"name": "Anka Ka\u010dnik",
"enabled": true
}
],
"status": "waiting"
}
],
"created_at": "2025-09-08T19:39:27.077222",
"total_players": 18,
"total_rounds": 3,
"current_round": 1,
"tournament_type": "4_targets"
},
"results": {
"tournament_id": "2025-09-08T19:39:27.077222",
"tournament_type": "4_targets",
"participants": {
"49": {
"name": "Jolanda Verhnjak",
"targets": {
"1": {
"shot1": 6,
"shot2": 10,
"shot3": 6,
"shot4": 3,
"shot5": 7
},
"2": {
"shot1": 6,
"shot2": 2,
"shot3": 1,
"shot4": 10,
"shot5": 0
},
"3": {
"shot1": 5,
"shot2": 5,
"shot3": 0,
"shot4": 3,
"shot5": 9
},
"4": {
"shot1": 9,
"shot2": 6,
"shot3": 8,
"shot4": 4,
"shot5": 8
}
},
"total_score": 108,
"completed": true
},
"7": {
"name": "Branko Poker\u017enik",
"targets": {
"1": {
"shot1": 10,
"shot2": 10,
"shot3": 6,
"shot4": 0,
"shot5": 9
},
"2": {
"shot1": 1,
"shot2": 2,
"shot3": 1,
"shot4": 4,
"shot5": 6
},
"3": {
"shot1": 3,
"shot2": 10,
"shot3": 5,
"shot4": 7,
"shot5": 7
},
"4": {
"shot1": 7,
"shot2": 7,
"shot3": 3,
"shot4": 2,
"shot5": 10
}
},
"total_score": 110,
"completed": true
},
"3": {
"name": "Ivan Tandler",
"targets": {
"1": {
"shot1": 6,
"shot2": 4,
"shot3": 1,
"shot4": 2,
"shot5": 0
},
"2": {
"shot1": 7,
"shot2": 6,
"shot3": 5,
"shot4": 10,
"shot5": 8
},
"3": {
"shot1": 4,
"shot2": 1,
"shot3": 4,
"shot4": 3,
"shot5": 7
},
"4": {
"shot1": 7,
"shot2": 2,
"shot3": 1,
"shot4": 1,
"shot5": 10
}
},
"total_score": 89,
"completed": true
},
"46": {
"name": "Tijana \u0160tumpfl",
"targets": {
"1": {
"shot1": 7,
"shot2": 10,
"shot3": 3,
"shot4": 7,
"shot5": 6
},
"2": {
"shot1": 5,
"shot2": 0,
"shot3": 10,
"shot4": 5,
"shot5": 1
},
"3": {
"shot1": 7,
"shot2": 9,
"shot3": 8,
"shot4": 6,
"shot5": 1
},
"4": {
"shot1": 3,
"shot2": 9,
"shot3": 0,
"shot4": 5,
"shot5": 0
}
},
"total_score": 102,
"completed": true
},
"15": {
"name": "Jan Pleterski",
"targets": {
"1": {
"shot1": 1,
"shot2": 4,
"shot3": 3,
"shot4": 1,
"shot5": 6
},
"2": {
"shot1": 5,
"shot2": 8,
"shot3": 1,
"shot4": 9,
"shot5": 2
},
"3": {
"shot1": 6,
"shot2": 9,
"shot3": 4,
"shot4": 1,
"shot5": 7
},
"4": {
"shot1": 5,
"shot2": 0,
"shot3": 2,
"shot4": 7,
"shot5": 4
}
},
"total_score": 85,
"completed": true
},
"17": {
"name": "Du\u0161an Onuk",
"targets": {
"1": {
"shot1": 6,
"shot2": 7,
"shot3": 8,
"shot4": 2,
"shot5": 8
},
"2": {
"shot1": 10,
"shot2": 7,
"shot3": 6,
"shot4": 9,
"shot5": 0
},
"3": {
"shot1": 1,
"shot2": 9,
"shot3": 0,
"shot4": 6,
"shot5": 4
},
"4": {
"shot1": 9,
"shot2": 5,
"shot3": 6,
"shot4": 3,
"shot5": 6
}
},
"total_score": 112,
"completed": true
},
"12": {
"name": "Matej Kvasnik",
"targets": {
"1": {
"shot1": 1,
"shot2": 0,
"shot3": 9,
"shot4": 2,
"shot5": 7
},
"2": {
"shot1": 7,
"shot2": 2,
"shot3": 4,
"shot4": 5,
"shot5": 5
},
"3": {
"shot1": 1,
"shot2": 0,
"shot3": 8,
"shot4": 2,
"shot5": 2
},
"4": {
"shot1": 6,
"shot2": 6,
"shot3": 5,
"shot4": 2,
"shot5": 8
}
},
"total_score": 82,
"completed": true
},
"9": {
"name": "Janez Bo\u017ei\u010d",
"targets": {
"1": {
"shot1": 9,
"shot2": 9,
"shot3": 7,
"shot4": 7,
"shot5": 9
},
"2": {
"shot1": 6,
"shot2": 0,
"shot3": 5,
"shot4": 1,
"shot5": 9
},
"3": {
"shot1": 8,
"shot2": 5,
"shot3": 10,
"shot4": 4,
"shot5": 4
},
"4": {
"shot1": 1,
"shot2": 2,
"shot3": 8,
"shot4": 1,
"shot5": 5
}
},
"total_score": 110,
"completed": true
},
"4": {
"name": "Mateja Pleterski",
"targets": {
"1": {
"shot1": 0,
"shot2": 10,
"shot3": 2,
"shot4": 4,
"shot5": 0
},
"2": {
"shot1": 0,
"shot2": 7,
"shot3": 4,
"shot4": 7,
"shot5": 0
},
"3": {
"shot1": 1,
"shot2": 0,
"shot3": 5,
"shot4": 6,
"shot5": 1
},
"4": {
"shot1": 4,
"shot2": 10,
"shot3": 4,
"shot4": 7,
"shot5": 2
}
},
"total_score": 74,
"completed": true
},
"21": {
"name": "Marko Blimen",
"targets": {
"1": {
"shot1": 8,
"shot2": 5,
"shot3": 4,
"shot4": 7,
"shot5": 8
},
"2": {
"shot1": 3,
"shot2": 1,
"shot3": 7,
"shot4": 2,
"shot5": 9
},
"3": {
"shot1": 1,
"shot2": 0,
"shot3": 2,
"shot4": 6,
"shot5": 5
},
"4": {
"shot1": 8,
"shot2": 3,
"shot3": 0,
"shot4": 3,
"shot5": 9
}
},
"total_score": 91,
"completed": true
},
"20": {
"name": "Jo\u017ee Preglav",
"targets": {
"1": {
"shot1": 1,
"shot2": 5,
"shot3": 0,
"shot4": 5,
"shot5": 9
},
"2": {
"shot1": 6,
"shot2": 5,
"shot3": 1,
"shot4": 8,
"shot5": 6
},
"3": {
"shot1": 4,
"shot2": 6,
"shot3": 10,
"shot4": 9,
"shot5": 2
},
"4": {
"shot1": 5,
"shot2": 9,
"shot3": 6,
"shot4": 3,
"shot5": 8
}
},
"total_score": 108,
"completed": true
},
"11": {
"name": "Rado Kefer",
"targets": {
"1": {
"shot1": 3,
"shot2": 5,
"shot3": 5,
"shot4": 1,
"shot5": 9
},
"2": {
"shot1": 2,
"shot2": 10,
"shot3": 8,
"shot4": 6,
"shot5": 1
},
"3": {
"shot1": 7,
"shot2": 9,
"shot3": 7,
"shot4": 9,
"shot5": 1
},
"4": {
"shot1": 0,
"shot2": 2,
"shot3": 9,
"shot4": 1,
"shot5": 3
}
},
"total_score": 98,
"completed": true
},
"47": {
"name": "Ljuba Mr\u0161ak",
"targets": {
"1": {
"shot1": 0,
"shot2": 2,
"shot3": 9,
"shot4": 7,
"shot5": 7
},
"2": {
"shot1": 7,
"shot2": 0,
"shot3": 0,
"shot4": 2,
"shot5": 3
},
"3": {
"shot1": 9,
"shot2": 9,
"shot3": 0,
"shot4": 6,
"shot5": 4
},
"4": {
"shot1": 2,
"shot2": 10,
"shot3": 9,
"shot4": 7,
"shot5": 7
}
},
"total_score": 100,
"completed": true
},
"48": {
"name": "Janja Salcman",
"targets": {
"1": {
"shot1": 8,
"shot2": 5,
"shot3": 3,
"shot4": 2,
"shot5": 2
},
"2": {
"shot1": 10,
"shot2": 6,
"shot3": 10,
"shot4": 4,
"shot5": 7
},
"3": {
"shot1": 10,
"shot2": 0,
"shot3": 6,
"shot4": 3,
"shot5": 3
},
"4": {
"shot1": 10,
"shot2": 0,
"shot3": 9,
"shot4": 8,
"shot5": 1
}
},
"total_score": 107,
"completed": true
},
"45": {
"name": "Lidija Blimen",
"targets": {
"1": {
"shot1": 8,
"shot2": 4,
"shot3": 8,
"shot4": 2,
"shot5": 1
},
"2": {
"shot1": 2,
"shot2": 4,
"shot3": 6,
"shot4": 7,
"shot5": 0
},
"3": {
"shot1": 7,
"shot2": 3,
"shot3": 10,
"shot4": 7,
"shot5": 8
},
"4": {
"shot1": 1,
"shot2": 2,
"shot3": 10,
"shot4": 4,
"shot5": 6
}
},
"total_score": 100,
"completed": true
},
"1": {
"name": "Domen Pleterski",
"targets": {
"1": {
"shot1": 9,
"shot2": 2,
"shot3": 10,
"shot4": 2,
"shot5": 2
},
"2": {
"shot1": 5,
"shot2": 4,
"shot3": 10,
"shot4": 10,
"shot5": 7
},
"3": {
"shot1": 9,
"shot2": 5,
"shot3": 5,
"shot4": 1,
"shot5": 9
},
"4": {
"shot1": 5,
"shot2": 8,
"shot3": 2,
"shot4": 10,
"shot5": 2
}
},
"total_score": 117,
"completed": true
},
"5": {
"name": "Jo\u017ee Verhnjak",
"targets": {
"1": {
"shot1": 2,
"shot2": 5,
"shot3": 3,
"shot4": 6,
"shot5": 5
},
"2": {
"shot1": 9,
"shot2": 7,
"shot3": 0,
"shot4": 2,
"shot5": 5
},
"3": {
"shot1": 5,
"shot2": 7,
"shot3": 0,
"shot4": 4,
"shot5": 2
},
"4": {
"shot1": 0,
"shot2": 7,
"shot3": 1,
"shot4": 4,
"shot5": 2
}
},
"total_score": 76,
"completed": true
},
"44": {
"name": "Anka Ka\u010dnik",
"targets": {
"1": {
"shot1": 5,
"shot2": 3,
"shot3": 2,
"shot4": 5,
"shot5": 1
},
"2": {
"shot1": 6,
"shot2": 7,
"shot3": 10,
"shot4": 10,
"shot5": 4
},
"3": {
"shot1": 1,
"shot2": 3,
"shot3": 1,
"shot4": 9,
"shot5": 0
},
"4": {
"shot1": 9,
"shot2": 10,
"shot3": 5,
"shot4": 10,
"shot5": 1
}
},
"total_score": 102,
"completed": true
}
},
"tournament_finished": true,
"created_at": "2025-09-08T19:39:27.077445",
"finished_at": "2025-09-08T19:39:44.030962"
},
"archived_at": "2025-09-08T19:39:44.030989"
}
@@ -1,759 +0,0 @@
{
"tournament": {
"rounds": [
{
"round_number": 1,
"players": [
{
"id": 12,
"name": "Matej Kvasnik",
"enabled": true
},
{
"id": 21,
"name": "Marko Blimen",
"enabled": true
},
{
"id": 1,
"name": "Domen Pleterski",
"enabled": true
},
{
"id": 4,
"name": "Mateja Pleterski",
"enabled": true
},
{
"id": 5,
"name": "Jo\u017ee Verhnjak",
"enabled": true
},
{
"id": 11,
"name": "Rado Kefer",
"enabled": true
}
],
"status": "pending"
},
{
"round_number": 2,
"players": [
{
"id": 44,
"name": "Anka Ka\u010dnik",
"enabled": true
},
{
"id": 48,
"name": "Janja Salcman",
"enabled": true
},
{
"id": 3,
"name": "Ivan Tandler",
"enabled": true
},
{
"id": 7,
"name": "Branko Poker\u017enik",
"enabled": true
},
{
"id": 9,
"name": "Janez Bo\u017ei\u010d",
"enabled": true
},
{
"id": 45,
"name": "Lidija Blimen",
"enabled": true
}
],
"status": "waiting"
},
{
"round_number": 3,
"players": [
{
"id": 47,
"name": "Ljuba Mr\u0161ak",
"enabled": true
},
{
"id": 46,
"name": "Tijana \u0160tumpfl",
"enabled": true
},
{
"id": 20,
"name": "Jo\u017ee Preglav",
"enabled": true
},
{
"id": 17,
"name": "Du\u0161an Onuk",
"enabled": true
},
{
"id": 15,
"name": "Jan Pleterski",
"enabled": true
},
{
"id": 49,
"name": "Jolanda Verhnjak",
"enabled": true
}
],
"status": "waiting"
}
],
"created_at": "2025-09-08T20:15:56.266521",
"total_players": 18,
"total_rounds": 3,
"current_round": 1,
"tournament_type": "4_targets"
},
"results": {
"tournament_id": "2025-09-08T20:15:56.266521",
"tournament_type": "4_targets",
"participants": {
"12": {
"name": "Matej Kvasnik",
"targets": {
"1": {
"shot1": 1,
"shot2": 6,
"shot3": 5,
"shot4": 1,
"shot5": 3
},
"2": {
"shot1": 8,
"shot2": 3,
"shot3": 3,
"shot4": 10,
"shot5": 4
},
"3": {
"shot1": 10,
"shot2": 0,
"shot3": 0,
"shot4": 2,
"shot5": 6
},
"4": {
"shot1": 7,
"shot2": 5,
"shot3": 0,
"shot4": 2,
"shot5": 5
}
},
"total_score": 81,
"completed": true
},
"21": {
"name": "Marko Blimen",
"targets": {
"1": {
"shot1": 1,
"shot2": 1,
"shot3": 0,
"shot4": 0,
"shot5": 5
},
"2": {
"shot1": 9,
"shot2": 0,
"shot3": 0,
"shot4": 2,
"shot5": 3
},
"3": {
"shot1": 3,
"shot2": 8,
"shot3": 2,
"shot4": 1,
"shot5": 0
},
"4": {
"shot1": 4,
"shot2": 6,
"shot3": 3,
"shot4": 9,
"shot5": 0
}
},
"total_score": 57,
"completed": true
},
"1": {
"name": "Domen Pleterski",
"targets": {
"1": {
"shot1": 0,
"shot2": 4,
"shot3": 0,
"shot4": 9,
"shot5": 10
},
"2": {
"shot1": 0,
"shot2": 3,
"shot3": 0,
"shot4": 1,
"shot5": 10
},
"3": {
"shot1": 5,
"shot2": 3,
"shot3": 4,
"shot4": 6,
"shot5": 10
},
"4": {
"shot1": 0,
"shot2": 2,
"shot3": 1,
"shot4": 8,
"shot5": 2
}
},
"total_score": 78,
"completed": true
},
"4": {
"name": "Mateja Pleterski",
"targets": {
"1": {
"shot1": 2,
"shot2": 0,
"shot3": 2,
"shot4": 3,
"shot5": 3
},
"2": {
"shot1": 7,
"shot2": 2,
"shot3": 9,
"shot4": 8,
"shot5": 6
},
"3": {
"shot1": 3,
"shot2": 8,
"shot3": 1,
"shot4": 1,
"shot5": 3
},
"4": {
"shot1": 10,
"shot2": 5,
"shot3": 10,
"shot4": 5,
"shot5": 7
}
},
"total_score": 95,
"completed": true
},
"5": {
"name": "Jo\u017ee Verhnjak",
"targets": {
"1": {
"shot1": 8,
"shot2": 3,
"shot3": 3,
"shot4": 2,
"shot5": 6
},
"2": {
"shot1": 7,
"shot2": 6,
"shot3": 7,
"shot4": 5,
"shot5": 9
},
"3": {
"shot1": 2,
"shot2": 0,
"shot3": 7,
"shot4": 6,
"shot5": 2
},
"4": {
"shot1": 6,
"shot2": 1,
"shot3": 5,
"shot4": 7,
"shot5": 5
}
},
"total_score": 97,
"completed": true
},
"11": {
"name": "Rado Kefer",
"targets": {
"1": {
"shot1": 4,
"shot2": 2,
"shot3": 3,
"shot4": 7,
"shot5": 2
},
"2": {
"shot1": 9,
"shot2": 6,
"shot3": 10,
"shot4": 7,
"shot5": 6
},
"3": {
"shot1": 8,
"shot2": 6,
"shot3": 9,
"shot4": 9,
"shot5": 1
},
"4": {
"shot1": 3,
"shot2": 0,
"shot3": 6,
"shot4": 8,
"shot5": 2
}
},
"total_score": 108,
"completed": true
},
"44": {
"name": "Anka Ka\u010dnik",
"targets": {
"1": {
"shot1": 8,
"shot2": 8,
"shot3": 4,
"shot4": 10,
"shot5": 3
},
"2": {
"shot1": 2,
"shot2": 8,
"shot3": 10,
"shot4": 7,
"shot5": 9
},
"3": {
"shot1": 3,
"shot2": 2,
"shot3": 0,
"shot4": 6,
"shot5": 9
},
"4": {
"shot1": 9,
"shot2": 5,
"shot3": 10,
"shot4": 10,
"shot5": 6
}
},
"total_score": 129,
"completed": true
},
"48": {
"name": "Janja Salcman",
"targets": {
"1": {
"shot1": 2,
"shot2": 6,
"shot3": 9,
"shot4": 4,
"shot5": 0
},
"2": {
"shot1": 0,
"shot2": 7,
"shot3": 3,
"shot4": 2,
"shot5": 0
},
"3": {
"shot1": 4,
"shot2": 6,
"shot3": 4,
"shot4": 8,
"shot5": 8
},
"4": {
"shot1": 2,
"shot2": 7,
"shot3": 0,
"shot4": 10,
"shot5": 3
}
},
"total_score": 85,
"completed": true
},
"3": {
"name": "Ivan Tandler",
"targets": {
"1": {
"shot1": 10,
"shot2": 9,
"shot3": 8,
"shot4": 8,
"shot5": 2
},
"2": {
"shot1": 7,
"shot2": 10,
"shot3": 3,
"shot4": 9,
"shot5": 2
},
"3": {
"shot1": 9,
"shot2": 4,
"shot3": 0,
"shot4": 2,
"shot5": 6
},
"4": {
"shot1": 0,
"shot2": 4,
"shot3": 1,
"shot4": 2,
"shot5": 10
}
},
"total_score": 106,
"completed": true
},
"7": {
"name": "Branko Poker\u017enik",
"targets": {
"1": {
"shot1": 4,
"shot2": 0,
"shot3": 2,
"shot4": 3,
"shot5": 6
},
"2": {
"shot1": 4,
"shot2": 5,
"shot3": 4,
"shot4": 10,
"shot5": 10
},
"3": {
"shot1": 2,
"shot2": 1,
"shot3": 0,
"shot4": 4,
"shot5": 5
},
"4": {
"shot1": 7,
"shot2": 10,
"shot3": 8,
"shot4": 5,
"shot5": 10
}
},
"total_score": 100,
"completed": true
},
"9": {
"name": "Janez Bo\u017ei\u010d",
"targets": {
"1": {
"shot1": 3,
"shot2": 9,
"shot3": 10,
"shot4": 5,
"shot5": 3
},
"2": {
"shot1": 5,
"shot2": 9,
"shot3": 6,
"shot4": 8,
"shot5": 5
},
"3": {
"shot1": 1,
"shot2": 7,
"shot3": 8,
"shot4": 1,
"shot5": 9
},
"4": {
"shot1": 6,
"shot2": 3,
"shot3": 10,
"shot4": 9,
"shot5": 4
}
},
"total_score": 121,
"completed": true
},
"45": {
"name": "Lidija Blimen",
"targets": {
"1": {
"shot1": 2,
"shot2": 9,
"shot3": 10,
"shot4": 2,
"shot5": 10
},
"2": {
"shot1": 8,
"shot2": 3,
"shot3": 7,
"shot4": 6,
"shot5": 8
},
"3": {
"shot1": 0,
"shot2": 7,
"shot3": 4,
"shot4": 1,
"shot5": 1
},
"4": {
"shot1": 2,
"shot2": 2,
"shot3": 4,
"shot4": 10,
"shot5": 1
}
},
"total_score": 97,
"completed": true
},
"47": {
"name": "Ljuba Mr\u0161ak",
"targets": {
"1": {
"shot1": 9,
"shot2": 1,
"shot3": 1,
"shot4": 5,
"shot5": 9
},
"2": {
"shot1": 2,
"shot2": 10,
"shot3": 10,
"shot4": 0,
"shot5": 9
},
"3": {
"shot1": 1,
"shot2": 5,
"shot3": 4,
"shot4": 10,
"shot5": 0
},
"4": {
"shot1": 3,
"shot2": 3,
"shot3": 2,
"shot4": 2,
"shot5": 2
}
},
"total_score": 88,
"completed": true
},
"46": {
"name": "Tijana \u0160tumpfl",
"targets": {
"1": {
"shot1": 1,
"shot2": 3,
"shot3": 2,
"shot4": 3,
"shot5": 3
},
"2": {
"shot1": 9,
"shot2": 5,
"shot3": 7,
"shot4": 7,
"shot5": 9
},
"3": {
"shot1": 3,
"shot2": 8,
"shot3": 1,
"shot4": 5,
"shot5": 6
},
"4": {
"shot1": 3,
"shot2": 0,
"shot3": 9,
"shot4": 9,
"shot5": 7
}
},
"total_score": 100,
"completed": true
},
"20": {
"name": "Jo\u017ee Preglav",
"targets": {
"1": {
"shot1": 4,
"shot2": 4,
"shot3": 9,
"shot4": 5,
"shot5": 2
},
"2": {
"shot1": 6,
"shot2": 0,
"shot3": 10,
"shot4": 0,
"shot5": 5
},
"3": {
"shot1": 7,
"shot2": 8,
"shot3": 9,
"shot4": 6,
"shot5": 5
},
"4": {
"shot1": 8,
"shot2": 7,
"shot3": 5,
"shot4": 5,
"shot5": 10
}
},
"total_score": 115,
"completed": true
},
"17": {
"name": "Du\u0161an Onuk",
"targets": {
"1": {
"shot1": 0,
"shot2": 3,
"shot3": 3,
"shot4": 2,
"shot5": 2
},
"2": {
"shot1": 2,
"shot2": 6,
"shot3": 1,
"shot4": 10,
"shot5": 10
},
"3": {
"shot1": 1,
"shot2": 7,
"shot3": 4,
"shot4": 5,
"shot5": 5
},
"4": {
"shot1": 10,
"shot2": 2,
"shot3": 8,
"shot4": 7,
"shot5": 9
}
},
"total_score": 97,
"completed": true
},
"15": {
"name": "Jan Pleterski",
"targets": {
"1": {
"shot1": 0,
"shot2": 0,
"shot3": 2,
"shot4": 0,
"shot5": 4
},
"2": {
"shot1": 1,
"shot2": 7,
"shot3": 8,
"shot4": 7,
"shot5": 9
},
"3": {
"shot1": 2,
"shot2": 8,
"shot3": 7,
"shot4": 2,
"shot5": 5
},
"4": {
"shot1": 9,
"shot2": 5,
"shot3": 1,
"shot4": 7,
"shot5": 3
}
},
"total_score": 87,
"completed": true
},
"49": {
"name": "Jolanda Verhnjak",
"targets": {
"1": {
"shot1": 8,
"shot2": 2,
"shot3": 7,
"shot4": 5,
"shot5": 0
},
"2": {
"shot1": 1,
"shot2": 8,
"shot3": 0,
"shot4": 4,
"shot5": 10
},
"3": {
"shot1": 0,
"shot2": 4,
"shot3": 4,
"shot4": 7,
"shot5": 4
},
"4": {
"shot1": 5,
"shot2": 6,
"shot3": 4,
"shot4": 6,
"shot5": 0
}
},
"total_score": 85,
"completed": true
}
},
"tournament_finished": true,
"created_at": "2025-09-08T20:15:56.266721",
"finished_at": "2025-09-08T20:16:01.584621"
},
"archived_at": "2025-09-08T20:16:01.584647"
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+107 -585
View File
@@ -1,3 +1,8 @@
"""
TV_APP V1.0.0 - Tournament and League Management System
Flask web application for managing tournaments with multi-camera streaming
"""
from flask import Flask, render_template, request, redirect, jsonify, session
import json
import os
@@ -7,63 +12,25 @@ from collections import defaultdict
from datetime import datetime
import re
# Import modular components
from app.utils import (
load_translations, get_current_language, get_translations,
is_mobile_device, calculate_tens_from_targets
)
from app.storage import (
SettingsStorage, PlayerStorage, TournamentStorage,
ResultsStorage, LeagueStorage, ArchiveStorage,
TOURNAMENT_FILE, RESULTS_FILE, LEAGUE_FILE
)
from app.models import Tournament, Scoring, RoundManager
# Initialize Flask app
app = Flask(__name__)
app.secret_key = 'your-secret-key-for-sessions' # Change this to a random secret key
app.secret_key = 'your-secret-key-for-sessions'
app.debug = False
# Language support
SUPPORTED_LANGUAGES = ['sl', 'en']
DEFAULT_LANGUAGE = 'sl'
def load_translations(language='sl'):
"""Load translations for the specified language"""
try:
locale_file = f'locales/{language}.json'
if os.path.exists(locale_file):
with open(locale_file, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
print(f"Error loading translations for {language}: {e}")
# Fallback to default language
try:
with open(f'locales/{DEFAULT_LANGUAGE}.json', 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
print(f"Error loading default translations: {e}")
return {}
def get_current_language():
"""Get current language from session or default"""
return session.get('language', DEFAULT_LANGUAGE)
def get_translations():
"""Get translations for current language"""
return load_translations(get_current_language())
def is_mobile_device():
"""Check if the request is coming from a mobile device"""
user_agent = request.headers.get('User-Agent', '').lower()
mobile_patterns = [
r'android', r'iphone', r'ipad', r'ipod', r'blackberry',
r'iemobile', r'opera mini', r'mobile', r'tablet'
]
return any(re.search(pattern, user_agent) for pattern in mobile_patterns)
def calculate_tens_from_targets(targets):
"""Calculate the number of 10s from a targets dictionary"""
tens_count = 0
if not targets:
return 0
for target in targets.values():
if isinstance(target, dict):
for shot_key, shot_value in target.items():
if shot_key.startswith('shot') and shot_value == 10:
tens_count += 1
return tens_count
# Define streams globally so both routes can access them
# Configuration: Camera Streams (6 targets)
STREAMS = [
{'name': 'Target1', 'url': 'http://192.168.0.134:9081'},
{'name': 'Target2', 'url': 'http://192.168.0.134:9082'},
@@ -73,455 +40,89 @@ STREAMS = [
{'name': 'Target6', 'url': 'http://192.168.0.134:9086'},
]
# Settings file paths
SETTINGS_FILE = 'camera_settings.json'
PLAYERS_FILE = 'players.json'
TOURNAMENT_FILE = 'tournament_state.json'
RESULTS_FILE = 'tournament_results.json'
LEAGUE_FILE = 'league_state.json'
ARCHIVE_DIR = 'tournament_archives'
LEAGUE_ARCHIVE_DIR = 'league_archives'
# Language Configuration
SUPPORTED_LANGUAGES = ['sl', 'en']
DEFAULT_LANGUAGE = 'sl'
# Default settings
DEFAULT_SETTINGS = {
'camera_titles': {
'1': 'Camera 1',
'2': 'Camera 2',
'3': 'Camera 3',
'4': 'Camera 4',
'5': 'Camera 5',
'6': 'Camera 6'
},
'display_options': {
'show_titles': True,
'title_size': 1.1
}
}
# Default players structure
DEFAULT_PLAYERS = {
'players': [
{'id': 1, 'name': 'Player 1', 'enabled': True},
{'id': 2, 'name': 'Player 2', 'enabled': True},
{'id': 3, 'name': 'Player 3', 'enabled': True},
{'id': 4, 'name': 'Player 4', 'enabled': True},
{'id': 5, 'name': 'Player 5', 'enabled': True},
{'id': 6, 'name': 'Player 6', 'enabled': True},
]
}
# Data file paths (organized in data directory)
ARCHIVE_DIR = 'data/tournament_archives'
LEAGUE_ARCHIVE_DIR = 'data/league_archives'
# Convenience function wrappers that delegate to storage classes
def load_settings():
"""Load settings from JSON file, create with defaults if not exists"""
try:
if os.path.exists(SETTINGS_FILE):
with open(SETTINGS_FILE, 'r') as f:
settings = json.load(f)
# Ensure all required keys exist (for backwards compatibility)
for key in DEFAULT_SETTINGS:
if key not in settings:
settings[key] = DEFAULT_SETTINGS[key]
for camera_id in DEFAULT_SETTINGS['camera_titles']:
if camera_id not in settings['camera_titles']:
settings['camera_titles'][camera_id] = DEFAULT_SETTINGS['camera_titles'][camera_id]
return settings
else:
return DEFAULT_SETTINGS.copy()
except (json.JSONDecodeError, IOError):
return DEFAULT_SETTINGS.copy()
return SettingsStorage.load_settings()
def save_settings(settings):
"""Save settings to JSON file"""
try:
with open(SETTINGS_FILE, 'w') as f:
json.dump(settings, f, indent=2)
return True
except IOError:
return False
return SettingsStorage.save_settings(settings)
def load_players():
"""Load players from JSON file, create with defaults if not exists"""
try:
if os.path.exists(PLAYERS_FILE):
with open(PLAYERS_FILE, 'r') as f:
return json.load(f)
else:
save_players(DEFAULT_PLAYERS)
return DEFAULT_PLAYERS.copy()
except (json.JSONDecodeError, IOError):
return DEFAULT_PLAYERS.copy()
return PlayerStorage.load_players()
def save_players(players_data):
"""Save players to JSON file"""
try:
with open(PLAYERS_FILE, 'w') as f:
json.dump(players_data, f, indent=2)
return True
except IOError:
return False
return PlayerStorage.save_players(players_data)
def load_tournament_state():
"""Load tournament state from JSON file"""
try:
if os.path.exists(TOURNAMENT_FILE):
with open(TOURNAMENT_FILE, 'r') as f:
return json.load(f)
else:
return None
except (json.JSONDecodeError, IOError):
return None
return TournamentStorage.load_tournament_state()
def save_tournament_state(tournament_data):
"""Save tournament state to JSON file"""
try:
with open(TOURNAMENT_FILE, 'w') as f:
json.dump(tournament_data, f, indent=2)
return True
except IOError:
return False
return TournamentStorage.save_tournament_state(tournament_data)
def load_league_state():
"""Load league state from JSON file"""
try:
if os.path.exists(LEAGUE_FILE):
with open(LEAGUE_FILE, 'r') as f:
return json.load(f)
else:
return None
except (json.JSONDecodeError, IOError):
return None
return LeagueStorage.load_league_state()
def save_league_state(league_data):
"""Save league state to JSON file"""
try:
with open(LEAGUE_FILE, 'w') as f:
json.dump(league_data, f, indent=2)
return True
except IOError:
return False
return LeagueStorage.save_league_state(league_data)
def load_results():
"""Load results from JSON file"""
try:
if os.path.exists(RESULTS_FILE):
with open(RESULTS_FILE, 'r') as f:
return json.load(f)
else:
return None
except (json.JSONDecodeError, IOError):
return None
return ResultsStorage.load_results()
def save_results(results_data):
"""Save results to JSON file"""
try:
with open(RESULTS_FILE, 'w') as f:
json.dump(results_data, f, indent=2)
return True
except IOError:
return False
return ResultsStorage.save_results(results_data)
def archive_tournament(tournament_data, results_data):
"""Archive completed tournament data"""
try:
# Create archive directory if it doesn't exist
if not os.path.exists(ARCHIVE_DIR):
os.makedirs(ARCHIVE_DIR)
# Create filename with timestamp
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
archive_filename = f"tournament_{timestamp}.json"
archive_path = os.path.join(ARCHIVE_DIR, archive_filename)
# Combine tournament and results data
archive_data = {
'tournament': tournament_data,
'results': results_data,
'archived_at': datetime.now().isoformat()
}
# Save to archive
with open(archive_path, 'w') as f:
json.dump(archive_data, f, indent=2)
print(f"Tournament archived to: {archive_path}")
return True
except Exception as e:
print(f"Error archiving tournament: {e}")
return False
return ArchiveStorage.archive_tournament(tournament_data, results_data)
def archive_league(league_data):
"""Archive completed league data"""
try:
if not os.path.exists(LEAGUE_ARCHIVE_DIR):
os.makedirs(LEAGUE_ARCHIVE_DIR)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
archive_filename = f"league_{timestamp}.json"
archive_path = os.path.join(LEAGUE_ARCHIVE_DIR, archive_filename)
archive_data = {
'league': league_data,
'archived_at': datetime.now().isoformat()
}
with open(archive_path, 'w') as f:
json.dump(archive_data, f, indent=2)
print(f"League archived to: {archive_path}")
return True
except Exception as e:
print(f"Error archiving league: {e}")
return False
return ArchiveStorage.archive_league(league_data)
# Convenience wrappers that delegate to modular classes
def create_league(enabled_players, tournament_type):
"""Create a new league with 5 tournaments (changed from 6)"""
league_id = f"league_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
league_data = {
'league_id': league_id,
'created_at': datetime.now().isoformat(),
'tournament_type': tournament_type,
'total_tournaments': 5, # Changed from 6 to 5
'current_tournament': 0, # Will be 1 when first tournament starts
'participants': {},
'completed_tournaments': [],
'league_finished': False
}
# Initialize participants
for player in enabled_players:
league_data['participants'][str(player['id'])] = {
'name': player['name'],
'joker_used': False,
'tournament_results': [],
'total_score': 0,
'final_score': 0, # Best 4 tournaments (changed from 5)
'tournaments_participated': 0
}
return league_data
return Tournament.create_league(enabled_players, tournament_type)
def create_draft(enabled_players, tournament_type='20_targets', league_tournament_number=None):
"""Create draft groups of 6 players and organize rounds"""
if len(enabled_players) < 1:
return None
# Shuffle players for random grouping
players_copy = enabled_players.copy()
random.shuffle(players_copy)
# Create groups of up to 6
groups = []
for i in range(0, len(players_copy), 6):
group = players_copy[i:i+6]
groups.append(group)
# Create rounds
rounds = []
for i, group in enumerate(groups):
rounds.append({
'round_number': i + 1,
'players': group,
'status': 'pending' if i == 0 else 'waiting'
})
tournament_data = {
'rounds': rounds,
'created_at': datetime.now().isoformat(),
'total_players': len(enabled_players),
'total_rounds': len(rounds),
'current_round': 1,
'tournament_type': tournament_type
}
if league_tournament_number:
tournament_data['league_tournament_number'] = league_tournament_number
return tournament_data
return Tournament.create_draft(enabled_players, tournament_type, league_tournament_number)
def create_results_structure(tournament_data):
"""Create results structure for a tournament"""
if not tournament_data:
return None
tournament_type = tournament_data.get('tournament_type', '20_targets')
# Determine target count and shots per target
if tournament_type == '40_targets':
num_targets = 40
shots_per_target = 2
elif tournament_type == '4_targets':
num_targets = 4
shots_per_target = 5
else: # 20_targets (default)
num_targets = 20
shots_per_target = 2
results = {
'tournament_id': tournament_data.get('created_at', datetime.now().isoformat()),
'tournament_type': tournament_type,
'participants': {},
'tournament_finished': False,
'created_at': datetime.now().isoformat()
}
# Add league info if present
if 'league_tournament_number' in tournament_data:
results['league_tournament_number'] = tournament_data['league_tournament_number']
# Create structure for each participant
all_players = []
for round_data in tournament_data['rounds']:
all_players.extend(round_data['players'])
for player in all_players:
player_id = str(player['id'])
# Create target structure based on tournament type
targets = {}
for i in range(1, num_targets + 1):
target = {}
for j in range(1, shots_per_target + 1):
target[f'shot{j}'] = None
targets[str(i)] = target
results['participants'][player_id] = {
'name': player['name'],
'targets': targets,
'total_score': 0,
'completed': False
}
return results
return Tournament.create_results_structure(tournament_data)
def calculate_total_score(targets):
"""Calculate total score from targets, treating None as 0"""
total = 0
for target in targets.values():
for shot_key, shot_value in target.items():
if shot_key.startswith('shot') and shot_value is not None:
total += shot_value
return total
return Scoring.calculate_total_score(targets)
def is_participant_completed(targets):
"""Check if a participant has completed all targets (all shots entered, including 0s)"""
for target in targets.values():
for shot_key, shot_value in target.items():
if shot_key.startswith('shot') and shot_value is None:
return False
return True
return Scoring.is_participant_completed(targets)
def calculate_league_final_scores(league_data):
"""Calculate final league scores using best 4 tournaments (changed from best 5)"""
for participant_id, participant in league_data['participants'].items():
tournament_scores = []
# Get all tournament scores where player participated
for result in participant['tournament_results']:
if result['participated']:
tournament_scores.append(result['score'])
# Sort scores descending and take best 4 (changed from 5)
tournament_scores.sort(reverse=True)
best_scores = tournament_scores[:4] if len(tournament_scores) > 4 else tournament_scores
participant['final_score'] = sum(best_scores)
participant['tournaments_participated'] = len(tournament_scores)
def get_league_final_rankings(league_data):
"""Get final league rankings sorted by final score"""
participants = []
for player_id, data in league_data['participants'].items():
# Calculate total 10s across all tournaments
total_tens = sum(result.get('tens_count', 0) for result in data['tournament_results'] if result.get('participated', False))
participants.append({
'id': player_id,
'name': data['name'],
'final_score': data['final_score'],
'total_score': data['total_score'],
'tournaments_participated': data['tournaments_participated'],
'joker_used': data['joker_used'],
'tournament_results': data['tournament_results'],
'total_tens': total_tens
})
# Sort by final score (best 5 tournaments) descending, then by total 10s for tiebreaking
participants.sort(key=lambda x: (x['final_score'], x['total_tens']), reverse=True)
# Add rankings
for i, participant in enumerate(participants):
participant['rank'] = i + 1
return participants
return Scoring.calculate_league_final_scores(league_data)
def get_current_round_data():
"""Get current round data from tournament"""
tournament_state = load_tournament_state()
if not tournament_state:
return None
current_round_num = tournament_state.get('current_round', 1)
# Find the current round
for round_data in tournament_state['rounds']:
if round_data['round_number'] == current_round_num:
return round_data
return None
def get_league_final_rankings(league_data):
return Scoring.get_league_final_rankings(league_data)
def calculate_current_league_standings(league_data):
"""Calculate current league standings during active league"""
participants = []
for player_id, data in league_data['participants'].items():
# Calculate current standings based on completed tournaments
tournament_scores = []
completed_tournaments = 0
total_tens = 0
for result in data['tournament_results']:
if result['participated']:
tournament_scores.append(result['score'])
completed_tournaments += 1
total_tens += result.get('tens_count', 0)
# Current score is sum of all completed tournaments
current_total = sum(tournament_scores)
# For display, show what the final score would be if we took best 5 now
tournament_scores.sort(reverse=True)
projected_final = sum(tournament_scores[:5]) if len(tournament_scores) >= 5 else sum(tournament_scores)
participants.append({
'id': player_id,
'name': data['name'],
'current_total': current_total,
'projected_final': projected_final,
'tournaments_completed': completed_tournaments,
'joker_used': data['joker_used'],
'tournament_results': data['tournament_results'],
'total_tens': total_tens
})
# Sort by current total score (descending), then by total 10s for tiebreaking
participants.sort(key=lambda x: (x['current_total'], x['total_tens']), reverse=True)
# Add rankings
for i, participant in enumerate(participants):
participant['rank'] = i + 1
return participants
return Scoring.calculate_current_league_standings(league_data)
def get_current_round_data():
tournament_state = load_tournament_state()
return RoundManager.get_current_round_data(tournament_state)
def get_league_current_standings(league_state):
"""Get current league standings including in-progress tournament"""
if not league_state:
return []
# Start with current league participants
participants = []
for player_id, data in league_state['participants'].items():
# Calculate total 10s from completed tournaments
total_tens = sum(result.get('tens_count', 0) for result in data['tournament_results'] if result.get('participated', False))
participant = {
'id': player_id,
'name': data['name'],
@@ -531,128 +132,36 @@ def get_league_current_standings(league_state):
'joker_used': data['joker_used'],
'tournament_results': data['tournament_results'],
'total_tens': total_tens,
'current_tournament_score': 0, # Score from current tournament
'current_tournament_score': 0,
'current_tournament_participating': False
}
participants.append(participant)
# Add current tournament scores if available
current_results = load_results()
if current_results and not current_results.get('tournament_finished', False):
for player_id, result_data in current_results['participants'].items():
# Find this participant in our list
for participant in participants:
if participant['id'] == player_id:
participant['current_tournament_score'] = result_data['total_score']
participant['current_tournament_participating'] = True
break
# Sort by final score (best 5 tournaments) descending, then by total 10s for tiebreaking
participants.sort(key=lambda x: (x['final_score'], x['total_tens']), reverse=True)
# Add rankings
for i, participant in enumerate(participants):
participant['rank'] = i + 1
return participants
# Add these functions after the existing helper functions in app.py
# Archive functions
def get_archived_tournaments():
"""Get list of standalone archived tournaments (excluding league tournaments)"""
try:
if not os.path.exists(ARCHIVE_DIR):
return []
archives = []
for file_path in glob.glob(os.path.join(ARCHIVE_DIR, "tournament_*.json")):
try:
with open(file_path, 'r') as f:
data = json.load(f)
# Extract metadata
filename = os.path.basename(file_path)
archived_at = data.get('archived_at', 'Unknown')
tournament_data = data.get('tournament', {})
results_data = data.get('results', {})
# Skip tournaments that are part of a league
if tournament_data.get('league_tournament_number') or results_data.get('league_tournament_number'):
continue
archive_info = {
'filename': filename,
'filepath': file_path,
'archived_at': archived_at,
'created_at': tournament_data.get('created_at', 'Unknown'),
'tournament_type': tournament_data.get('tournament_type', '20_targets'),
'total_players': tournament_data.get('total_players', 0),
'total_rounds': tournament_data.get('total_rounds', 0),
'tournament_finished': results_data.get('tournament_finished', False),
'participants_count': len(results_data.get('participants', {}))
}
archives.append(archive_info)
except (json.JSONDecodeError, IOError) as e:
print(f"Error reading archive {file_path}: {e}")
continue
# Sort by archived date (newest first)
archives.sort(key=lambda x: x['archived_at'], reverse=True)
return archives
except Exception as e:
print(f"Error getting archived tournaments: {e}")
return []
return ArchiveStorage.get_archived_tournaments()
def get_archived_leagues():
"""Get list of all archived leagues"""
try:
if not os.path.exists(LEAGUE_ARCHIVE_DIR):
return []
archives = []
for file_path in glob.glob(os.path.join(LEAGUE_ARCHIVE_DIR, "league_*.json")):
try:
with open(file_path, 'r') as f:
data = json.load(f)
filename = os.path.basename(file_path)
archived_at = data.get('archived_at', 'Unknown')
league_data = data.get('league', {})
archive_info = {
'filename': filename,
'filepath': file_path,
'archived_at': archived_at,
'created_at': league_data.get('created_at', 'Unknown'),
'league_id': league_data.get('league_id', 'Unknown'),
'tournament_type': league_data.get('tournament_type', '20_targets'),
'total_tournaments': league_data.get('total_tournaments', 6),
'participants_count': len(league_data.get('participants', {})),
'league_finished': league_data.get('league_finished', False),
'completed_tournaments': len(league_data.get('completed_tournaments', []))
}
archives.append(archive_info)
except (json.JSONDecodeError, IOError) as e:
print(f"Error reading league archive {file_path}: {e}")
continue
# Sort by archived date (newest first)
archives.sort(key=lambda x: x['archived_at'], reverse=True)
return archives
except Exception as e:
print(f"Error getting archived leagues: {e}")
return []
return ArchiveStorage.get_archived_leagues()
def load_archive_file(filepath):
"""Load a specific archive file"""
try:
with open(filepath, 'r') as f:
return json.load(f)
except (json.JSONDecodeError, IOError) as e:
print(f"Error loading archive file {filepath}: {e}")
return None
return ArchiveStorage.load_archive_file(filepath)
def analyze_player_performance(player_id, archives_data):
"""Analyze performance of a specific player across all archives"""
@@ -819,9 +328,9 @@ def get_all_players_from_archives():
# Add these routes after the existing routes in app.py
# Add this to your app.py file to integrate the modern archive system
# ============================================================================
# Flask Routes
# ============================================================================
# Replace your existing archive routes with these updated ones:
@@ -1190,7 +699,9 @@ def index():
if i < len(players):
tournament_titles[str(i + 1)] = players[i]['name']
else:
tournament_titles[str(i + 1)] = 'Empty'
# Use "Prazno" for Slovenian, "Empty" for English
current_language = get_current_language()
tournament_titles[str(i + 1)] = 'Prazno' if current_language == 'sl' else 'Empty'
# Create a copy of settings with tournament titles
display_settings = settings.copy()
@@ -1572,13 +1083,13 @@ def update_settings():
if 'display_options' in new_settings:
current_settings['display_options'].update(new_settings['display_options'])
# Save updated settings
if save_settings(current_settings):
return jsonify({'status': 'success', 'settings': current_settings})
else:
return jsonify({'status': 'error', 'message': 'Failed to save settings'}), 500
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 400
@@ -1593,12 +1104,12 @@ def update_players():
"""API endpoint to update players"""
try:
players_data = request.get_json()
if save_players(players_data):
return jsonify({'status': 'success', 'players': players_data})
else:
return jsonify({'status': 'error', 'message': 'Failed to save players'}), 500
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 400
@@ -1626,12 +1137,12 @@ def add_player():
}
players_data['players'].append(new_player)
if save_players(players_data):
return jsonify({'status': 'success', 'player': new_player})
else:
return jsonify({'status': 'error', 'message': 'Failed to save player'}), 500
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 400
@@ -1640,15 +1151,15 @@ def delete_player(player_id):
"""API endpoint to delete a player"""
try:
players_data = load_players()
# Find and remove the player
players_data['players'] = [p for p in players_data['players'] if p['id'] != player_id]
if save_players(players_data):
return jsonify({'status': 'success'})
else:
return jsonify({'status': 'error', 'message': 'Failed to save players'}), 500
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 400
@@ -1667,7 +1178,7 @@ def update_player(player_id):
if 'enabled' in data:
player['enabled'] = data['enabled']
break
if save_players(players_data):
return jsonify({'status': 'success', 'player': player})
else:
@@ -1804,12 +1315,12 @@ def start_tournament():
return jsonify({'status': 'error', 'message': 'Need at least 1 enabled player'}), 400
tournament_data = create_draft(enabled_players, tournament_type)
if save_tournament_state(tournament_data):
# Create results structure when tournament starts
results = create_results_structure(tournament_data)
save_results(results)
return jsonify({'status': 'success', 'tournament': tournament_data})
else:
return jsonify({'status': 'error', 'message': 'Failed to save tournament'}), 500
@@ -1821,15 +1332,19 @@ def start_tournament():
def change_round(round_number):
"""API endpoint to change current round"""
try:
print(f"[ROUND CHANGE] Attempting to change to round {round_number}")
tournament_state = load_tournament_state()
if not tournament_state:
print(f"[ROUND CHANGE] Error: No active tournament")
return jsonify({'status': 'error', 'message': 'No active tournament'}), 400
print(f"[ROUND CHANGE] Tournament found. Total rounds: {tournament_state.get('total_rounds')}")
if round_number < 1 or round_number > tournament_state['total_rounds']:
print(f"[ROUND CHANGE] Error: Invalid round number {round_number}")
return jsonify({'status': 'error', 'message': 'Invalid round number'}), 400
tournament_state['current_round'] = round_number
# Update round statuses
for round_data in tournament_state['rounds']:
if round_data['round_number'] < round_number:
@@ -1838,13 +1353,20 @@ def change_round(round_number):
round_data['status'] = 'pending'
else:
round_data['status'] = 'waiting'
print(f"[ROUND CHANGE] Saving tournament state...")
if save_tournament_state(tournament_state):
print(f"[ROUND CHANGE] Tournament saved successfully")
return jsonify({'status': 'success', 'tournament': tournament_state})
else:
print(f"[ROUND CHANGE] Error: Failed to save tournament")
return jsonify({'status': 'error', 'message': 'Failed to save tournament'}), 500
except Exception as e:
print(f"[ROUND CHANGE] Exception: {str(e)}")
import traceback
traceback.print_exc()
return jsonify({'status': 'error', 'message': str(e)}), 400
@app.route('/api/tournament/reset', methods=['POST'])
@@ -1886,12 +1408,12 @@ def update_participant_scores(player_id):
# Check if all targets are completed (all shots entered, including 0s)
all_completed = is_participant_completed(targets)
results['participants'][player_id_str]['completed'] = all_completed
if save_results(results):
return jsonify({'status': 'success', 'participant': results['participants'][player_id_str]})
else:
return jsonify({'status': 'error', 'message': 'Failed to save results'}), 500
except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 400
@@ -2023,24 +1545,24 @@ def finish_tournament():
# End the tournament by removing tournament state
if os.path.exists(TOURNAMENT_FILE):
os.remove(TOURNAMENT_FILE)
# If league finished, also remove league state file
if league_finished and os.path.exists(LEAGUE_FILE):
os.remove(LEAGUE_FILE)
response_data = {
'status': 'success',
'status': 'success',
'results': results,
'archived': archive_success,
'league_archived': league_archive_success if league_finished else None,
'tournament_type': results.get('tournament_type', '20_targets'),
'tournament_format': get_tournament_format_description(results.get('tournament_type', '20_targets'))
}
if league_state:
response_data['league'] = league_state
response_data['league_finished'] = league_state.get('league_finished', False)
return jsonify(response_data)
else:
return jsonify({'status': 'error', 'message': 'Failed to save results'}), 500