Added ability to calculate election result
This commit is contained in:
parent
da22c792da
commit
a8d4d47f70
|
@ -0,0 +1,58 @@
|
|||
package de.twomartens.wahlrecht.controller.v1;
|
||||
|
||||
import de.twomartens.wahlrecht.mapper.v1.ElectedCandidatesMapper;
|
||||
import de.twomartens.wahlrecht.mapper.v1.ElectionResultMapper;
|
||||
import de.twomartens.wahlrecht.model.dto.v1.ElectedCandidates;
|
||||
import de.twomartens.wahlrecht.model.dto.v1.ElectionResult;
|
||||
import de.twomartens.wahlrecht.service.CalculationService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping(value = "/wahlrecht/v1")
|
||||
@Tag(name = "Calculations", description = "all requests relating to calculations")
|
||||
public class CalculationController {
|
||||
|
||||
private final ElectedCandidatesMapper electedCandidatesMapper = Mappers.getMapper(
|
||||
ElectedCandidatesMapper.class);
|
||||
private final ElectionResultMapper electionResultMapper = Mappers.getMapper(
|
||||
ElectionResultMapper.class);
|
||||
private final CalculationService service;
|
||||
|
||||
@Operation(
|
||||
summary = "Calculates a provided election result",
|
||||
description = "This request does not store any result and is idempotent.",
|
||||
responses = {
|
||||
@ApiResponse(responseCode = "200",
|
||||
description = "Returns all elected candidates",
|
||||
content = {@Content(
|
||||
mediaType = "application/json",
|
||||
schema = @Schema(implementation = ElectedCandidates.class))}
|
||||
)
|
||||
}
|
||||
)
|
||||
@PostMapping(value = "/calculate", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ResponseEntity<ElectedCandidates> calculateResult(
|
||||
@RequestBody ElectionResult electionResult
|
||||
) {
|
||||
ElectedCandidates result = electedCandidatesMapper.mapToExternal(
|
||||
service.determineElectedCandidates(electionResultMapper.mapToInternal(electionResult)));
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(result);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package de.twomartens.wahlrecht.mapper.v1;
|
||||
|
||||
import de.twomartens.wahlrecht.model.internal.ElectedCandidate;
|
||||
import de.twomartens.wahlrecht.model.internal.ElectedCandidates;
|
||||
import de.twomartens.wahlrecht.model.internal.VotingResult;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import org.mapstruct.CollectionMappingStrategy;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.NullValueCheckStrategy;
|
||||
import org.mapstruct.ReportingPolicy;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED,
|
||||
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS,
|
||||
unmappedTargetPolicy = ReportingPolicy.IGNORE)
|
||||
public interface ElectedCandidatesMapper {
|
||||
|
||||
ElectedResultMapper ELECTED_RESULT_MAPPER = Mappers.getMapper(ElectedResultMapper.class);
|
||||
|
||||
|
||||
de.twomartens.wahlrecht.model.dto.v1.ElectedCandidates mapToExternal(ElectedCandidates candidate);
|
||||
|
||||
default Map<String, Collection<de.twomartens.wahlrecht.model.dto.v1.ElectedCandidate>> mapToExternal(
|
||||
Map<VotingResult, Collection<ElectedCandidate>> value) {
|
||||
return ELECTED_RESULT_MAPPER.mapToExternal(value);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package de.twomartens.wahlrecht.mapper.v1;
|
||||
|
||||
import de.twomartens.wahlrecht.model.dto.v1.ElectedCandidate;
|
||||
import de.twomartens.wahlrecht.model.dto.v1.NominationId;
|
||||
import de.twomartens.wahlrecht.model.internal.ElectedResult;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
@ -11,6 +10,7 @@ import org.mapstruct.CollectionMappingStrategy;
|
|||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.NullValueCheckStrategy;
|
||||
import org.mapstruct.ReportingPolicy;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED,
|
||||
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS,
|
||||
|
@ -19,16 +19,21 @@ public interface ElectedResultMapper {
|
|||
|
||||
de.twomartens.wahlrecht.model.dto.v1.ElectedResult mapToExternal(ElectedResult result);
|
||||
|
||||
Map<NominationId, Collection<ElectedCandidate>> mapToExternal(
|
||||
Map<de.twomartens.wahlrecht.model.internal.NominationId,
|
||||
Collection<de.twomartens.wahlrecht.model.internal.ElectedCandidate>> value);
|
||||
default Map<String, Collection<ElectedCandidate>> mapToExternal(
|
||||
@NonNull Map<de.twomartens.wahlrecht.model.internal.VotingResult,
|
||||
Collection<de.twomartens.wahlrecht.model.internal.ElectedCandidate>> value) {
|
||||
return value.entrySet().stream()
|
||||
.collect(Collectors.toMap(
|
||||
entry -> entry.getKey().getNominationId().partyAbbreviation(),
|
||||
entry -> mapToExternal(entry.getValue())));
|
||||
}
|
||||
|
||||
Collection<ElectedCandidate> mapToExternal(
|
||||
Collection<de.twomartens.wahlrecht.model.internal.ElectedCandidate> value);
|
||||
|
||||
default Map<de.twomartens.wahlrecht.model.internal.NominationId,
|
||||
Collection<de.twomartens.wahlrecht.model.internal.ElectedCandidate>> map(
|
||||
Map<de.twomartens.wahlrecht.model.internal.VotingResult,
|
||||
@NonNull Map<de.twomartens.wahlrecht.model.internal.VotingResult,
|
||||
Collection<de.twomartens.wahlrecht.model.internal.ElectedCandidate>> value) {
|
||||
return value.entrySet().stream()
|
||||
.collect(Collectors.toMap(entry -> entry.getKey().getNominationId(), Entry::getValue));
|
||||
|
|
|
@ -9,13 +9,17 @@ import org.mapstruct.Mapper;
|
|||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.NullValueCheckStrategy;
|
||||
import org.mapstruct.ReportingPolicy;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED,
|
||||
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS,
|
||||
unmappedTargetPolicy = ReportingPolicy.IGNORE)
|
||||
public interface ElectionResultMapper {
|
||||
|
||||
de.twomartens.wahlrecht.model.dto.v1.ElectionResult mapToExternal(de.twomartens.wahlrecht.model.db.ElectionResult result);
|
||||
VotingResultMapper VOTING_RESULT_MAPPER = Mappers.getMapper(VotingResultMapper.class);
|
||||
|
||||
de.twomartens.wahlrecht.model.dto.v1.ElectionResult mapToExternal(
|
||||
de.twomartens.wahlrecht.model.db.ElectionResult result);
|
||||
|
||||
Collection<de.twomartens.wahlrecht.model.dto.v1.VotingResult> mapToExternal(
|
||||
Collection<de.twomartens.wahlrecht.model.db.VotingResult> results);
|
||||
|
@ -36,6 +40,10 @@ public interface ElectionResultMapper {
|
|||
|
||||
ElectionResult mapToInternal(de.twomartens.wahlrecht.model.dto.v1.ElectionResult result);
|
||||
|
||||
default VotingResult mapToInternal(de.twomartens.wahlrecht.model.dto.v1.VotingResult result) {
|
||||
return VOTING_RESULT_MAPPER.mapToInternal(result);
|
||||
}
|
||||
|
||||
Collection<VotingResult> mapToInternal(
|
||||
Collection<de.twomartens.wahlrecht.model.dto.v1.VotingResult> results);
|
||||
|
||||
|
|
|
@ -6,13 +6,14 @@ import org.mapstruct.CollectionMappingStrategy;
|
|||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.NullValueCheckStrategy;
|
||||
import org.mapstruct.ReportingPolicy;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED,
|
||||
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS,
|
||||
unmappedTargetPolicy = ReportingPolicy.IGNORE)
|
||||
public interface VotingResultMapper {
|
||||
|
||||
default de.twomartens.wahlrecht.model.dto.v1.VotingResult mapToExternal(VotingResult result) {
|
||||
default de.twomartens.wahlrecht.model.dto.v1.VotingResult mapToExternal(@NonNull VotingResult result) {
|
||||
return new de.twomartens.wahlrecht.model.dto.v1.VotingResult(
|
||||
result.getNominationId().electionName(),
|
||||
result.getNominationId().partyAbbreviation(),
|
||||
|
@ -23,7 +24,7 @@ public interface VotingResultMapper {
|
|||
);
|
||||
}
|
||||
|
||||
default VotingResult mapToInternal(de.twomartens.wahlrecht.model.dto.v1.VotingResult result) {
|
||||
default VotingResult mapToInternal(@NonNull de.twomartens.wahlrecht.model.dto.v1.VotingResult result) {
|
||||
return new VotingResult(
|
||||
new NominationId(result.electionName(), result.partyAbbreviation(),
|
||||
result.nominationName()),
|
||||
|
|
|
@ -1,12 +1,4 @@
|
|||
package de.twomartens.wahlrecht.model.dto.v1;
|
||||
|
||||
public record ElectedCandidate(Candidate candidate, Elected elected) {
|
||||
|
||||
public String name() {
|
||||
return candidate.name();
|
||||
}
|
||||
|
||||
public String profession() {
|
||||
return candidate.profession();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package de.twomartens.wahlrecht.model.dto.v1;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
|
||||
public record ElectedCandidates(ElectedResult overallResult,
|
||||
Map<Integer, ElectedResult> constituencyResults,
|
||||
LinkedList<Double> electionNumbersForSeatAllocation) {
|
||||
|
||||
}
|
|
@ -1,13 +1,12 @@
|
|||
package de.twomartens.wahlrecht.model.dto.v1;
|
||||
|
||||
import de.twomartens.wahlrecht.model.internal.NominationId;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import lombok.Builder;
|
||||
|
||||
@Builder
|
||||
public record ElectedResult(Map<NominationId, Collection<ElectedCandidate>> electedCandidates,
|
||||
public record ElectedResult(Map<String, Collection<ElectedCandidate>> electedCandidates,
|
||||
LinkedList<Double> usedElectionNumbers) {
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package de.twomartens.wahlrecht.model.internal;
|
||||
|
||||
public record ConstituencyId(String electionName, Integer number) {
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package de.twomartens.wahlrecht.model.internal;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import lombok.Builder;
|
||||
|
||||
@Builder
|
||||
public record ElectedCandidates(ElectedResult overallResult,
|
||||
Map<Integer, ElectedResult> constituencyResults,
|
||||
LinkedList<Double> electionNumbersForSeatAllocation) {
|
||||
|
||||
}
|
|
@ -4,8 +4,10 @@ import de.twomartens.wahlrecht.model.internal.Candidate;
|
|||
import de.twomartens.wahlrecht.model.internal.Constituency;
|
||||
import de.twomartens.wahlrecht.model.internal.Elected;
|
||||
import de.twomartens.wahlrecht.model.internal.ElectedCandidate;
|
||||
import de.twomartens.wahlrecht.model.internal.ElectedCandidates;
|
||||
import de.twomartens.wahlrecht.model.internal.ElectedResult;
|
||||
import de.twomartens.wahlrecht.model.internal.Election;
|
||||
import de.twomartens.wahlrecht.model.internal.ElectionResult;
|
||||
import de.twomartens.wahlrecht.model.internal.Nomination;
|
||||
import de.twomartens.wahlrecht.model.internal.NominationId;
|
||||
import de.twomartens.wahlrecht.model.internal.SeatResult;
|
||||
|
@ -22,22 +24,69 @@ import java.util.Map.Entry;
|
|||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StopWatch;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
@Slf4j
|
||||
public class CalculationService {
|
||||
|
||||
private final NominationService nominationService;
|
||||
|
||||
private final ElectionService electionService;
|
||||
private final LinkedList<Double> electionNumberHistory = new LinkedList<>();
|
||||
private StopWatch stopWatch;
|
||||
|
||||
public ElectedCandidates determineElectedCandidates(@NonNull ElectionResult electionResult) {
|
||||
log.info("Calculate election result for election {}", electionResult.electionName());
|
||||
stopWatch = new StopWatch();
|
||||
stopWatch.start("determineElectedCandidates");
|
||||
Election election = electionService.getElectionInternal(electionResult.electionName());
|
||||
SeatResult seatResult = calculateOverallSeatDistribution(election,
|
||||
electionResult.overallResults());
|
||||
Map<Integer, ElectedResult> constituencyResults = new HashMap<>();
|
||||
|
||||
for (Constituency constituency : election.constituencies()) {
|
||||
ElectedResult electedResult = calculateConstituency(constituency,
|
||||
electionResult.constituencyResults().get(constituency.number()));
|
||||
constituencyResults.put(constituency.number(), electedResult);
|
||||
}
|
||||
Map<String, Collection<ElectedCandidate>> electedCandidatesMap = constituencyResults
|
||||
.entrySet().stream()
|
||||
.flatMap(entry -> entry.getValue().electedCandidates().entrySet().stream())
|
||||
.collect(Collectors.groupingBy(entry -> entry.getKey().getNominationId().partyAbbreviation(),
|
||||
Collectors.flatMapping(entry -> entry.getValue().stream(),
|
||||
Collectors.toCollection(ArrayList::new))));
|
||||
|
||||
Map<VotingResult, Integer> remainingSeatsPerParty = seatResult.seatsPerResult()
|
||||
.entrySet().stream()
|
||||
.map(entry -> Map.entry(entry.getKey(),
|
||||
entry.getValue() - electedCandidatesMap.getOrDefault(
|
||||
entry.getKey().getNominationId().partyAbbreviation(),
|
||||
Collections.emptyList()).size()))
|
||||
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
|
||||
|
||||
ElectedResult overallElectedCandidates = calculateElectedOverallCandidates(
|
||||
remainingSeatsPerParty,
|
||||
electedCandidatesMap
|
||||
);
|
||||
|
||||
stopWatch.stop();
|
||||
log.debug("determineCandidates took {} seconds", stopWatch.getTotalTimeSeconds());
|
||||
|
||||
return ElectedCandidates.builder()
|
||||
.overallResult(overallElectedCandidates)
|
||||
.constituencyResults(constituencyResults)
|
||||
.electionNumbersForSeatAllocation(seatResult.usedElectionNumbers())
|
||||
.build();
|
||||
}
|
||||
|
||||
public ElectedResult calculateConstituency(@NonNull Constituency constituency,
|
||||
@NonNull Collection<VotingResult> votingResults) {
|
||||
electionNumberHistory.clear();
|
||||
log.info("Calculate constituency {}", constituency.name());
|
||||
int totalVotes = votingResults.stream()
|
||||
.map(VotingResult::getTotalVotes)
|
||||
.reduce(0, Integer::sum);
|
||||
|
@ -49,12 +98,13 @@ public class CalculationService {
|
|||
|
||||
return ElectedResult.builder()
|
||||
.electedCandidates(findElectedCandidates(assignedSeatsPerResult, Collections.emptyMap()))
|
||||
.usedElectionNumbers(electionNumberHistory)
|
||||
.usedElectionNumbers(new LinkedList<>(electionNumberHistory))
|
||||
.build();
|
||||
}
|
||||
|
||||
public SeatResult calculateOverallSeatDistribution(@NonNull Election election,
|
||||
@NonNull Collection<VotingResult> votingResults) {
|
||||
log.info("Calculate overall seat distribution for election {}", election.name());
|
||||
electionNumberHistory.clear();
|
||||
int totalVotes = votingResults.stream()
|
||||
.map(VotingResult::getTotalVotes)
|
||||
|
@ -64,8 +114,12 @@ public class CalculationService {
|
|||
int totalIgnoredVotes = 0;
|
||||
for (VotingResult votingResult : votingResults) {
|
||||
if (passesVotingThreshold(election, totalVotes, votingResult)) {
|
||||
log.info("Party passes voting threshold: {}",
|
||||
votingResult.getNominationId().partyAbbreviation());
|
||||
validVotingResults.add(votingResult);
|
||||
} else {
|
||||
log.info("Party fails voting threshold: {}",
|
||||
votingResult.getNominationId().partyAbbreviation());
|
||||
totalIgnoredVotes += votingResult.getTotalVotes();
|
||||
}
|
||||
}
|
||||
|
@ -79,18 +133,25 @@ public class CalculationService {
|
|||
|
||||
return SeatResult.builder()
|
||||
.seatsPerResult(assignedSeatsPerResult)
|
||||
.usedElectionNumbers(electionNumberHistory)
|
||||
.usedElectionNumbers(new LinkedList<>(electionNumberHistory))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all elected candidates for given voting results
|
||||
*
|
||||
* @param seatsPerNomination Seats to allocate per voting result
|
||||
* @param electedCandidates Already elected candidates per party
|
||||
*/
|
||||
public ElectedResult calculateElectedOverallCandidates(
|
||||
Map<VotingResult, Integer> seatsPerNomination,
|
||||
Map<VotingResult, Collection<ElectedCandidate>> electedCandidates) {
|
||||
Map<String, Collection<ElectedCandidate>> electedCandidates) {
|
||||
log.info("Calculate overall elected candidates");
|
||||
electionNumberHistory.clear();
|
||||
|
||||
return ElectedResult.builder()
|
||||
.electedCandidates(findElectedCandidates(seatsPerNomination, electedCandidates))
|
||||
.usedElectionNumbers(electionNumberHistory)
|
||||
.usedElectionNumbers(new LinkedList<>(electionNumberHistory))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -100,10 +161,16 @@ public class CalculationService {
|
|||
<= votingResult.getTotalVotes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all elected candidates for given voting results.
|
||||
*
|
||||
* @param assignedSeatsPerNomination Map of seats to allocate for a voting result
|
||||
* @param alreadyElectedCandidates Map of already elected candidates per party
|
||||
*/
|
||||
@NonNull
|
||||
private Map<VotingResult, Collection<ElectedCandidate>> findElectedCandidates(
|
||||
@NonNull Map<VotingResult, Integer> assignedSeatsPerNomination,
|
||||
Map<VotingResult, Collection<ElectedCandidate>> alreadyElectedCandidates) {
|
||||
Map<String, Collection<ElectedCandidate>> alreadyElectedCandidates) {
|
||||
|
||||
Map<VotingResult, Collection<ElectedCandidate>> electedCandidates = new HashMap<>();
|
||||
for (Entry<VotingResult, Integer> entry : assignedSeatsPerNomination.entrySet()) {
|
||||
|
@ -111,7 +178,9 @@ public class CalculationService {
|
|||
Collection<ElectedCandidate> candidates = findCandidates(
|
||||
entry.getKey(),
|
||||
entry.getValue(),
|
||||
alreadyElectedCandidates.getOrDefault(entry.getKey(), Collections.emptyList())
|
||||
alreadyElectedCandidates.getOrDefault(
|
||||
entry.getKey().getNominationId().partyAbbreviation(),
|
||||
Collections.emptyList())
|
||||
);
|
||||
electedCandidates.put(entry.getKey(), candidates);
|
||||
}
|
||||
|
@ -120,9 +189,18 @@ public class CalculationService {
|
|||
return electedCandidates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds elected candidates for given voting result.
|
||||
*
|
||||
* @param votingResult Result to search candidates for
|
||||
* @param numberOfSeats Number of seats to allocate
|
||||
* @param alreadyElectedCandidates Candidates already elected previously
|
||||
*/
|
||||
@NonNull
|
||||
private Collection<ElectedCandidate> findCandidates(@NonNull VotingResult votingResult,
|
||||
int numberOfSeats, Collection<ElectedCandidate> alreadyElectedCandidates) {
|
||||
int numberOfSeats, @NonNull Collection<ElectedCandidate> alreadyElectedCandidates) {
|
||||
log.info("Find elected candidates on nomination: {}",
|
||||
votingResult.getNominationId().name());
|
||||
List<Integer> individualVotesOrder = votingResult.getVotesPerPosition()
|
||||
.entrySet().stream()
|
||||
.sorted(Comparator.comparing(Entry<Integer, Integer>::getValue).reversed())
|
||||
|
@ -184,6 +262,7 @@ public class CalculationService {
|
|||
double electionNumber = initialElectionNumber;
|
||||
long assignedSeats;
|
||||
Map<VotingResult, Integer> assignedSeatsPerVotingResult;
|
||||
log.debug("Calculate assigned seats with initial election number {}", electionNumber);
|
||||
|
||||
do {
|
||||
electionNumberHistory.add(electionNumber);
|
||||
|
@ -202,10 +281,12 @@ public class CalculationService {
|
|||
// election number was too big, decrease
|
||||
electionNumber = calculateLowerElectionNumber(initialElectionNumber, seatsPerVotingResult,
|
||||
assignedSeatsPerVotingResult);
|
||||
log.debug("Calculated lower election number {}", electionNumber);
|
||||
} else if (assignedSeats > numberOfSeats) {
|
||||
// election number was too small, increase
|
||||
electionNumber = calculateHigherElectionNumber(initialElectionNumber, seatsPerVotingResult,
|
||||
assignedSeatsPerVotingResult);
|
||||
log.debug("Calculated higher election number {}", electionNumber);
|
||||
}
|
||||
} while (assignedSeats != numberOfSeats);
|
||||
|
||||
|
@ -216,12 +297,10 @@ public class CalculationService {
|
|||
@NonNull Map<VotingResult, Double> seatsPerVotingResult,
|
||||
Map<VotingResult, Integer> assignedSeatsPerVotingResult) {
|
||||
double electionNumber;
|
||||
electionNumber = seatsPerVotingResult.entrySet().stream()
|
||||
.map(entry -> Map.entry(entry.getKey(),
|
||||
entry.getValue() - assignedSeatsPerVotingResult.get(entry.getKey())))
|
||||
.min(Comparator.comparing(Entry<VotingResult, Double>::getValue))
|
||||
.map(entry -> entry.getKey().getTotalVotes()
|
||||
/ (assignedSeatsPerVotingResult.get(entry.getKey()) - 0.5))
|
||||
electionNumber = seatsPerVotingResult.keySet().stream()
|
||||
.map(votingResult -> votingResult.getTotalVotes()
|
||||
/ (assignedSeatsPerVotingResult.get(votingResult) - 0.5))
|
||||
.min(Comparator.comparing(Double::doubleValue))
|
||||
.orElse(initialElectionNumber);
|
||||
return electionNumber;
|
||||
}
|
||||
|
@ -230,12 +309,10 @@ public class CalculationService {
|
|||
@NonNull Map<VotingResult, Double> seatsPerVotingResult,
|
||||
Map<VotingResult, Integer> assignedSeatsPerVotingResult) {
|
||||
double electionNumber;
|
||||
electionNumber = seatsPerVotingResult.entrySet().stream()
|
||||
.map(entry -> Map.entry(entry.getKey(),
|
||||
entry.getValue() - assignedSeatsPerVotingResult.get(entry.getKey())))
|
||||
.max(Comparator.comparing(Entry<VotingResult, Double>::getValue))
|
||||
.map(entry -> entry.getKey().getTotalVotes()
|
||||
/ (assignedSeatsPerVotingResult.get(entry.getKey()) + 0.5))
|
||||
electionNumber = seatsPerVotingResult.keySet().stream()
|
||||
.map(votingResult -> votingResult.getTotalVotes()
|
||||
/ (assignedSeatsPerVotingResult.get(votingResult) + 0.5))
|
||||
.max(Comparator.comparing(Double::doubleValue))
|
||||
.orElse(initialElectionNumber);
|
||||
return electionNumber;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
package de.twomartens.wahlrecht.service;
|
||||
|
||||
import de.twomartens.wahlrecht.mapper.v1.ConstituencyMapper;
|
||||
import de.twomartens.wahlrecht.model.db.Constituency;
|
||||
import de.twomartens.wahlrecht.model.internal.ConstituencyId;
|
||||
import de.twomartens.wahlrecht.repository.ConstituencyRepository;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
@ -13,21 +18,52 @@ import org.springframework.stereotype.Service;
|
|||
public class ConstituencyService {
|
||||
|
||||
private final ConstituencyRepository repository;
|
||||
private final ConstituencyMapper mapper = Mappers.getMapper(ConstituencyMapper.class);
|
||||
|
||||
private Map<ConstituencyId, Constituency> constituencies;
|
||||
|
||||
public Collection<Constituency> getConstituencies() {
|
||||
return repository.findAll();
|
||||
}
|
||||
|
||||
public Constituency storeConstituency(@NonNull Constituency constituency) {
|
||||
Optional<Constituency> foundOptional = repository
|
||||
.findByElectionNameAndNumber(constituency.getElectionName(), constituency.getNumber());
|
||||
public Constituency getConstituency(ConstituencyId constituencyId) {
|
||||
return constituencies.get(constituencyId);
|
||||
}
|
||||
|
||||
if (foundOptional.isPresent()) {
|
||||
Constituency found = foundOptional.get();
|
||||
found.setNumberOfSeats(constituency.getNumberOfSeats());
|
||||
found.setName(constituency.getName());
|
||||
constituency = found;
|
||||
public de.twomartens.wahlrecht.model.internal.Constituency getConstituencyInternal(
|
||||
ConstituencyId constituencyId) {
|
||||
return mapper.mapToInternal(getConstituency(constituencyId));
|
||||
}
|
||||
|
||||
public Constituency storeConstituency(@NonNull Constituency constituency) {
|
||||
if (constituencies == null) {
|
||||
fetchConstituencies();
|
||||
}
|
||||
return repository.save(constituency);
|
||||
|
||||
ConstituencyId constituencyId = new ConstituencyId(constituency.getElectionName(),
|
||||
constituency.getNumber());
|
||||
Constituency existing = constituencies.get(constituencyId);
|
||||
|
||||
if (constituency.equals(existing)) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
if (existing != null) {
|
||||
existing.setNumberOfSeats(constituency.getNumberOfSeats());
|
||||
existing.setName(constituency.getName());
|
||||
constituency = existing;
|
||||
}
|
||||
Constituency stored = repository.save(constituency);
|
||||
constituencies.put(constituencyId, stored);
|
||||
|
||||
return stored;
|
||||
}
|
||||
|
||||
private void fetchConstituencies() {
|
||||
constituencies = repository.findAll().stream()
|
||||
.collect(Collectors.toMap(
|
||||
constituency -> new ConstituencyId(constituency.getElectionName(),
|
||||
constituency.getNumber()),
|
||||
Function.identity()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
package de.twomartens.wahlrecht.service;
|
||||
|
||||
import de.twomartens.wahlrecht.mapper.v1.ElectionMapper;
|
||||
import de.twomartens.wahlrecht.model.db.Constituency;
|
||||
import de.twomartens.wahlrecht.model.db.Election;
|
||||
import de.twomartens.wahlrecht.repository.ElectionRepository;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
@ -16,22 +20,43 @@ public class ElectionService {
|
|||
|
||||
private final ElectionRepository electionRepository;
|
||||
private final ConstituencyService constituencyService;
|
||||
private final ElectionMapper electionMapper = Mappers.getMapper(ElectionMapper.class);
|
||||
|
||||
private Map<String, Election> elections;
|
||||
|
||||
public Collection<Election> getElections() {
|
||||
return electionRepository.findAll();
|
||||
}
|
||||
|
||||
public Election getElection(String electionName) {
|
||||
if (elections == null) {
|
||||
fetchElections();
|
||||
}
|
||||
return elections.get(electionName);
|
||||
}
|
||||
|
||||
public de.twomartens.wahlrecht.model.internal.Election getElectionInternal(String electionName) {
|
||||
return electionMapper.mapToInternal(getElection(electionName));
|
||||
}
|
||||
|
||||
public boolean storeElection(@NonNull Election election) {
|
||||
if (elections == null) {
|
||||
fetchElections();
|
||||
}
|
||||
boolean createdNew = true;
|
||||
String electionName = election.getName();
|
||||
Optional<Election> existingElectionOptional = electionRepository.findByName(electionName);
|
||||
Election existing = elections.get(electionName);
|
||||
|
||||
if (election.equals(existing)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Collection<Constituency> constituencies = new ArrayList<>();
|
||||
election.getConstituencies()
|
||||
.forEach(constituency -> constituencies.add(
|
||||
constituencyService.storeConstituency(constituency)));
|
||||
|
||||
if (existingElectionOptional.isPresent()) {
|
||||
Election existing = existingElectionOptional.get();
|
||||
if (existing != null) {
|
||||
existing.setDay(election.getDay());
|
||||
existing.setTotalNumberOfSeats(election.getTotalNumberOfSeats());
|
||||
existing.setVotingThreshold(election.getVotingThreshold());
|
||||
|
@ -40,8 +65,14 @@ public class ElectionService {
|
|||
}
|
||||
|
||||
election.setConstituencies(constituencies);
|
||||
electionRepository.save(election);
|
||||
Election stored = electionRepository.save(election);
|
||||
elections.put(electionName, stored);
|
||||
|
||||
return createdNew;
|
||||
}
|
||||
|
||||
private void fetchElections() {
|
||||
elections = electionRepository.findAll().stream()
|
||||
.collect(Collectors.toMap(Election::getName, Function.identity()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ import java.util.Collection;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
@ -35,13 +34,16 @@ public class PartyService {
|
|||
|
||||
public PartyInElection getPartyByElectionNameAndAbbreviation(String electionName,
|
||||
String abbreviation) {
|
||||
Optional<PartyInElection> optionalParty = partyRepository.findByAbbreviationAndElectionName(
|
||||
abbreviation, electionName);
|
||||
if (optionalParty.isEmpty()) {
|
||||
if (parties == null) {
|
||||
fetchParties();
|
||||
}
|
||||
PartyId partyId = new PartyId(electionName, abbreviation);
|
||||
PartyInElection party = parties.get(partyId);
|
||||
if (party == null) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND,
|
||||
"no party found for %s and %s".formatted(electionName, abbreviation));
|
||||
}
|
||||
return optionalParty.get();
|
||||
return party;
|
||||
}
|
||||
|
||||
public boolean storeParty(PartyInElection partyInElection) {
|
||||
|
|
|
@ -116,6 +116,9 @@ class CalculationServiceTest {
|
|||
@Mock
|
||||
private NominationService nominationService;
|
||||
|
||||
@Mock
|
||||
private ElectionService electionService;
|
||||
|
||||
@BeforeAll
|
||||
static void setUp() {
|
||||
setUpCandidatesConstituency();
|
||||
|
@ -250,7 +253,7 @@ class CalculationServiceTest {
|
|||
|
||||
@BeforeEach
|
||||
void setService() {
|
||||
service = new CalculationService(nominationService);
|
||||
service = new CalculationService(nominationService, electionService);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -316,7 +319,7 @@ class CalculationServiceTest {
|
|||
FDP_BEZIRK_RESULT, 3,
|
||||
AFD_BEZIRK_RESULT, 3
|
||||
);
|
||||
Map<VotingResult, Collection<ElectedCandidate>> electedCandidates = setUpConstituencyResults();
|
||||
Map<String, Collection<ElectedCandidate>> electedCandidates = setUpConstituencyResults();
|
||||
when(nominationService.getNominationInternal(SPD_BEZIRK.getId())).thenReturn(SPD_BEZIRK);
|
||||
when(nominationService.getNominationInternal(CDU_BEZIRK.getId())).thenReturn(CDU_BEZIRK);
|
||||
when(nominationService.getNominationInternal(FDP_BEZIRK.getId())).thenReturn(FDP_BEZIRK);
|
||||
|
@ -397,8 +400,8 @@ class CalculationServiceTest {
|
|||
}
|
||||
|
||||
@NonNull
|
||||
private Map<VotingResult, Collection<ElectedCandidate>> setUpConstituencyResults() {
|
||||
return Map.of(GRUENE_BEZIRK_RESULT, List.of(
|
||||
private Map<String, Collection<ElectedCandidate>> setUpConstituencyResults() {
|
||||
return Map.of(GRUENE_BEZIRK_RESULT.getNominationId().partyAbbreviation(), List.of(
|
||||
new ElectedCandidate(GRUENE_BEZIRK.getCandidate(1), Elected.CONSTITUENCY),
|
||||
new ElectedCandidate(GRUENE_BEZIRK.getCandidate(2), Elected.CONSTITUENCY),
|
||||
new ElectedCandidate(GRUENE_BEZIRK.getCandidate(3), Elected.CONSTITUENCY),
|
||||
|
|
Loading…
Reference in New Issue