add accountGroupStageBreakTime groupStageRotationDifference in matchscheduler and uniqueRandomIndex in team reg

newoffer2025
Raz 6 months ago
parent e554999d8a
commit a865efc696
  1. 16
      PadelClubData/Data/Gen/BaseMatchScheduler.swift
  2. 23
      PadelClubData/Data/Gen/BaseRound.swift
  3. 9
      PadelClubData/Data/Gen/BaseTeamRegistration.swift
  4. 10
      PadelClubData/Data/Gen/MatchScheduler.json
  5. 6
      PadelClubData/Data/Gen/TeamRegistration.json
  6. 72
      PadelClubData/Data/MatchScheduler.swift
  7. 89
      PadelClubData/Data/Tournament.swift

@ -28,6 +28,8 @@ public class BaseMatchScheduler: BaseModelObject, Storable {
public var shouldTryToFillUpCourtsAvailable: Bool = false
public var courtsAvailable: Set<Int> = Set<Int>()
public var simultaneousStart: Bool = true
public var groupStageRotationDifference: Int = 0
public var accountGroupStageBreakTime: Bool = false
public init(
id: String = Store.randomId(),
@ -45,7 +47,9 @@ public class BaseMatchScheduler: BaseModelObject, Storable {
overrideCourtsUnavailability: Bool = false,
shouldTryToFillUpCourtsAvailable: Bool = false,
courtsAvailable: Set<Int> = Set<Int>(),
simultaneousStart: Bool = true
simultaneousStart: Bool = true,
groupStageRotationDifference: Int = 0,
accountGroupStageBreakTime: Bool = false
) {
super.init()
self.id = id
@ -64,6 +68,8 @@ public class BaseMatchScheduler: BaseModelObject, Storable {
self.shouldTryToFillUpCourtsAvailable = shouldTryToFillUpCourtsAvailable
self.courtsAvailable = courtsAvailable
self.simultaneousStart = simultaneousStart
self.groupStageRotationDifference = groupStageRotationDifference
self.accountGroupStageBreakTime = accountGroupStageBreakTime
}
required public override init() {
super.init()
@ -86,6 +92,8 @@ public class BaseMatchScheduler: BaseModelObject, Storable {
case _shouldTryToFillUpCourtsAvailable = "shouldTryToFillUpCourtsAvailable"
case _courtsAvailable = "courtsAvailable"
case _simultaneousStart = "simultaneousStart"
case _groupStageRotationDifference = "groupStageRotationDifference"
case _accountGroupStageBreakTime = "accountGroupStageBreakTime"
}
required init(from decoder: Decoder) throws {
@ -106,6 +114,8 @@ public class BaseMatchScheduler: BaseModelObject, Storable {
self.shouldTryToFillUpCourtsAvailable = try container.decodeIfPresent(Bool.self, forKey: ._shouldTryToFillUpCourtsAvailable) ?? false
self.courtsAvailable = try container.decodeIfPresent(Set<Int>.self, forKey: ._courtsAvailable) ?? Set<Int>()
self.simultaneousStart = try container.decodeIfPresent(Bool.self, forKey: ._simultaneousStart) ?? true
self.groupStageRotationDifference = try container.decodeIfPresent(Int.self, forKey: ._groupStageRotationDifference) ?? 0
self.accountGroupStageBreakTime = try container.decodeIfPresent(Bool.self, forKey: ._accountGroupStageBreakTime) ?? false
try super.init(from: decoder)
}
@ -127,6 +137,8 @@ public class BaseMatchScheduler: BaseModelObject, Storable {
try container.encode(self.shouldTryToFillUpCourtsAvailable, forKey: ._shouldTryToFillUpCourtsAvailable)
try container.encode(self.courtsAvailable, forKey: ._courtsAvailable)
try container.encode(self.simultaneousStart, forKey: ._simultaneousStart)
try container.encode(self.groupStageRotationDifference, forKey: ._groupStageRotationDifference)
try container.encode(self.accountGroupStageBreakTime, forKey: ._accountGroupStageBreakTime)
try super.encode(to: encoder)
}
@ -152,6 +164,8 @@ public class BaseMatchScheduler: BaseModelObject, Storable {
self.shouldTryToFillUpCourtsAvailable = matchscheduler.shouldTryToFillUpCourtsAvailable
self.courtsAvailable = matchscheduler.courtsAvailable
self.simultaneousStart = matchscheduler.simultaneousStart
self.groupStageRotationDifference = matchscheduler.groupStageRotationDifference
self.accountGroupStageBreakTime = matchscheduler.accountGroupStageBreakTime
}
public static func relationships() -> [Relationship] {

@ -7,11 +7,11 @@ import SwiftUI
@Observable
public class BaseRound: SyncedModelObject, SyncedStorable {
public static func resourceName() -> String { return "rounds" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
public static var copyServerResponse: Bool = false
public var id: String = Store.randomId()
public var tournament: String = ""
public var index: Int = 0
@ -19,13 +19,13 @@ public class BaseRound: SyncedModelObject, SyncedStorable {
public var format: MatchFormat? = nil
public var startDate: Date? = nil {
didSet {
self.didSetStartDate()
self.didSetStartDate()
}
}
public var groupStageLoserBracket: Bool = false
public var loserBracketMode: LoserBracketMode = .automatic
public var plannedStartDate: Date? = nil
public init(
id: String = Store.randomId(),
tournament: String = "",
@ -51,9 +51,9 @@ public class BaseRound: SyncedModelObject, SyncedStorable {
required public override init() {
super.init()
}
public func didSetStartDate() {}
public enum CodingKeys: String, CodingKey {
case _id = "id"
case _tournament = "tournament"
@ -65,7 +65,7 @@ public class BaseRound: SyncedModelObject, SyncedStorable {
case _loserBracketMode = "loserBracketMode"
case _plannedStartDate = "plannedStartDate"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(String.self, forKey: ._id) ?? Store.randomId()
@ -79,7 +79,7 @@ public class BaseRound: SyncedModelObject, SyncedStorable {
self.plannedStartDate = try container.decodeIfPresent(Date.self, forKey: ._plannedStartDate) ?? nil
try super.init(from: decoder)
}
public override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: ._id)
@ -93,11 +93,11 @@ public class BaseRound: SyncedModelObject, SyncedStorable {
try container.encode(self.plannedStartDate, forKey: ._plannedStartDate)
try super.encode(to: encoder)
}
func tournamentValue() -> Tournament? {
return Store.main.findById(tournament)
}
public func copy(from other: any Storable) {
guard let round = other as? BaseRound else { return }
self.id = round.id
@ -110,11 +110,10 @@ public class BaseRound: SyncedModelObject, SyncedStorable {
self.loserBracketMode = round.loserBracketMode
self.plannedStartDate = round.plannedStartDate
}
public static func relationships() -> [Relationship] {
return [
Relationship(type: Tournament.self, keyPath: \BaseRound.tournament),
]
}
}

@ -33,6 +33,7 @@ public class BaseTeamRegistration: SyncedModelObject, SyncedStorable {
public var qualified: Bool = false
public var finalRanking: Int? = nil
public var pointsEarned: Int? = nil
public var uniqueRandomIndex: Int = 0
public init(
id: String = Store.randomId(),
@ -55,7 +56,8 @@ public class BaseTeamRegistration: SyncedModelObject, SyncedStorable {
confirmationDate: Date? = nil,
qualified: Bool = false,
finalRanking: Int? = nil,
pointsEarned: Int? = nil
pointsEarned: Int? = nil,
uniqueRandomIndex: Int = 0
) {
super.init()
self.id = id
@ -79,6 +81,7 @@ public class BaseTeamRegistration: SyncedModelObject, SyncedStorable {
self.qualified = qualified
self.finalRanking = finalRanking
self.pointsEarned = pointsEarned
self.uniqueRandomIndex = uniqueRandomIndex
}
required public override init() {
super.init()
@ -106,6 +109,7 @@ public class BaseTeamRegistration: SyncedModelObject, SyncedStorable {
case _qualified = "qualified"
case _finalRanking = "finalRanking"
case _pointsEarned = "pointsEarned"
case _uniqueRandomIndex = "uniqueRandomIndex"
}
required init(from decoder: Decoder) throws {
@ -131,6 +135,7 @@ public class BaseTeamRegistration: SyncedModelObject, SyncedStorable {
self.qualified = try container.decodeIfPresent(Bool.self, forKey: ._qualified) ?? false
self.finalRanking = try container.decodeIfPresent(Int.self, forKey: ._finalRanking) ?? nil
self.pointsEarned = try container.decodeIfPresent(Int.self, forKey: ._pointsEarned) ?? nil
self.uniqueRandomIndex = try container.decodeIfPresent(Int.self, forKey: ._uniqueRandomIndex) ?? 0
try super.init(from: decoder)
}
@ -157,6 +162,7 @@ public class BaseTeamRegistration: SyncedModelObject, SyncedStorable {
try container.encode(self.qualified, forKey: ._qualified)
try container.encode(self.finalRanking, forKey: ._finalRanking)
try container.encode(self.pointsEarned, forKey: ._pointsEarned)
try container.encode(self.uniqueRandomIndex, forKey: ._uniqueRandomIndex)
try super.encode(to: encoder)
}
@ -188,6 +194,7 @@ public class BaseTeamRegistration: SyncedModelObject, SyncedStorable {
self.qualified = teamregistration.qualified
self.finalRanking = teamregistration.finalRanking
self.pointsEarned = teamregistration.pointsEarned
self.uniqueRandomIndex = teamregistration.uniqueRandomIndex
}
public static func relationships() -> [Relationship] {

@ -84,6 +84,16 @@
"name": "simultaneousStart",
"type": "Bool",
"defaultValue": "true"
},
{
"name": "groupStageRotationDifference",
"type": "Int",
"defaultValue": "0"
},
{
"name": "accountGroupStageBreakTime",
"type": "Bool",
"defaultValue": "false"
}
]
}

@ -111,6 +111,12 @@
"name": "pointsEarned",
"type": "Int",
"optional": true
},
{
"name": "uniqueRandomIndex",
"type": "Int",
"defaultValue": "0",
"optional": false
}
]
}

@ -105,8 +105,15 @@ final public class MatchScheduler: BaseMatchScheduler, SideStorable {
dispatch.timedMatches.forEach { matchSchedule in
if let match = matches.first(where: { $0.id == matchSchedule.matchID }) {
let estimatedDuration = match.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)
let timeIntervalToAdd = (Double(matchSchedule.rotationIndex)) * Double(estimatedDuration) * 60
var estimatedDuration = match.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)
if accountGroupStageBreakTime {
estimatedDuration += match.matchFormat.breakTime.breakTime * 60
}
var timeIntervalToAdd = (Double(matchSchedule.rotationIndex)) * Double(estimatedDuration) * 60
timeIntervalToAdd += Double(matchSchedule.rotationIndex) * Double(self.groupStageRotationDifference) * Double(match.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)) * 60
if let startDate = match.groupStageObject?.startDate {
let matchStartDate = startDate.addingTimeInterval(timeIntervalToAdd)
match.startDate = matchStartDate
@ -129,8 +136,16 @@ final public class MatchScheduler: BaseMatchScheduler, SideStorable {
dispatch.timedMatches.forEach { matchSchedule in
if let match = matches.first(where: { $0.id == matchSchedule.matchID }) {
let estimatedDuration = match.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)
let timeIntervalToAdd = (Double(matchSchedule.rotationIndex)) * Double(estimatedDuration) * 60
var estimatedDuration = match.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)
if accountGroupStageBreakTime {
estimatedDuration += match.matchFormat.breakTime.breakTime * 60
}
var timeIntervalToAdd = (Double(matchSchedule.rotationIndex)) * Double(estimatedDuration) * 60
timeIntervalToAdd += Double(matchSchedule.rotationIndex) * Double(self.groupStageRotationDifference) * Double(match.matchFormat.getEstimatedDuration(tournament.additionalEstimationDuration)) * 60
if let startDate = match.groupStageObject?.startDate {
let matchStartDate = startDate.addingTimeInterval(timeIntervalToAdd)
match.startDate = matchStartDate
@ -207,8 +222,16 @@ final public class MatchScheduler: BaseMatchScheduler, SideStorable {
courtsAvailable.forEach { courtIndex in
print("Checking availability for court \(courtIndex) in rotation \(rotationIndex)")
if let first = rotationMatches.first(where: { match in
let estimatedDuration = match.matchFormat.getEstimatedDuration(additionalEstimationDuration)
let timeIntervalToAdd = Double(rotationIndex) * Double(estimatedDuration) * 60
var estimatedDuration = match.matchFormat.getEstimatedDuration(additionalEstimationDuration)
if accountGroupStageBreakTime {
estimatedDuration += match.matchFormat.breakTime.breakTime * 60
}
var timeIntervalToAdd = (Double(rotationIndex)) * Double(estimatedDuration) * 60
timeIntervalToAdd += Double(rotationIndex) * Double(self.groupStageRotationDifference) * Double(match.matchFormat.getEstimatedDuration(additionalEstimationDuration)) * 60
let rotationStartDate: Date = startingDate.addingTimeInterval(timeIntervalToAdd)
let courtsUnavailable = courtsUnavailable(startDate: rotationStartDate, duration: match.matchFormat.getEstimatedDuration(additionalEstimationDuration), courtsUnavailability: courtsUnavailability)
@ -822,7 +845,31 @@ final public class MatchScheduler: BaseMatchScheduler, SideStorable {
if tournament.groupStages(atStep: 1).isEmpty == false {
lastDate = updateGroupStageSchedule(tournament: tournament, atStep: 1, startDate: lastDate)
}
return updateBracketSchedule(tournament: tournament, fromRoundId: nil, fromMatchId: nil, startDate: lastDate)
let allMatches = tournament.groupStagesMatches().filter({ $0.hasEnded() == false && $0.hasStarted() == false })
let groupMatchesByDay = tournament.groupMatchesByDay(matches: allMatches)
let countedSet = tournament.matchCountPerDay(matchesByDay: groupMatchesByDay)
var bracketStartDate = lastDate
print("lastDate", lastDate)
var errorFormat = false
let dates = countedSet.keys
if let first = dates.first, let matchesInLastDate = countedSet[first] {
let totalMatches = matchesInLastDate.totalCount()
let count = matchesInLastDate.count(for: tournament.groupStageMatchFormat)
let totalForThisFormat = tournament.groupStageMatchFormat.maximumMatchPerDay(for: totalMatches)
print(totalMatches, count, totalForThisFormat)
errorFormat = count >= totalForThisFormat
print("bracketStartDate", bracketStartDate)
}
if tournament.dayDuration > 1 && (lastDate.timeOfDay == .evening || errorFormat) {
bracketStartDate = lastDate.tomorrowAtNine
}
return updateBracketSchedule(tournament: tournament, fromRoundId: nil, fromMatchId: nil, startDate: bracketStartDate)
}
}
@ -886,3 +933,14 @@ extension Match {
return groupStageObject._matchUp(for: index).map { groupStageObject.id + "_\($0)" }
}
}
// Extension to compute the total count in an NSCountedSet
extension NSCountedSet {
func totalCount() -> Int {
var total = 0
for element in self {
total += self.count(for: element)
}
return total
}
}

@ -636,7 +636,7 @@ defer {
let defaultSorting : [MySortDescriptor<TeamRegistration>] = _defaultSorting()
let _completeTeams = _teams.sorted(using: defaultSorting, order: .ascending).filter { $0.isWildCard() == false }.prefix(teamCount).sorted(using: [.keyPath(\.initialWeight), .keyPath(\.id)], order: .ascending)
let _completeTeams = _teams.sorted(using: defaultSorting, order: .ascending).filter { $0.isWildCard() == false }.prefix(teamCount).sorted(using: [.keyPath(\.initialWeight), .keyPath(\.uniqueRandomIndex), .keyPath(\.id)], order: .ascending)
let wcGroupStage = _teams.filter { $0.wildCardGroupStage }.sorted(using: _currentSelectionSorting, order: .ascending)
@ -1777,9 +1777,9 @@ defer {
private func _defaultSorting() -> [MySortDescriptor<TeamRegistration>] {
switch teamSorting {
case .rank:
[.keyPath(\TeamRegistration.initialWeight), .keyPath(\TeamRegistration.id)]
[.keyPath(\TeamRegistration.initialWeight), .keyPath(\TeamRegistration.uniqueRandomIndex), .keyPath(\TeamRegistration.id)]
case .inscriptionDate:
[.keyPath(\TeamRegistration.registrationDate!), .keyPath(\TeamRegistration.initialWeight), .keyPath(\TeamRegistration.id)]
[.keyPath(\TeamRegistration.registrationDate!), .keyPath(\TeamRegistration.initialWeight), .keyPath(\TeamRegistration.uniqueRandomIndex), .keyPath(\TeamRegistration.id)]
}
}
@ -1789,7 +1789,7 @@ defer {
&& federalTournamentAge == build.age
}
private let _currentSelectionSorting : [MySortDescriptor<TeamRegistration>] = [.keyPath(\.weight), .keyPath(\.id)]
private let _currentSelectionSorting : [MySortDescriptor<TeamRegistration>] = [.keyPath(\.weight), .keyPath(\.uniqueRandomIndex), .keyPath(\.id)]
private func _matchSchedulers() -> [MatchScheduler] {
guard let tournamentStore = self.tournamentStore else { return [] }
@ -2227,6 +2227,87 @@ defer {
return Array(leftInterval...maxSize)
}
public func groupMatchesByDay(matches: [Match]) -> [Date: [Match]] {
var matchesByDay = [Date: [Match]]()
let calendar = Calendar.current
for match in matches {
// Extract day/month/year and create a date with only these components
let components = calendar.dateComponents([.year, .month, .day], from: match.computedStartDateForSorting)
let strippedDate = calendar.date(from: components)!
// Group matches by the strippedDate (only day/month/year)
if matchesByDay[strippedDate] == nil {
matchesByDay[strippedDate] = []
}
let shouldIncludeMatch: Bool
switch match.matchType {
case .groupStage:
shouldIncludeMatch = !matchesByDay[strippedDate]!.filter { $0.groupStage != nil }.compactMap { $0.groupStage }.contains(match.groupStage!)
case .bracket:
shouldIncludeMatch = !matchesByDay[strippedDate]!.filter { $0.round != nil }.compactMap { $0.round }.contains(match.round!)
case .loserBracket:
shouldIncludeMatch = true
}
if shouldIncludeMatch {
matchesByDay[strippedDate]!.append(match)
}
}
return matchesByDay
}
public func matchCountPerDay(matchesByDay: [Date: [Match]]) -> [Date: NSCountedSet] {
let days = matchesByDay.keys
var matchCountPerDay = [Date: NSCountedSet]()
for day in days {
if let matches = matchesByDay[day] {
var groupStageCount = 0
let countedSet = NSCountedSet()
for match in matches {
switch match.matchType {
case .groupStage:
if let groupStage = match.groupStageObject {
if groupStageCount < groupStage.size - 1 {
groupStageCount = groupStage.size - 1
}
}
case .bracket:
countedSet.add(match.matchFormat)
case .loserBracket:
break
}
}
if groupStageCount > 0 {
for _ in 0..<groupStageCount {
countedSet.add(groupStageMatchFormat)
}
}
if let loserRounds = matches.filter({ $0.round != nil }).filter({ $0.roundObject?.parent == nil }).sorted(by: \.computedStartDateForSorting).last?.roundObject?.loserRounds() {
let ids = matches.map { $0.id }
for loserRound in loserRounds {
if let first = loserRound.playedMatches().first {
if ids.contains(first.id) {
countedSet.add(first.matchFormat)
}
}
}
}
matchCountPerDay[day] = countedSet
}
}
return matchCountPerDay
}
// MARK: -
func insertOnServer() throws {

Loading…
Cancel
Save