Skip to content

Commit ef787bc

Browse files
committed
Merge branch 'developer'
2 parents 09e3598 + b202fbb commit ef787bc

File tree

9 files changed

+323
-16
lines changed

9 files changed

+323
-16
lines changed

README.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Lounge Occupancy Analyzer (Análise de Capacidade das Salas de Espera)
2+
Aplicação que analisa a capacidade das salas de espera de passageiros no aeroporto.
3+
4+
## Visão de Negócio
5+
Este sistema foi desenvolvido como uma solução estratégica para o redimensionamento de salas de espera de aeroportos. O objetivo central é garantir que a infraestrutura possua a quantidade exata de assentos necessária para o conforto de todos os passageiros, evitando transtornos operacionais relatados pelo cliente.
6+
7+
Para compreender a lotação máxima em horários de pico, o sistema utiliza uma análise dinâmica de eventos. Isso permite identificar o momento de maior fluxo simultâneo, auxiliando gestores em decisões assertivas de investimento e otimização de espaço com base nos resultados gerados.
8+
9+
## Explicação Técnica
10+
API desenvolvida para o desafio técnico da Atech, focada em calcular a ocupação máxima simultânea de salas de espera de aeroportos utilizando o algoritmo **Sweep Line**.
11+
12+
### Tecnologias Utilizadas
13+
- Java 25
14+
- Spring Boot 4.0.2
15+
- Dependências: SpringWeb (REST API), Validation, Swagger API
16+
- Project Loom (Virtual Threads)
17+
- *GitHub Actions configurado para compilação e execução de testes automatizados.*
18+
19+
### Arquitetura
20+
21+
A solução baseia-se na técnica de **Sweep Line (Linha de Varredura)**, que é ideal para problemas de sobreposição de intervalos.
22+
23+
#### Lógica de Negócio (N, E, S)
24+
25+
**O problema técnico:**
26+
> "Dado um número `N` de passageiros `(1 <= N <= 100)` que passaram pela sala de espera, uma lista `E` com os números do momento de entrada de cada passageiro e uma lista `S` com os números do momento de saída de cada passageiro [...]".
27+
28+
A solução respeitou os padrões do enunciado em toda a aplicação, sendo assim:
29+
30+
- **N**: Número de passageiros (limitado entre 1 e 100 conforme o enunciado).
31+
- **E**: Lista de horários de entrada (mapeados em uma escala simplificada de 1 a 1000).
32+
- **S**: Lista de horários de saída (mapeados em uma escala simplificada de 1 a 1000).
33+
34+
#### Tratamento de Eventos Simultâneos
35+
36+
> "[...] você deve calcular qual foi o número máximo de passageiros simultâneos naquela sala de espera durante todo o período. Considere que se um passageiro entra no mesmo momento que outro sai, então naquele instante só é contabilizada uma pessoa para a lotação da sala."
37+
38+
A solução implementada possui tratamento de eventos que ocorrem no mesmo instante: no modelo de domínio `Event.java`, está configurada o critério de desempate para que a saída seja processada antes da entrada, o que garante que a contagem da ocupação não retorne valores irreais.
39+
40+
*OBS: Está definido nas constantes `ENTRY` (Entrada) e `EXIT` (Saída) para facilitar a leitura do código.*
41+
42+
### Instruções de Setup
43+
44+
#### Pré-requisitos
45+
46+
* JDK 25.
47+
* Maven 3.9+.
48+
49+
#### Passo a Passo
50+
51+
1. Clone o repositório:
52+
```bash
53+
git clone https://github.com/MarleneMoraes/lounge-occupancy-analyzer.git
54+
```
55+
56+
2. Execute a aplicação:
57+
```bash
58+
# Se tiver Maven global:
59+
mvn spring-boot:run
60+
61+
# Ou use o Wrapper incluso no projeto:
62+
./mvnw spring-boot:run
63+
```
64+
65+
#### Como Utilizar
66+
67+
A API disponibiliza um endpoint principal para o cálculo da ocupação.
68+
69+
- **Endpoint POST**: `/api/v1/occupancy/calculate`
70+
71+
##### **Teste via Terminal (CURL)**
72+
73+
Você pode testar o cenário padrão do desafio com o seguinte comando:
74+
75+
- Exemplo 1:
76+
```bash
77+
curl -X POST http://localhost:8080/api/v1/occupancy/calculate \
78+
-H "Content-Type: application/json" \
79+
-d '{
80+
"N": 3,
81+
"E": [1, 5, 7],
82+
"S": [9, 13, 12]
83+
}'
84+
85+
```
86+
**Resposta esperada**: `{"maxOccupancy": 3}`
87+
88+
- Exemplo 2:
89+
```bash
90+
curl -X POST http://localhost:8080/api/v1/occupancy/calculate \
91+
-H "Content-Type: application/json" \
92+
-d '{
93+
"N": 4,
94+
"E": [1, 4, 8, 10],
95+
"S": [3, 8, 10, 17]
96+
}'
97+
98+
```
99+
**Resposta esperada**: `{"maxOccupancy": 1}`
100+
101+
##### **Interface Visual (Swagger)**
102+
103+
Para uma experiência interativa, acesse a interface do Swagger após iniciar a aplicação: `http://localhost:8080/swagger-ui/index.html`
104+
105+
Na interface, você encontrará a documentação completa dos esquemas de entrada e poderá realizar testes manuais preenchendo os campos de `N`, `E` e `S`.
106+
107+
### Testes Automatizados
108+
109+
A suíte de testes unitários cobre cenários padrão, casos de borda e validações de erro. Execute no terminal: `mvn test`.

pom.xml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<groupId>org.springframework.boot</groupId>
77
<artifactId>spring-boot-starter-parent</artifactId>
88
<version>4.0.2</version>
9-
<relativePath/> <!-- lookup parent from repository -->
9+
<relativePath/>
1010
</parent>
1111
<groupId>com.atech</groupId>
1212
<artifactId>lounge-occupancy-analyzer</artifactId>
@@ -18,7 +18,11 @@
1818
<license/>
1919
</licenses>
2020
<developers>
21-
<developer/>
21+
<developer>
22+
<id>MarleneMoraes</id>
23+
<name>Marlene Moraes</name>
24+
<email>marlenevmoraes@outlook.com</email>
25+
</developer>
2226
</developers>
2327
<scm>
2428
<connection/>
@@ -49,6 +53,11 @@
4953
<artifactId>spring-boot-starter-webmvc-test</artifactId>
5054
<scope>test</scope>
5155
</dependency>
56+
<dependency>
57+
<groupId>org.springdoc</groupId>
58+
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
59+
<version>2.8.1</version>
60+
</dependency>
5261
</dependencies>
5362

5463
<build>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.atech.loungeoccupancyanalyzer.application.dto;
2+
3+
import java.util.List;
4+
5+
import jakarta.validation.constraints.Max;
6+
import jakarta.validation.constraints.Min;
7+
import jakarta.validation.constraints.NotNull;
8+
import jakarta.validation.constraints.Size;
9+
10+
public record OccupancyRequest(
11+
@NotNull(message = "N is required")
12+
@Min(value = 1, message = "N must be at least 1")
13+
@Max(value = 100, message = "N cannot exceed 100")
14+
int N,
15+
16+
@NotNull(message = "List E is required")
17+
@Size(min = 1, max = 100, message = "E list size must match N")
18+
List<Integer> E,
19+
20+
@NotNull(message = "List S is required")
21+
@Size(min = 1, max = 100, message = "S list size must match N")
22+
List<Integer> S) {
23+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.atech.loungeoccupancyanalyzer.domain.model;
2+
3+
/**
4+
* Represents a point in time where a passenger enters or exits the lounge.
5+
* ENTRY (+1) maps to List E, while EXIT (-1) maps to List S from the
6+
* requirements.
7+
*/
8+
9+
public record Event(int time, int type) implements Comparable<Event> {
10+
public static final int ENTRY = 1;
11+
public static final int EXIT = -1;
12+
13+
@Override
14+
public int compareTo(Event other) {
15+
if (this.time != other.time) {
16+
return Integer.compare(this.time, other.time);
17+
}
18+
19+
return Integer.compare(this.type, other.type);
20+
}
21+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.atech.loungeoccupancyanalyzer.domain.service;
2+
3+
import java.util.ArrayList;
4+
import java.util.Collections;
5+
import java.util.List;
6+
7+
import com.atech.loungeoccupancyanalyzer.domain.model.Event;
8+
9+
public class OccupancyCalculator {
10+
public int calculateMax(int N, List<Integer> E, List<Integer> S) {
11+
if (N == 0 || E.isEmpty()) {
12+
return 0;
13+
}
14+
15+
List<Event> events = new ArrayList<>();
16+
17+
for (int i = 0; i < N; i++) {
18+
events.add(new Event(E.get(i), Event.ENTRY));
19+
events.add(new Event(S.get(i), Event.EXIT));
20+
}
21+
22+
Collections.sort(events);
23+
24+
int currentOccupancy = 0;
25+
int maxOccupancy = 0;
26+
27+
for (Event event : events) {
28+
currentOccupancy += event.type();
29+
maxOccupancy = Math.max(maxOccupancy, currentOccupancy);
30+
}
31+
32+
return maxOccupancy;
33+
}
34+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.atech.loungeoccupancyanalyzer.infrastructure.controller;
2+
3+
import java.util.Map;
4+
5+
import org.springframework.http.ResponseEntity;
6+
import org.springframework.web.bind.annotation.PostMapping;
7+
import org.springframework.web.bind.annotation.RequestBody;
8+
import org.springframework.web.bind.annotation.RequestMapping;
9+
import org.springframework.web.bind.annotation.RestController;
10+
11+
import com.atech.loungeoccupancyanalyzer.LoungeOccupancyAnalyzerApplication;
12+
import com.atech.loungeoccupancyanalyzer.application.dto.OccupancyRequest;
13+
import com.atech.loungeoccupancyanalyzer.domain.service.OccupancyCalculator;
14+
15+
import jakarta.validation.Valid;
16+
17+
@RestController
18+
@RequestMapping("/api/v1/occupancy")
19+
public class OccupancyController {
20+
21+
private final LoungeOccupancyAnalyzerApplication loungeOccupancyAnalyzerApplication;
22+
23+
private final OccupancyCalculator calculator = new OccupancyCalculator();
24+
25+
OccupancyController(LoungeOccupancyAnalyzerApplication loungeOccupancyAnalyzerApplication) {
26+
this.loungeOccupancyAnalyzerApplication = loungeOccupancyAnalyzerApplication;
27+
}
28+
29+
@PostMapping("/calculate")
30+
public ResponseEntity<?> calculate(@Valid @RequestBody OccupancyRequest request) {
31+
if (request.E().size() != request.N() || request.S().size() != request.N()) {
32+
return ResponseEntity.badRequest()
33+
.body(Map.of("error", "The size of lists E and S must be equal to N"));
34+
}
35+
36+
int maxOccupancy = calculator.calculateMax(request.N(), request.E(), request.S());
37+
38+
return ResponseEntity.ok(Map.of("maxOccupancy", maxOccupancy));
39+
}
40+
41+
}
Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
11
spring:
22
application:
3-
name: Lounge Occupancy Analyzer
3+
name: lounge-occupancy-analyzer
4+
threads:
5+
virtual:
6+
enabled: true
7+
web:
8+
error:
9+
include-message: always
10+
11+
server:
12+
port: 8080

src/test/java/com/atech/loungeoccupancyanalyzer/LoungeOccupancyAnalyzerApplicationTests.java

Lines changed: 0 additions & 13 deletions
This file was deleted.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package com.atech.loungeoccupancyanalyzer.domain.service;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
6+
import java.util.List;
7+
8+
import org.junit.jupiter.api.DisplayName;
9+
import org.junit.jupiter.api.Test;
10+
11+
class OccupancyCalculatorTest {
12+
private final OccupancyCalculator calculator = new OccupancyCalculator();
13+
14+
private void assertOccupancy(int expected, int N, List<Integer> E, List<Integer> S, String message) {
15+
assertEquals(expected, calculator.calculateMax(N, E, S), message);
16+
}
17+
18+
@Test
19+
@DisplayName("Standard Example - Max occupancy is 3")
20+
void maxOccupancyIsThree() {
21+
assertOccupancy(
22+
3,
23+
3,
24+
List.of(1,5,7),
25+
List.of(9,13,12),
26+
"The maximun occupancy should be 3");
27+
}
28+
29+
@Test
30+
@DisplayName("Standard Example - Max occupancy is 1")
31+
void maxOccupancyIsOne() {
32+
assertOccupancy(
33+
1,
34+
4,
35+
List.of(1,4,8,10),
36+
List.of(3,8,10,17),
37+
"The maximun occupancy should be 1");
38+
}
39+
40+
@Test
41+
@DisplayName("Case Empty Lists")
42+
void returnZeroWhenListsAreEmpty() {
43+
assertOccupancy(
44+
0,
45+
0,
46+
List.of(),
47+
List.of(),
48+
"The maximun occupancy should be 0 for empty lists");
49+
}
50+
51+
@Test
52+
@DisplayName("Case: Simultaneous Entry and Exit")
53+
void handleSameEntryAndExitTime() {
54+
assertOccupancy(
55+
0,
56+
1,
57+
List.of(1),
58+
List.of(1),
59+
"Should count 1 occupant even if entry and exit happen at the same time"
60+
);
61+
}
62+
63+
@Test
64+
@DisplayName("Case: Exception on Size Mismatch")
65+
void throwExceptionWhenSizesMismatch() {
66+
int n = 3;
67+
List<Integer> e = List.of(1, 2);
68+
List<Integer> s = List.of(5, 6, 7);
69+
70+
assertThrows(IndexOutOfBoundsException.class, () -> {
71+
calculator.calculateMax(n, e, s);
72+
}, "Should throw IndexOutOfBoundsException when list size does not match N");
73+
}
74+
}

0 commit comments

Comments
 (0)