diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..7b016a89 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.compile.nullAnalysis.mode": "automatic" +} \ No newline at end of file diff --git a/challenges/bdd/.gitignore b/challenges/bdd/.gitignore deleted file mode 100644 index 549e00a2..00000000 --- a/challenges/bdd/.gitignore +++ /dev/null @@ -1,33 +0,0 @@ -HELP.md -target/ -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/**/target/ -!**/src/test/**/target/ - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ -build/ -!**/src/main/**/build/ -!**/src/test/**/build/ - -### VS Code ### -.vscode/ diff --git a/challenges/bdd/README.md b/challenges/bdd/README.md deleted file mode 100644 index a4c51a3b..00000000 --- a/challenges/bdd/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Behavior Driven Development (BDD) challenge - -## Introduction - -Behavior Driven Development (BDD) is a software development process that emerged from Test Driven Development (TDD). BDD is a refinement of TDD and is intended to make TDD and Agile development more accessible and intuitive to newcomers and experts alike. - -This challenge is intended to utilize Github Copilot to help you write BDD tests for the following user story: - -> As a user, I want to be able to interact with the Author REST API endpoints so that I can manage authors in the system. This includes being able to create a new author, update an existing author's details, retrieve details about a specific author or all authors, and delete an author from the system. An Author has an id and a name. - -This user story has already a working implementation in the [AuthorController.java](src/main/java/com/microsoft/hackathon/demo/controller/AuthorController.java) file. - -## Challenge - -Your challenge is to write BDD tests for the user story above and all the test must pass based on the implementation in the [AuthorController.java](src/main/java/com/microsoft/hackathon/demo/controller/AuthorController.java) file. - -**Hints** - -Use Copilot chat to help you define the features and scenarios for the user story. Consider using the [Gherkin](https://cucumber.io/docs/gherkin/) syntax to define the feature and scenarios and use the [Cucumber](https://cucumber.io/docs/cucumber/) framework to run the tests. diff --git a/challenges/bdd/pom.xml b/challenges/bdd/pom.xml deleted file mode 100644 index c02cf081..00000000 --- a/challenges/bdd/pom.xml +++ /dev/null @@ -1,91 +0,0 @@ - - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.1.1 - - - com.microsoft.hackathon - demo-bdd - 0.0.1-SNAPSHOT - demo-bdd - Demo BDD project for Spring Boot - - 17 - - - - org.springframework.boot - spring-boot-starter-data-jpa - - - org.springframework.boot - spring-boot-starter-web - - - - com.h2database - h2 - runtime - - - org.springframework.boot - spring-boot-starter-test - test - - - - io.cucumber - cucumber-core - 7.14.1 - test - - - - io.cucumber - cucumber-java - 7.14.1 - test - - - - io.cucumber - cucumber-junit - 7.14.1 - test - - - - io.cucumber - cucumber-spring - 7.14.1 - test - - - - org.junit.vintage - junit-vintage-engine - 5.10.1 - test - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - org.apache.maven.plugins - maven-surefire-plugin - 3.1.2 - - - - - diff --git a/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/DemoApplication.java b/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/DemoApplication.java deleted file mode 100644 index b0d9a3ee..00000000 --- a/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/DemoApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.microsoft.hackathon.demo; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class DemoApplication { - - public static void main(String[] args) { - SpringApplication.run(DemoApplication.class, args); - } - -} diff --git a/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/controller/AuthorController.java b/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/controller/AuthorController.java deleted file mode 100644 index d7ddfd9a..00000000 --- a/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/controller/AuthorController.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.microsoft.hackathon.demo.controller; - -// add imports -import com.microsoft.hackathon.demo.model.Author; -import com.microsoft.hackathon.demo.repository.AuthorRepository; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.server.ResponseStatusException; -import java.util.List; - - -@RestController -@RequestMapping("/authors") -public class AuthorController { - @Autowired - private AuthorRepository authorRepository; - - @GetMapping - public List getAllAuthors() { - return authorRepository.findAll(); - } - - @GetMapping("/{id}") - public Author getAuthorById(@PathVariable Long id) { - return authorRepository.findById(id) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - } - - @PostMapping - public Author createAuthor(@RequestBody Author author) { - return authorRepository.save(author); - } - - @PutMapping("/{id}") - public Author updateAuthor(@PathVariable Long id, @RequestBody Author author) { - if (!authorRepository.existsById(id)) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND); - } - author.setId(id); - return authorRepository.save(author); - } - - @DeleteMapping("/{id}") - public void deleteAuthor(@PathVariable Long id) { - if (!authorRepository.existsById(id)) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND); - } - authorRepository.deleteById(id); - } -} \ No newline at end of file diff --git a/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/controller/BookController.java b/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/controller/BookController.java deleted file mode 100644 index f2aa03ea..00000000 --- a/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/controller/BookController.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.microsoft.hackathon.demo.controller; - -// add imports -import com.microsoft.hackathon.demo.model.Book; -import com.microsoft.hackathon.demo.model.Rating; -import com.microsoft.hackathon.demo.repository.BookRepository; -import com.microsoft.hackathon.demo.repository.RatingRepository; -import com.microsoft.hackathon.demo.service.BookService; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.server.ResponseStatusException; -import java.util.List; - - -@RestController -@RequestMapping("/books") -public class BookController { - - @Autowired - private BookRepository bookRepository; - - @Autowired - private RatingRepository ratingRepository; - - @Autowired - private BookService bookService; - - @GetMapping - public List getAllBooks() { - return bookRepository.findAll(); - } - - @GetMapping("/{id}") - public Book getBookById(@PathVariable Long id) { - return bookRepository.findById(id) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - } - - @PostMapping - public Book createBook(@RequestBody Book book) { - return bookRepository.save(book); - } - - @PutMapping("/{id}") - public Book updateBook(@PathVariable Long id, @RequestBody Book book) { - if (!bookRepository.existsById(id)) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND); - } - book.setId(id); - return bookRepository.save(book); - } - - @DeleteMapping("/{id}") - public void deleteBook(@PathVariable Long id) { - if (!bookRepository.existsById(id)) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND); - } - bookRepository.deleteById(id); - } - - @GetMapping("/{bookId}/ratings") - public List getAllRatingsForBook(@PathVariable Long bookId) { - Book book = bookRepository.findById(bookId) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - return book.getRatings(); - } - - @GetMapping("/{bookId}/ratings/{id}") - public Rating getRatingByIdForBook(@PathVariable Long bookId, @PathVariable Long id) { - Book book = bookRepository.findById(bookId) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - return book.getRatings().stream() - .filter(rating -> rating.getId().equals(id)) - .findFirst() - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - } - - @PostMapping("/{bookId}/ratings") - public Rating createRatingForBook(@PathVariable Long bookId, @RequestBody Rating rating) { - Book book = bookRepository.findById(bookId) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - rating.setBook(book); - return ratingRepository.save(rating); - } - - @PutMapping("/{bookId}/ratings/{id}") - public Rating updateRatingForBook(@PathVariable Long bookId, @PathVariable Long id, @RequestBody Rating rating) { - Book book = bookRepository.findById(bookId) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - Rating existingRating = book.getRatings().stream() - .filter(r -> r.getId().equals(id)) - .findFirst() - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - existingRating.setScore(rating.getScore()); - return ratingRepository.save(existingRating); - } - - @DeleteMapping("/{bookId}/ratings/{id}") - public void deleteRatingForBook(@PathVariable Long bookId, @PathVariable Long id) { - Book book = bookRepository.findById(bookId) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - Rating existingRating = book.getRatings().stream() - .filter(r -> r.getId().equals(id)) - .findFirst() - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - ratingRepository.delete(existingRating); - } - - @GetMapping("/{id}/ratings/average") - public ResponseEntity getAverageRating(@PathVariable Long id) { - double averageRating = bookService.getAverageRating(id); - return ResponseEntity.ok(averageRating); - } -} \ No newline at end of file diff --git a/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/controller/RatingController.java b/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/controller/RatingController.java deleted file mode 100644 index 2c628a38..00000000 --- a/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/controller/RatingController.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.microsoft.hackathon.demo.controller; - -public class RatingController { - -} diff --git a/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/model/Author.java b/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/model/Author.java deleted file mode 100644 index d231f2d6..00000000 --- a/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/model/Author.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.microsoft.hackathon.demo.model; - -// add required imports -import java.util.List; -import jakarta.persistence.*; -import java.util.ArrayList; -import com.fasterxml.jackson.annotation.JsonIgnore; - - -@Entity -public class Author { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - private String name; - - @ManyToMany(mappedBy = "authors") - @JsonIgnore - private List books; - - // constructors, getters, and setters - - // constructor - public Author() { - } - - // constructor - public Author(String name) { - this.name = name; - } - - // getters - public Long getId() { - return id; - } - - public String getName() { - return name; - } - - public List getBooks() { - return books; - } - - //setters - public void setId(Long id) { - this.id = id; - } - - public void setName(String name) { - this.name = name; - } - - // add book - public void addBook(Book book) { - if (getBooks()==null) { - books = new ArrayList<>(); - } - books.add(book); - } - - //toString - @Override - public String toString() { - return "Author{" + - "id=" + id + - ", name='" + name + - '}'; - } - -} \ No newline at end of file diff --git a/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/model/Book.java b/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/model/Book.java deleted file mode 100644 index 718cbcfb..00000000 --- a/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/model/Book.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.microsoft.hackathon.demo.model; - -//create imports required for the class -import java.util.List; - -import java.util.ArrayList; -import jakarta.persistence.*; - - -@Entity -public class Book { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - private String title; - - @ManyToMany - private List authors; - - @OneToMany(mappedBy = "book") - private List ratings; - - // constructors, getters, and setters - - // constructor - public Book() { - this.ratings = new ArrayList<>(); - } - - // constructor - public Book(String title, List authors) { - this.title = title; - this.authors = authors; - this.ratings = new ArrayList<>(); - } - - // getters - public Long getId() { - return id; - } - - public String getTitle() { - return title; - } - - public List getAuthors() { - return authors; - } - - public List getRatings() { - return ratings; - } - - // setters - public void setId(Long id) { - this.id = id; - } - - public void setTitle(String title) { - this.title = title; - } - - //add author - public void addAuthor(Author author) { - if (getAuthors() == null) { - this.authors = new ArrayList<>(); - } - this.authors.add(author); - } - - //add rating - public void addRating(Rating rating) { - if (getRatings() == null) { - this.ratings = new ArrayList<>(); - } - this.ratings.add(rating); - } - - // toString method - @Override - public String toString() { - return "Book{" + - "id=" + id + - ", title='" + title + - '}'; - } - -} diff --git a/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/model/Rating.java b/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/model/Rating.java deleted file mode 100644 index 4d3b4d3e..00000000 --- a/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/model/Rating.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.microsoft.hackathon.demo.model; - -import com.fasterxml.jackson.annotation.JsonIgnore; - -// add required imports -import jakarta.persistence.*; - - -@Entity -public class Rating { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - private int score; - - @ManyToOne - @JsonIgnore - private Book book; - - // constructors, getters, and setters - - // constructor - public Rating() { - } - - // constructor - public Rating(int score, Book book) { - this.score = score; - this.book = book; - } - - // getters - public Long getId() { - return id; - } - - public int getScore() { - return score; - } - - public Book getBook() { - return book; - } - - // setters - public void setId(Long id) { - this.id = id; - } - - public void setScore(int score) { - this.score = score; - } - - // add book - public void setBook(Book book) { - this.book = book; - } - - //toString - @Override - public String toString() { - return "Rating{" + - "id=" + id + - ", score=" + score + - ", book=" + book + - '}'; - } -} diff --git a/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/repository/AuthorRepository.java b/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/repository/AuthorRepository.java deleted file mode 100644 index d8bf29cb..00000000 --- a/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/repository/AuthorRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.microsoft.hackathon.demo.repository; - -// add imports -import com.microsoft.hackathon.demo.model.Author; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -@Repository -public interface AuthorRepository extends JpaRepository { -} \ No newline at end of file diff --git a/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/repository/BookRepository.java b/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/repository/BookRepository.java deleted file mode 100644 index 01ad9bfc..00000000 --- a/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/repository/BookRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.microsoft.hackathon.demo.repository; - -// add imports -import com.microsoft.hackathon.demo.model.Book; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - - -@Repository -public interface BookRepository extends JpaRepository { -} \ No newline at end of file diff --git a/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/repository/RatingRepository.java b/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/repository/RatingRepository.java deleted file mode 100644 index fadb0d8c..00000000 --- a/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/repository/RatingRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.microsoft.hackathon.demo.repository; - -// add imports -import com.microsoft.hackathon.demo.model.Rating; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -@Repository -public interface RatingRepository extends JpaRepository { -} diff --git a/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/service/BookService.java b/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/service/BookService.java deleted file mode 100644 index b77b237a..00000000 --- a/challenges/bdd/src/main/java/com/microsoft/hackathon/demo/service/BookService.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.microsoft.hackathon.demo.service; - -// add imports -import java.util.List; -import com.microsoft.hackathon.demo.model.Book; -import com.microsoft.hackathon.demo.model.Rating; -import com.microsoft.hackathon.demo.repository.BookRepository; - -import jakarta.persistence.EntityNotFoundException; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - - -@Service -public class BookService { - - @Autowired - BookRepository bookRepository; - - public double getAverageRating(Long bookId) { - Book book = bookRepository.findById(bookId).orElseThrow(() -> new EntityNotFoundException()); - - List ratings = book.getRatings(); - if (ratings.isEmpty()) { - return 0.0; - } - - double sum = 0.0; - for (Rating rating : ratings) { - sum += rating.getScore(); - } - - return sum / ratings.size(); - } -} diff --git a/challenges/bdd/src/main/resources/application.properties b/challenges/bdd/src/main/resources/application.properties deleted file mode 100644 index f0030768..00000000 --- a/challenges/bdd/src/main/resources/application.properties +++ /dev/null @@ -1,4 +0,0 @@ -spring.datasource.driverClassName=org.h2.Driver -spring.datasource.username=sa -spring.datasource.password= -spring.jpa.database-platform=org.hibernate.dialect.H2Dialect diff --git a/challenges/bdd/src/test/java/com/microsoft/hackathon/demo/CucumberIntegrationTests.java b/challenges/bdd/src/test/java/com/microsoft/hackathon/demo/CucumberIntegrationTests.java deleted file mode 100644 index 928592ce..00000000 --- a/challenges/bdd/src/test/java/com/microsoft/hackathon/demo/CucumberIntegrationTests.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.microsoft.hackathon.demo; - -import io.cucumber.junit.CucumberOptions; -import io.cucumber.spring.CucumberContextConfiguration; - -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; - -import io.cucumber.junit.Cucumber; - - -@RunWith(Cucumber.class) -@CucumberOptions(features = "src/test/resources/features") -@CucumberContextConfiguration -@SpringBootTest(classes = DemoApplication.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) -public class CucumberIntegrationTests { - -} diff --git a/challenges/chatwebsockets/chatwebsockets.md b/challenges/chatwebsockets/chatwebsockets.md deleted file mode 100644 index 705d9a30..00000000 --- a/challenges/chatwebsockets/chatwebsockets.md +++ /dev/null @@ -1,24 +0,0 @@ -# DEVELOP A CHAT BASED ON WEBSOCKETS - -## DESCRIPTION - - The goal of this challenge is to develop a chat based on websockets. The chat should allow users to send and receive messages in real time. - (Optional) The chat should also allow users to send and receive images. - (Optional) The chat should keep the session to start again from where it was left off. - -## INSTRUCTIONS - -### 1. Choose a programming language of your choice. - - You can use the following API to get a list of random users https://randomuser.me/api/?results=10 - - you can use the following API to get a list of random images https://picsum.photos/v2/list?page=2&limit=100 - - you can use socket.io to implement the chat https://socket.io/docs/v4/index.html - - you have an initial index.html file in the root of the project that you can use as a starting point - -### 2. Develop the chat. You can use the following steps as a guide: - - Allow users to send and receive messages in real time. - - (Optional) Create a chat with a login screen. - - (Optional) Allow users to send and receive images. - - (Optional) Keep the session to start again from where it was left off. - -Use Copilot chat to support your learning and development. -Use Copilot to speed up your development. diff --git a/challenges/chatwebsockets/index.html b/challenges/chatwebsockets/index.html deleted file mode 100644 index e69de29b..00000000 diff --git a/challenges/cryptoanalisis/crypto.md b/challenges/cryptoanalisis/crypto.md deleted file mode 100644 index 8bcef747..00000000 --- a/challenges/cryptoanalisis/crypto.md +++ /dev/null @@ -1,30 +0,0 @@ -# Exploring the Cryptocurrency Market - -## Introduction - - Crytpocurrencies are digital currencies that use cryptography for security and are powered by blockchain technology. Bitcoin, first released as open-source software in 2009, is the first decentralized cryptocurrency. Since the release of bitcoin, over 6,000 altcoins (alternative variants of bitcoin, or other cryptocurrencies) have been created. - -## Instructions -1. Use Copilot Chat to create a new notebook in your project. Use command /newnotebook and name it as "Bitcoin Cryptocurrency Market". -2. Use Copilot and Copilot Chat to develop the exercise and support your learning. - -## EXERCISE - - The objective of this project is to explore the Cryptocurrency Market. The dataset is obtained from [Kaggle](https://www.kaggle.com/sudalairajkumar/cryptocurrencypricehistory). The dataset has one csv file for each currency. Price history is available on a daily basis from April 28, 2013. This dataset has the historical price information of some of the top crypto currencies by market capitalization. The dataset contains the following columns: - - - Date : date of observation - - Open : Opening price on the given day - - High : Highest price on the given day - - Low : Lowest price on the given day - - Close : Closing price on the given day - - Volume : Volume of transactions on the given day - - Market Cap : Market capitalization in USD - -### Some of the questions which could be inferred from this dataset are: - - How did the historical prices / market capitalizations of various currencies change over time? - How big is Bitcoin compared with the rest of the cryptocurrencies - Predicting the future price of the currencies - Which currencies are more volatile and which ones are more stable? - How does the price fluctuations of currencies correlate with each other? - Seasonal trend in the price fluctuations diff --git a/challenges/memorygame/memorygame.md b/challenges/memorygame/memorygame.md deleted file mode 100644 index 20b1ef37..00000000 --- a/challenges/memorygame/memorygame.md +++ /dev/null @@ -1,33 +0,0 @@ -# DEVELOP A MEMORY GAME - -## DESCRIPTION - - The goal of this challenge is to develop a memory game. The game consists of a grid of cards. Each card has a symbol on one side. The cards are arranged randomly on the grid with the symbol face down. The player flips two cards over each turn. If the two cards have the same symbol, they remain face up. Otherwise, the cards flip back over after a short period of time. The goal of the game is to match all pairs of cards. - -## INSTRUCTIONS - -### 1. Choose a programming language of your choice. -### 2. Choose a topic for your cards. Some examples are: - - Colors - - Star Wars characters (you can choose this API https://akabab.github.io/starwars-api/#alljson) - - Pokemon characters (you can choose this API https://pokeapi.co/) - - Countries (you can choose this API https://flagsapi.com/#quick) - -### 3. Develop the game. You can use the following steps as a guide: - - Create a grid of cards. The grid should be 4x4, 6x6, or 8x8. - - Choose number of players. The game can be played by one or two players. - - Add a card to the grid. The card should have a symbol on one side. - - Shuffle the cards on the grid. - - Add a click event to each card. When the player clicks a card, the card flips over. - - When the player clicks two cards, check if the cards have the same symbol. If the cards have the same symbol, the cards remain face up. Otherwise, the cards flip back over. - - When all pairs of cards are matched, the player wins the game. - - -Use Copilot chat to support your learning and development. -Use Copilot to speed up your development. - - - - - - diff --git a/completesolution/dataengineer/COVID19WorldwideTestingData.ipynb b/completesolution/dataengineer/COVID19WorldwideTestingData.ipynb deleted file mode 100644 index 81b4d82c..00000000 --- a/completesolution/dataengineer/COVID19WorldwideTestingData.ipynb +++ /dev/null @@ -1,1450 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Import Required Libraries\n", - "Import the necessary libraries, including pandas." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "# Import Required Libraries\n", - "# Here we are importing the necessary libraries for our task\n", - "\n", - "import pandas as pd # pandas is a software library written for the Python programming language for data manipulation and analysis." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Load the Dataset\n", - "Use pandas to load the 'tested_worldwide.csv' file from the root level." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
DateCountry_RegionProvince_StatepositiveactivehospitalizedhospitalizedCurrrecovereddeathtotal_testeddaily_testeddaily_positive
02020-01-16IcelandAll States3.0NaNNaNNaNNaNNaNNaNNaNNaN
12020-01-17IcelandAll States4.0NaNNaNNaNNaNNaNNaNNaN1.0
22020-01-18IcelandAll States7.0NaNNaNNaNNaNNaNNaNNaN3.0
32020-01-20South KoreaAll States1.0NaNNaNNaNNaNNaN4.0NaNNaN
42020-01-22United StatesAll States0.0NaNNaNNaNNaN0.00.0NaNNaN
\n", - "
" - ], - "text/plain": [ - " Date Country_Region Province_State positive active hospitalized \\\n", - "0 2020-01-16 Iceland All States 3.0 NaN NaN \n", - "1 2020-01-17 Iceland All States 4.0 NaN NaN \n", - "2 2020-01-18 Iceland All States 7.0 NaN NaN \n", - "3 2020-01-20 South Korea All States 1.0 NaN NaN \n", - "4 2020-01-22 United States All States 0.0 NaN NaN \n", - "\n", - " hospitalizedCurr recovered death total_tested daily_tested \\\n", - "0 NaN NaN NaN NaN NaN \n", - "1 NaN NaN NaN NaN NaN \n", - "2 NaN NaN NaN NaN NaN \n", - "3 NaN NaN NaN 4.0 NaN \n", - "4 NaN NaN 0.0 0.0 NaN \n", - "\n", - " daily_positive \n", - "0 NaN \n", - "1 1.0 \n", - "2 3.0 \n", - "3 NaN \n", - "4 NaN " - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Load the Dataset\n", - "# Here we are using pandas to load the 'tested_worldwide.csv' file from the root level.\n", - "\n", - "# Load the dataset\n", - "data = pd.read_csv('tested_worldwide.csv')\n", - "\n", - "# Display the first 5 rows of the dataset\n", - "data.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Display the First 5 Rows\n", - "Use the head() function to display the first 5 rows of the dataset." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
DateCountry_RegionProvince_StatepositiveactivehospitalizedhospitalizedCurrrecovereddeathtotal_testeddaily_testeddaily_positive
02020-01-16IcelandAll States3.0NaNNaNNaNNaNNaNNaNNaNNaN
12020-01-17IcelandAll States4.0NaNNaNNaNNaNNaNNaNNaN1.0
22020-01-18IcelandAll States7.0NaNNaNNaNNaNNaNNaNNaN3.0
32020-01-20South KoreaAll States1.0NaNNaNNaNNaNNaN4.0NaNNaN
42020-01-22United StatesAll States0.0NaNNaNNaNNaN0.00.0NaNNaN
\n", - "
" - ], - "text/plain": [ - " Date Country_Region Province_State positive active hospitalized \\\n", - "0 2020-01-16 Iceland All States 3.0 NaN NaN \n", - "1 2020-01-17 Iceland All States 4.0 NaN NaN \n", - "2 2020-01-18 Iceland All States 7.0 NaN NaN \n", - "3 2020-01-20 South Korea All States 1.0 NaN NaN \n", - "4 2020-01-22 United States All States 0.0 NaN NaN \n", - "\n", - " hospitalizedCurr recovered death total_tested daily_tested \\\n", - "0 NaN NaN NaN NaN NaN \n", - "1 NaN NaN NaN NaN NaN \n", - "2 NaN NaN NaN NaN NaN \n", - "3 NaN NaN NaN 4.0 NaN \n", - "4 NaN NaN 0.0 0.0 NaN \n", - "\n", - " daily_positive \n", - "0 NaN \n", - "1 1.0 \n", - "2 3.0 \n", - "3 NaN \n", - "4 NaN " - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Display the First 5 Rows\n", - "# Here we are using the head() function to display the first 5 rows of the dataset\n", - "\n", - "# Display the first 5 rows of the dataset\n", - "data.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Display the number of rows and columns in the dataframe" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Number of rows: 27641\n", - "Number of columns: 12\n" - ] - } - ], - "source": [ - "num_rows, num_cols = data.shape\n", - "print(\"Number of rows:\", num_rows)\n", - "print(\"Number of columns:\", num_cols)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Display the data types of each column" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Date object\n", - "Country_Region object\n", - "Province_State object\n", - "positive float64\n", - "active float64\n", - "hospitalized float64\n", - "hospitalizedCurr float64\n", - "recovered float64\n", - "death float64\n", - "total_tested float64\n", - "daily_tested float64\n", - "daily_positive float64\n", - "dtype: object" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "data.dtypes\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Display the number of missing values in each column" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Date 0\n", - "Country_Region 0\n", - "Province_State 0\n", - "positive 4242\n", - "active 9833\n", - "hospitalized 19231\n", - "hospitalizedCurr 13080\n", - "recovered 9626\n", - "death 4010\n", - "total_tested 912\n", - "daily_tested 1174\n", - "daily_positive 4557\n", - "dtype: int64\n" - ] - } - ], - "source": [ - "missing_values = data.isnull().sum()\n", - "print(missing_values)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Display the number of unique values in each column" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Date 297\n", - "Country_Region 147\n", - "Province_State 81\n", - "positive 14998\n", - "active 9554\n", - "hospitalized 4862\n", - "hospitalizedCurr 2904\n", - "recovered 9183\n", - "death 5641\n", - "total_tested 23610\n", - "daily_tested 13375\n", - "daily_positive 3440\n", - "dtype: int64\n" - ] - } - ], - "source": [ - "unique_values = data.nunique()\n", - "print(unique_values)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Drop the columns that are not needed for the analysis" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "data = data[['Country_Region', 'positive', 'total_tested']]\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Rename the columns to make them more readable" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "data.rename(columns={'Country_Region': 'Country', 'positive': 'Positive Cases', 'total_tested': 'Total Tested'}, inplace=True)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Drop the rows that have missing values" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "data.dropna(inplace=True)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Convert the data types of the columns to the appropriate types" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [], - "source": [ - "data['Positive Cases'] = data['Positive Cases'].astype(int)\n", - "data['Total Tested'] = data['Total Tested'].astype(int)\n", - "data['Country'] = data['Country'].astype(str)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Display the number of missing values in each column" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Country 0\n", - "Positive Cases 0\n", - "Total Tested 0\n", - "dtype: int64\n" - ] - } - ], - "source": [ - "missing_values = data.isnull().sum()\n", - "print(missing_values)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Create a new dataframe that contains the total number of positive cases for each country" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
CountryTotal Positive Cases
0Albania12279
1Argentina28220
2Armenia7225809
3Australia20654
4Austria541013
.........
112United States1837768204
113Uruguay2160
114Veneto119910
115Venezuela461
116Vietnam6450
\n", - "

117 rows × 2 columns

\n", - "
" - ], - "text/plain": [ - " Country Total Positive Cases\n", - "0 Albania 12279\n", - "1 Argentina 28220\n", - "2 Armenia 7225809\n", - "3 Australia 20654\n", - "4 Austria 541013\n", - ".. ... ...\n", - "112 United States 1837768204\n", - "113 Uruguay 2160\n", - "114 Veneto 119910\n", - "115 Venezuela 461\n", - "116 Vietnam 6450\n", - "\n", - "[117 rows x 2 columns]" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Group the data by 'Country' and calculate the sum of 'Positive Cases'\n", - "total_positive_cases = data.groupby('Country')['Positive Cases'].sum()\n", - "\n", - "# Create a new dataframe with the total positive cases for each country\n", - "df_total_positive_cases = pd.DataFrame({'Country': total_positive_cases.index, 'Total Positive Cases': total_positive_cases.values})\n", - "\n", - "# Display the new dataframe\n", - "df_total_positive_cases\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Sort the dataframe in descending order of the total number of positive cases" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [], - "source": [ - "df_total_positive_cases.sort_values(by='Total Positive Cases', ascending=False, inplace=True)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Display the top ten countries with the most positive cases" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
CountryTotal Positive Cases
112United States1837768204
48Italy64199206
17Canada50960416
7Bangladesh43771939
106Turkey16556127
26Czechia10822251
88Russia10663267
2Armenia7225809
21Costa Rica6655702
111United Kingdom4924956
\n", - "
" - ], - "text/plain": [ - " Country Total Positive Cases\n", - "112 United States 1837768204\n", - "48 Italy 64199206\n", - "17 Canada 50960416\n", - "7 Bangladesh 43771939\n", - "106 Turkey 16556127\n", - "26 Czechia 10822251\n", - "88 Russia 10663267\n", - "2 Armenia 7225809\n", - "21 Costa Rica 6655702\n", - "111 United Kingdom 4924956" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_total_positive_cases.head(10)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Create a new dataframe that contains the total number of tests conducted for each country" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
CountryTotal Tests
0Albania114611
1Argentina323035
2Armenia35667650
3Australia323233309
4Austria7610548
.........
112United States23592177772
113Uruguay51789
114Veneto1650118
115Venezuela395904
116Vietnam3761926
\n", - "

117 rows × 2 columns

\n", - "
" - ], - "text/plain": [ - " Country Total Tests\n", - "0 Albania 114611\n", - "1 Argentina 323035\n", - "2 Armenia 35667650\n", - "3 Australia 323233309\n", - "4 Austria 7610548\n", - ".. ... ...\n", - "112 United States 23592177772\n", - "113 Uruguay 51789\n", - "114 Veneto 1650118\n", - "115 Venezuela 395904\n", - "116 Vietnam 3761926\n", - "\n", - "[117 rows x 2 columns]" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Group the data by 'Country' and calculate the sum of 'Total Tested'\n", - "total_tests = data.groupby('Country')['Total Tested'].sum()\n", - "\n", - "# Create a new dataframe with the total tests conducted for each country\n", - "df_total_tests = pd.DataFrame({'Country': total_tests.index, 'Total Tests': total_tests.values})\n", - "\n", - "# Display the new dataframe\n", - "df_total_tests\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Sort the dataframe in descending order of the total number of tests conducted" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [], - "source": [ - "df_total_tests.sort_values(by='Total Tests', ascending=False, inplace=True)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Display the top ten countries with the most tests conducted" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
CountryTotal Tests
112United States23592177772
17Canada1797986542
48Italy1547472209
47Israel388197039
3Australia323233309
88Russia300300499
7Bangladesh233149800
106Turkey228325949
26Czechia180065598
39Greece142101237
\n", - "
" - ], - "text/plain": [ - " Country Total Tests\n", - "112 United States 23592177772\n", - "17 Canada 1797986542\n", - "48 Italy 1547472209\n", - "47 Israel 388197039\n", - "3 Australia 323233309\n", - "88 Russia 300300499\n", - "7 Bangladesh 233149800\n", - "106 Turkey 228325949\n", - "26 Czechia 180065598\n", - "39 Greece 142101237" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_total_tests.head(10)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Merge the two dataframes created in the previous steps" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
CountryTotal Positive CasesTotal Tests
0United States183776820423592177772
1Italy641992061547472209
2Canada509604161797986542
3Bangladesh43771939233149800
4Turkey16556127228325949
............
112Mozambique1605287
113Myanmar1518460
114Malawi781689
115Grenada42322
116North Korea01449
\n", - "

117 rows × 3 columns

\n", - "
" - ], - "text/plain": [ - " Country Total Positive Cases Total Tests\n", - "0 United States 1837768204 23592177772\n", - "1 Italy 64199206 1547472209\n", - "2 Canada 50960416 1797986542\n", - "3 Bangladesh 43771939 233149800\n", - "4 Turkey 16556127 228325949\n", - ".. ... ... ...\n", - "112 Mozambique 160 5287\n", - "113 Myanmar 151 8460\n", - "114 Malawi 78 1689\n", - "115 Grenada 42 322\n", - "116 North Korea 0 1449\n", - "\n", - "[117 rows x 3 columns]" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "merged_df = pd.merge(df_total_positive_cases, df_total_tests, on='Country')\n", - "merged_df\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Create a new column that contains the ratio of positive cases to the number of tests conducted" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [], - "source": [ - "merged_df['Positive Cases to Tests Ratio'] = merged_df['Total Positive Cases'] / merged_df['Total Tests']\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Sort the dataframe in descending order of the ratio of positive cases to the number of tests conducted" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [], - "source": [ - "merged_df.sort_values(by='Positive Cases to Tests Ratio', ascending=False, inplace=True)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Display the top three countries with the highest ratio of positive cases to the number of tests conducted" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
CountryPositive Cases to Tests Ratio
106Tanzania0.780675
103Burkina Faso0.480870
50Ecuador0.322330
\n", - "
" - ], - "text/plain": [ - " Country Positive Cases to Tests Ratio\n", - "106 Tanzania 0.780675\n", - "103 Burkina Faso 0.480870\n", - "50 Ecuador 0.322330" - ] - }, - "execution_count": 22, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "top_countries = merged_df.nlargest(3, 'Positive Cases to Tests Ratio')\n", - "top_countries[['Country', 'Positive Cases to Tests Ratio']]\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Display the results a chart that shows the top three countries with the highest ratio of positive cases to the number of tests conducted" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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/HMlJiaaLs/lcZjGxcyfP1+33nqrPD09NX/+/HP2HTVqVBVVBfzO399fW7ZsUevWrfX0009r1apVWrlypb766ivdeuutysjIMF0iaqGgoCC9++67GjBggEP7Z599pltuuUU5OTlmCqslOJvGxfwxYBA2UB3ZbDaVlpZKkj799FNdd911kqTQ0FBOOYcxhYWFCg4OPqu9cePGHKapAoyMuLjS0lLt27dP2dnZ9l8AZ/Tv399QVajN/v73vys0NFTR0dG68847tXPnTrVp00aff/65Ro0apYMHD5ouEbXQlVdeqYYNG2rBggXy8vKSJP32228aNWqUjh49qk8//dRwha6NMOLCvv76a40YMUKHDh06686oFotFJSUlhipDbfbNN99o5MiRSk9PV0JCgpKSkiRJ999/v44cOaK33nrLcIWojb799lvFxMSoqKhI3bp1kyRt375dXl5eWrlypf2Ckbg4CCMuLCIiQm3bttWUKVPUpEkTWSwWh+f9/f0NVQac7cSJE3J3d1fdunVNl4JaqrCwUAsXLtTu3bslnb7+zciRI1WvXj3Dlbk+wogL8/Hx0fbt29WmTRvTpQAAUC4msLqwqKgo7du3jzCCaqWkpET//ve/9c477yg9Pf2sa4scPXrUUGWobZYuXVrhvtdff/1FrASEERd2//33KzExUZmZmerSpctZw99du3Y1VBlqsylTpui1115TYmKinnjiCT3++OM6ePCgPvjgA02aNMl0eahFhgwZ4vDYYrGUOb9OEnPsLjIO07gwN7ezr2l35oeNCawwpXXr1po1a5YGDRokX19fbdu2zd729ddfM4EVRnz66acaN26cpk+frj59+kiS1q1bpyeeeELTp0/XVVddZbhC10YYcWGHDh065/PcKA8m+Pj4aNeuXWrevLmaNGmiZcuWqUePHjpw4IC6d++uvLw80yWiFurcubPS0tL0t7/9zaH9yy+/1D//+U/7TR1xcXCYxoURNlAdXXLJJTp8+LCaN2+u1q1b6+OPP1aPHj20ceNGeXp6mi4PtdT+/fsVEBBwVru/vz/XvqkCjIzUAjt37ixzoiATsmDCY489Jj8/P02YMEGLFy/W7bffrrCwMKWnp2vs2LGaMWOG6RJRC/Xv319eXl5644037FdizcrKUmxsrE6cOKHPP//ccIWujTDiwg4cOKChQ4dqx44dDhOzmJAFE0pLS8ucx/T1119r7dq1Cg8P1+DBgw1UBkj79u3T0KFD9f333ys0NFSSlJGRofDwcH3wwQeclXiREUZc2ODBg+Xu7q7XXntNLVu21IYNG3TkyBElJibq2WefVb9+/UyXiFrE3d1dhw8fVuPGjSVJjzzyiMaPH88dpFFt2Gw2ffLJJw4XPYuOjj7rgpGofIQRFxYUFKRVq1apa9eu8vf314YNG9SuXTutWrVKiYmJ2rp1q+kSUYu4ubkpMzPTHkb8/Py0bds2tWrVynBlAExjAqsLKykpka+vr6TTweTnn39Wu3bt1KJFC+3Zs8dwdajt+DsI1U1BQYE+//zzMufYPfDAA4aqqh0IIy6sc+fO2r59u1q2bKmoqCg988wz8vDw0CuvvMJfowDwB1u3btW1116rwsJCFRQUqEGDBsrNzZW3t7caN25MGLnICCMu7IknnlBBQYEkaerUqbruuuvUr18/NWzYUIsXLzZcHWqjSZMmydvbW5JUXFysadOmnXXDxpSUFBOloZYbO3asBg8erLS0NPn7++vrr79W3bp1dfvtt+vBBx80XZ7LY85ILXP06FEFBgYyIQtVbsCAAX/5vbNYLFq1alUVVQT8LiAgQOvXr1e7du0UEBCgdevWqUOHDlq/fr1GjRpln9SKi4ORERe2YMECRUZGqmPHjva2Bg0a6MSJE3rnnXcUGxtrsDrUNqtXrzZdAlCuunXr2k89b9y4sdLT09WhQwf5+/srIyPDcHWu7+yT/uEyRo8eraioKL333nsO7Xl5eYqLizNUFQBUP927d9fGjRslSZdffrkmTZqkhQsX6qGHHlLnzp0NV+f6CCMubsqUKbrjjjs0efJk06UAQLU1ffp0NWnSRJI0bdo0BQYG6t5771VOTo5eeeUVw9W5PuaMuLAz13U4cyXWyy67TG+88Yby8/PVtGlTrsAKAKgWGBlxYWcmC1566aVav3699u3bp759+3LTJwBAtcLIiAv78xUvCwsLNXLkSFmtVhUUFDAyAgD/p2XLluc82+vAgQNVWE3tw9k0LiwpKUn169e3P/b29tb777+vpKQkffHFFwYrA06H47KudNm1a1dDFaE2e+ihhxwenzx5Ulu3btWKFSv0yCOPmCmqFmFkBECVysnJUVxcnP73v/+V+TwjdqhOZs+erU2bNmnu3LmmS3FphBEXt3fvXn322WfKzs5WaWmpvd1isWjixIkGK0NtNXLkSB06dEipqakaMGCA3n//fWVlZempp57Sc889p0GDBpkuEbA7cOCAIiIilJ+fb7oUl8ZhGhf26quv6t5771VQUJBCQkIcjocSRmDKqlWr9OGHHyoyMlJubm5q0aKFrrrqKvn5+Sk5OZkwgmrl3XffVYMGDUyX4fIIIy7sqaee0rRp0zRu3DjTpQB2BQUF9knVgYGBysnJUdu2bdWlSxdt2bLFcHWorbp37+7wB5vNZlNmZqZycnL04osvGqysdiCMuLBjx45p2LBhpssAHLRr10579uxRWFiYunXrppdffllhYWFKS0uzX3QKqGpDhgxxeOzm5qZGjRppwIABat++vZmiahHmjLiwO++8U7169dI999xjuhTA7s0339SpU6c0evRobd68WQMHDtTRo0fl4eGhefPmafjw4aZLBFDFCCMuLDk5WSkpKRo0aJC6dOmiunXrOjz/wAMPGKoM+F1hYaF2796t5s2bKygoyHQ5qKWWL18ud3d3xcTEOLSvXLlSpaWluuaaawxVVjsQRlxYy5Yty33OYrFwER8A+D9du3bVjBkzdO211zq0r1ixQuPGjdP27dsNVVY7EEYAVKmSkhLNmzdPVqv1rFPOpdNn2wBVrV69etq1a5fCwsIc2g8ePKhOnTqpoKDATGG1BBNYAVSpBx98UPPmzdOgQYPUuXPnc16CG6gq/v7+OnDgwFlhZN++ffLx8TFTVC3CyIiL+/HHH7V06dIyL7udkpJiqCrUZkFBQVqwYMFZw+GASXfffbfWrVun999/X61bt5Z0OojcdNNN6tWrl1577TXDFbo2RkZcmNVq1fXXX69WrVpp9+7d6ty5sw4ePCibzaYePXqYLg+1lIeHh9q0aWO6DMDBM888o4EDB6p9+/a65JJLJJ3+Y65fv36aOXOm4epcHyMjLqx379665pprNGXKFPn6+mr79u1q3LixRo4cqYEDB+ree+81XSJqoeeee04HDhzQCy+8wCEaVCs2m02ffvqptm3bpnr16qlr167q37+/6bJqBcKIC/P19dW2bdvUunVrBQYGas2aNerUqZO2b9+uG264QQcPHjRdImqhoUOH6rPPPlODBg3UqVOns045X7JkiaHKUBtde+21evvtt+Xv7y9JmjFjhu655x4FBARIko4cOaJ+/fpp586d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gIAA7dq1y6n9de3a1eFxkyZNlJ2d/ZfrtWjRwiGISNKYMWP08ccf66effpIkzZs3T6NHj5bFYnGqJgBVg5ERAGUKDw+XxWK54Emqbm5uZwWbkydPntWvbt26Do8tFotKS0v/cvtlHULq3r27unXrpgULFujqq6/Wd999p2XLljlZOYCqwsgIgDI1aNBAMTExmj17tgoKCs56/pdfflGHDh2UkZGhjIwMe/vOnTv1yy+/qGPHjpKkRo0a6fDhww7rbtu2zel66tatq5KSkgr3v+uuuzRv3jzNnTtX0dHRDqM3AKoXwgiAcs2ePVslJSXq3bu33nvvPe3du1e7du3SrFmz1KdPH0VHR6tLly4aOXKktmzZog0bNig2NlaXX365IiMjJUl///vftWnTJi1YsEB79+5VUlKSvv32W6drCQsLk9VqVWZmpo4dO/aX/UeMGKEff/xRr776KhNXgWqOMAKgXK1atdKWLVt0xRVXKDExUZ07d9ZVV10lq9Wql156SRaLRR9++KECAwPVv39/RUdHq1WrVlq8eLF9GzExMZo4caIeffRR9erVS7/++qtiY2OdruW5557TJ598otDQUHXv3v0v+/v7++umm25S/fr1zzq1GED1YrFVZJYaANRAV155pTp16qRZs2aZLgXAORBGALicY8eOafXq1br55pu1c+dOtWvXznRJAM6Bs2kAuJzu3bvr2LFjevrppwkiQA3AyAgAADCKCawAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAo/4/36kP45oL7z0AAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "top_countries.plot(x='Country', y='Positive Cases to Tests Ratio', kind='bar')\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Display the results in a chart that shows the top ten countries with the most positive cases" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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/v4qr29paQlLS0vpfmhoKJ4+fYq+ffsqraenp/feK+oSERVX2b/7sosgouLGysoqX/7Ga/ShkdWrV8PX1xdOTk5K7Tdu3ICDgwOMjIxQr149zJw5E+XKlctzO+np6UhPT5fuJycnF1hmIqKCpFAoYG9vDxsbG7x69UruOET5Sl9f/5OP/GTT2ALov//+w59//onffvtNqb1OnTpYt24dKlasiPv372Py5Mlo1KgR/vnnH5ibm+e6rZkzZ0rTdRMRFQe6urr59oeCqDjS2FFg69evh5WVFTp06KDU7u/vjy5dusDDwwN+fn7Yt28fnj17hq1bt+a5rbFjxyIpKUm63b17t4DTExERkZw08giQEAJr1qxBr169YGBg8N51raysUKFCBcTGxua5jqGhIQwNDfM7JhERERVRGnkEKCIiArGxsfj6668/uG5KSgpu3rwJe3v7QkhGREREmkDWAiglJQUxMTGIiYkBAMTFxSEmJgbx8fEA3pyaCggIyPG41atXo06dOqhWrVqOZSNHjkRERARu376NU6dOoWPHjtDV1UWPHj0KdF+IiIhIc8h6CuzcuXPw8fGR7gcGBgIAevfujXXr1uH+/ftSMZQtKSkJO3bswMKFC3Pd5r///osePXrg8ePHKF26NBo2bIjTp0+jdOnSBbcjREREpFEUgnOm55CcnAxLS0skJSXBwsJCpcc4j9lbwKmA27NaF/hzEBERaSp1/n5rZB8gIiIiok/BAoiIiIi0DgsgIiIi0josgIiIiEjrsAAiIiIircMCiIiIiLQOCyAiIiLSOiyAiIiISOuwACIiIiKtwwKIiIiItA4LICIiItI6LICIiIhI67AAIiIiIq3DAoiIiIi0DgsgIiIi0josgIiIiEjrsAAiIiIircMCiIiIiLQOCyAiIiLSOiyAiIiISOuwACIiIiKtwwKIiIiItA4LICIiItI6LICIiIhI67AAIiIiIq3DAoiIiIi0DgsgIiIi0josgIiIiEjrsAAiIiIircMCiIiIiLQOCyAiIiLSOiyAiIiISOuwACIiIiKtwwKIiIiItA4LICIiItI6LICIiIhI67AAIiIiIq0jawF07NgxtG3bFg4ODlAoFAgNDX3v+kePHoVCochxS0hIUFpvyZIlcHZ2hpGREerUqYOzZ88W4F4QERGRppG1AEpNTYWnpyeWLFmi1uOuXbuG+/fvSzcbGxtpWUhICAIDAzFx4kRER0fD09MTfn5+ePDgQX7HJyIiIg2lJ+eT+/v7w9/fX+3H2djYwMrKKtdl8+fPx4ABA9C3b18AwLJly7B3716sWbMGY8aM+ZS4REREVExoZB8gLy8v2Nvbo3nz5jh58qTUnpGRgfPnz8PX11dq09HRga+vLyIjI/PcXnp6OpKTk5VuREREVHxpVAFkb2+PZcuWYceOHdixYwccHR3RtGlTREdHAwAePXqEzMxM2NraKj3O1tY2Rz+ht82cOROWlpbSzdHRsUD3g4iIiOQl6ykwdVWsWBEVK1aU7tevXx83b97EggULsHHjxo/e7tixYxEYGCjdT05OZhFERERUjGlUAZSb2rVr48SJEwCAUqVKQVdXF4mJiUrrJCYmws7OLs9tGBoawtDQsEBzEhERUdGhUafAchMTEwN7e3sAgIGBAby9vREeHi4tz8rKQnh4OOrVqydXRCIiIipiZD0ClJKSgtjYWOl+XFwcYmJiUKJECZQrVw5jx47FvXv3sGHDBgBAcHAwXFxcULVqVbx8+RKrVq3C4cOHceDAAWkbgYGB6N27N2rVqoXatWsjODgYqamp0qgwIiIiIlkLoHPnzsHHx0e6n90Pp3fv3li3bh3u37+P+Ph4aXlGRgZGjBiBe/fuwcTEBB4eHjh06JDSNrp164aHDx8iKCgICQkJ8PLyQlhYWI6O0URERKS9FEIIIXeIoiY5ORmWlpZISkqChYWFSo9xHrO3gFMBt2e1LvDnICIi0lTq/P3W+D5AREREROpiAURERERahwUQERERaR0WQERERKR1WAARERGR1mEBRERERFqHBRARERFpHRZAREREpHVYABEREZHWYQFEREREWocFEBEREWkdFkBERESkdVgAERERkdZhAURERERahwUQERERaR0WQERERKR1WAARERGR1mEBRERERFqHBRARERFpHRZAREREpHVYABEREZHWYQFEREREWocFEBEREWkdFkBERESkdVgAERERkdZhAURERERahwUQERERaR0WQERERKR1WAARERGR1vnkAig5ORmhoaG4cuVKfuQhIiIiKnBqF0Bdu3bF4sWLAQAvXrxArVq10LVrV3h4eGDHjh35HpCIiIgov6ldAB07dgyNGjUCAOzatQtCCDx79gyLFi3CtGnT8j0gERERUX5TuwBK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+/ejSpUqOHv2LDw8PODh4YEzZ86gatWqOHjwYEFkJCIiIspXah8BGjNmDIYPH45Zs2blaP/xxx/RvHnzfAtHREREVBDUPgJ05coVfP311zna+/Xrh8uXL+dLKCIiIqKCpHYBVLp0acTExORoj4mJgY2NTX5kIiIiIipQKp8CmzJlCkaOHIkBAwZg4MCBuHXrFurXrw8AOHnyJGbPno3AwMACC0pERESUX1S+Gryuri7u37+P0qVLIzg4GPPmzcN///0HAHBwcMCoUaMwdOhQKBSKAg1cGHg1eCIiIs1TIFeDz66TFAoFhg8fjuHDh+P58+cAAHNz80+IS0RERFS41BoF9u7RHRY+REREpInUKoAqVKjwwVNcT548+aRARERERAVNrQJo8uTJsLS0LKgsRERERIVCrQKoe/fuHOpOREREGk/leYCKw+guIiIiIkCNAkjF0fJERERERZ7Kp8CysrIKMgcRERFRoVH7UhhEREREmo4FEBEREWkdFkBERESkdVgAERERkdZhAURERERahwUQERERaR0WQERERKR1WAARERGR1mEBRERERFqHBRARERFpHRZAREREpHVYABEREZHWYQFEREREWocFEBEREWkdFkBERESkdYpEAbRkyRI4OzvDyMgIderUwdmzZ/Ncd926dVAoFEo3IyMjpXWEEAgKCoK9vT2MjY3h6+uLGzduFPRuEBERkYaQvQAKCQlBYGAgJk6ciOjoaHh6esLPzw8PHjzI8zEWFha4f/++dLtz547S8jlz5mDRokVYtmwZzpw5A1NTU/j5+eHly5cFvTtERESkAWQvgObPn48BAwagb9++qFKlCpYtWwYTExOsWbMmz8coFArY2dlJN1tbW2mZEALBwcH46aef0L59e3h4eGDDhg3477//EBoaWgh7REREREWdrAVQRkYGzp8/D19fX6lNR0cHvr6+iIyMzPNxKSkpcHJygqOjI9q3b49Lly5Jy+Li4pCQkKC0TUtLS9SpUyfPbaanpyM5OVnpRkRERMWXrAXQo0ePkJmZqXQEBwBsbW2RkJCQ62MqVqyINWvWYPfu3fjf//6HrKws1K9fH//++y8ASI9TZ5szZ86EpaWldHN0dPzUXSMiIqIiTPZTYOqqV68eAgIC4OXlhSZNmmDnzp0oXbo0li9f/tHbHDt2LJKSkqTb3bt38zExERERFTWy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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "df_total_positive_cases.head(10).plot(x='Country', y='Total Positive Cases', kind='bar')\n", - "plt.xlabel('Country')\n", - "plt.ylabel('Total Positive Cases')\n", - "plt.title('Top Ten Countries with the Most Positive Cases')\n", - "plt.show()\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.10" - }, - "orig_nbformat": 4 - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/completesolution/dataengineer/PROMPTS.md b/completesolution/dataengineer/PROMPTS.md deleted file mode 100644 index 5d0f25bf..00000000 --- a/completesolution/dataengineer/PROMPTS.md +++ /dev/null @@ -1,121 +0,0 @@ -# Exercises tips and tricks - -## 1. Understanding the Data - -### 1.1 Import data and show first 5 rows of the dataframe - -Chat prompt: @workspace /newNotebook create a notebook called "COVID19 Worldwide Testing Data" that imports the tested_worldwide.csv at root level and display the first 5 rows - -NOTE: In case you get an error related to "pandas library not found", use copilot to help you fixing it: - -![Pandas error fixed by copilot](images/pandaserror.png) - -### 1.2 Display the number of rows and columns in the dataframe. - -![Display the number of rows and columns in the dataframet](images/1_2.png) - -### 1.3 Display the data types of each column - -![Display the data types of each column](images/1_3.png) - -### 1.4 Display the number of missing values in each column - -![Display the number of missing values in each column](images/1_4.png) - -### 1.5 Display the number of unique values in each column - -![Display the number of unique values in each column](images/1_5.png) - -## 2. Data Cleaning - -### 2.1 Drop the columns that are not needed for the analysis - -![Drop the columns that are not needed for the analysis](images/2_1.png) - -### 2.2 Rename the columns to make them more readable - -![Rename the columns to make them more readable](images/2_2.png) - -### 2.3 Drop the rows that have missing values - -![Drop the rows that have missing values](images/2_3.png) - -### 2.4 Convert the data types of the columns to the appropriate types - -![Convert the data types of the columns to the appropriate types](images/2_4.png) - -Notice that I have first used the chat, then copilot inline to complete the coversion for the column "Country". - -### 2.5 Display the number of missing values in each column - -![Display the number of missing values in each column](images/2_5.png) - -## 3. Extracting the Top Ten Countries with Most Covid-19 Cases - -### 3.1 Create a new dataframe that contains the total number of positive cases for each country - -![Create a new dataframe that contains the total number of positive cases for each country](images/3_1.png) - -### 3.2 Sort the dataframe in descending order of the total number of positive cases - -![Sort the dataframe in descending order of the total number of positive cases](images/3_2.png) - -### 3.3 Display the top ten countries with the most positive cases - -![Display the top ten countries with the most positive cases](images/3_3.png) - -## 4. Identifying the Highest Positive Against Tested Cases - -### 4.1 Create a new dataframe that contains the total number of tests conducted for each country - -![Create a new dataframe that contains the total number of tests conducted for each country](images/4_1.png) - -### 4.2 Sort the dataframe in descending order of the total number of tests conducted - -![Sort the dataframe in descending order of the total number of tests conducted](images/4_2.png) - -### 4.3 Display the top ten countries with the most tests conducted - -![Display the top ten countries with the most tests conducted](images/4_3.png) - -## 5. Identifying top three countries that have had the highest number of positive cases against the number of tests carried out - -### 5.1 Merge the two dataframes created in the previous steps - -![Merge the two dataframes created in the previous steps](images/5_1.png) - -### 5.2 Create a new column that contains the ratio of positive cases to the number of tests conducted - -![Create a new column that contains the ratio of positive cases to the number of tests conducted](images/5_2.png) - -### 5.3 Sort the dataframe in descending order of the ratio of positive cases to the number of tests conducted - -![Sort the dataframe in descending order of the ratio of positive cases to the number of tests conducted](images/5_3.png) - -### 5.4 Display the top three countries with the highest ratio of positive cases to the number of tests conducted - -![Display the top three countries with the highest ratio of positive cases to the number of tests conducted](images/5_4.png) - -## 6. Displaying the Results - -### 6.1 Display the results a chart that shows the top three countries with the highest ratio of positive cases to the number of tests conducted - -![Display the results a chart that shows the top three countries with the highest ratio of positive cases to the number of tests conducted](images/6_1.png) - -NOTE: In case you get an error related to "ImportError with matplotlib", use copilot inline to help you fixing it: - -![Import matplotlib](images/importmatplot.png) - -And then, if the module is not found, use copilot chat to help you fixing it: -![Module not found matplotlib](images/notfoundmatplotlib.png) - - -### 6.2 Display the results in a chart that shows the top ten countries with the most positive cases - -![Display the results in a chart that shows the top ten countries with the most positive cases](images/6_2.png) - -### 6.3 Display the results in a chart that shows the top ten countries with the most tests conducted - -![Display the results in a chart that shows the top ten countries with the most tests conducted](images/6_3.png) - - diff --git a/completesolution/dataengineer/images/1_2.png b/completesolution/dataengineer/images/1_2.png deleted file mode 100644 index fac8c0c9..00000000 Binary files a/completesolution/dataengineer/images/1_2.png and /dev/null differ diff --git a/completesolution/dataengineer/images/1_3.png b/completesolution/dataengineer/images/1_3.png deleted file mode 100644 index f9e75ea6..00000000 Binary files a/completesolution/dataengineer/images/1_3.png and /dev/null differ diff --git a/completesolution/dataengineer/images/1_4.png b/completesolution/dataengineer/images/1_4.png deleted file mode 100644 index f29617b5..00000000 Binary files a/completesolution/dataengineer/images/1_4.png and /dev/null differ diff --git a/completesolution/dataengineer/images/1_5.png b/completesolution/dataengineer/images/1_5.png deleted file mode 100644 index 65c6b038..00000000 Binary files a/completesolution/dataengineer/images/1_5.png and /dev/null differ diff --git a/completesolution/dataengineer/images/2_1.png b/completesolution/dataengineer/images/2_1.png deleted file mode 100644 index f25c29fe..00000000 Binary files a/completesolution/dataengineer/images/2_1.png and /dev/null differ diff --git a/completesolution/dataengineer/images/2_2.png b/completesolution/dataengineer/images/2_2.png deleted file mode 100644 index 6e647225..00000000 Binary files a/completesolution/dataengineer/images/2_2.png and /dev/null differ diff --git a/completesolution/dataengineer/images/2_3.png b/completesolution/dataengineer/images/2_3.png deleted file mode 100644 index 85ececee..00000000 Binary files a/completesolution/dataengineer/images/2_3.png and /dev/null differ diff --git a/completesolution/dataengineer/images/2_4.png b/completesolution/dataengineer/images/2_4.png deleted file mode 100644 index 44db685e..00000000 Binary files a/completesolution/dataengineer/images/2_4.png and /dev/null differ diff --git a/completesolution/dataengineer/images/2_5.png b/completesolution/dataengineer/images/2_5.png deleted file mode 100644 index 15ca0a75..00000000 Binary files a/completesolution/dataengineer/images/2_5.png and /dev/null differ diff --git a/completesolution/dataengineer/images/3_1.png b/completesolution/dataengineer/images/3_1.png deleted file mode 100644 index 900c3a95..00000000 Binary files a/completesolution/dataengineer/images/3_1.png and /dev/null differ diff --git a/completesolution/dataengineer/images/3_2.png b/completesolution/dataengineer/images/3_2.png deleted file mode 100644 index 232b59f6..00000000 Binary files a/completesolution/dataengineer/images/3_2.png and /dev/null differ diff --git a/completesolution/dataengineer/images/3_3.png b/completesolution/dataengineer/images/3_3.png deleted file mode 100644 index affd86d1..00000000 Binary files a/completesolution/dataengineer/images/3_3.png and /dev/null differ diff --git a/completesolution/dataengineer/images/4_1.png b/completesolution/dataengineer/images/4_1.png deleted file mode 100644 index 236b6d53..00000000 Binary files a/completesolution/dataengineer/images/4_1.png and /dev/null differ diff --git a/completesolution/dataengineer/images/4_2.png b/completesolution/dataengineer/images/4_2.png deleted file mode 100644 index 1ba365df..00000000 Binary files a/completesolution/dataengineer/images/4_2.png and /dev/null differ diff --git a/completesolution/dataengineer/images/4_3.png b/completesolution/dataengineer/images/4_3.png deleted file mode 100644 index 402ef4a4..00000000 Binary files a/completesolution/dataengineer/images/4_3.png and /dev/null differ diff --git a/completesolution/dataengineer/images/5_1.png b/completesolution/dataengineer/images/5_1.png deleted file mode 100644 index c3dd18d8..00000000 Binary files a/completesolution/dataengineer/images/5_1.png and /dev/null differ diff --git a/completesolution/dataengineer/images/5_2.png b/completesolution/dataengineer/images/5_2.png deleted file mode 100644 index 41b4bbde..00000000 Binary files a/completesolution/dataengineer/images/5_2.png and /dev/null differ diff --git a/completesolution/dataengineer/images/5_3.png b/completesolution/dataengineer/images/5_3.png deleted file mode 100644 index 107913d2..00000000 Binary files a/completesolution/dataengineer/images/5_3.png and /dev/null differ diff --git a/completesolution/dataengineer/images/5_4.png b/completesolution/dataengineer/images/5_4.png deleted file mode 100644 index 9e949b21..00000000 Binary files a/completesolution/dataengineer/images/5_4.png and /dev/null differ diff --git a/completesolution/dataengineer/images/6_1.png b/completesolution/dataengineer/images/6_1.png deleted file mode 100644 index 6f247644..00000000 Binary files a/completesolution/dataengineer/images/6_1.png and /dev/null differ diff --git a/completesolution/dataengineer/images/6_2.png b/completesolution/dataengineer/images/6_2.png deleted file mode 100644 index b72ed758..00000000 Binary files a/completesolution/dataengineer/images/6_2.png and /dev/null differ diff --git a/completesolution/dataengineer/images/6_3.png b/completesolution/dataengineer/images/6_3.png deleted file mode 100644 index e501fedf..00000000 Binary files a/completesolution/dataengineer/images/6_3.png and /dev/null differ diff --git a/completesolution/dataengineer/images/importmatplot.png b/completesolution/dataengineer/images/importmatplot.png deleted file mode 100644 index 3002957e..00000000 Binary files a/completesolution/dataengineer/images/importmatplot.png and /dev/null differ diff --git a/completesolution/dataengineer/images/notfoundmatplotlib.png b/completesolution/dataengineer/images/notfoundmatplotlib.png deleted file mode 100644 index af26c95c..00000000 Binary files a/completesolution/dataengineer/images/notfoundmatplotlib.png and /dev/null differ diff --git a/completesolution/dataengineer/images/pandaserror.png b/completesolution/dataengineer/images/pandaserror.png deleted file mode 100644 index cd010c62..00000000 Binary files a/completesolution/dataengineer/images/pandaserror.png and /dev/null differ diff --git a/completesolution/datascientist/diabetes.csv b/completesolution/datascientist/diabetes.csv deleted file mode 100644 index dcaf5fe8..00000000 --- a/completesolution/datascientist/diabetes.csv +++ /dev/null @@ -1,769 +0,0 @@ -Pregnancies,Glucose,BloodPressure,SkinThickness,Insulin,BMI,DiabetesPedigreeFunction,Age,Outcome -6,148,72,35,0,33.6,0.627,50,1 -1,85,66,29,0,26.6,0.351,31,0 -8,183,64,0,0,23.3,0.672,32,1 -1,89,66,23,94,28.1,0.167,21,0 -0,137,40,35,168,43.1,2.288,33,1 -5,116,74,0,0,25.6,0.201,30,0 -3,78,50,32,88,31,0.248,26,1 -10,115,0,0,0,35.3,0.134,29,0 -2,197,70,45,543,30.5,0.158,53,1 -8,125,96,0,0,0,0.232,54,1 -4,110,92,0,0,37.6,0.191,30,0 -10,168,74,0,0,38,0.537,34,1 -10,139,80,0,0,27.1,1.441,57,0 -1,189,60,23,846,30.1,0.398,59,1 -5,166,72,19,175,25.8,0.587,51,1 -7,100,0,0,0,30,0.484,32,1 -0,118,84,47,230,45.8,0.551,31,1 -7,107,74,0,0,29.6,0.254,31,1 -1,103,30,38,83,43.3,0.183,33,0 -1,115,70,30,96,34.6,0.529,32,1 -3,126,88,41,235,39.3,0.704,27,0 -8,99,84,0,0,35.4,0.388,50,0 -7,196,90,0,0,39.8,0.451,41,1 -9,119,80,35,0,29,0.263,29,1 -11,143,94,33,146,36.6,0.254,51,1 -10,125,70,26,115,31.1,0.205,41,1 -7,147,76,0,0,39.4,0.257,43,1 -1,97,66,15,140,23.2,0.487,22,0 -13,145,82,19,110,22.2,0.245,57,0 -5,117,92,0,0,34.1,0.337,38,0 -5,109,75,26,0,36,0.546,60,0 -3,158,76,36,245,31.6,0.851,28,1 -3,88,58,11,54,24.8,0.267,22,0 -6,92,92,0,0,19.9,0.188,28,0 -10,122,78,31,0,27.6,0.512,45,0 -4,103,60,33,192,24,0.966,33,0 -11,138,76,0,0,33.2,0.42,35,0 -9,102,76,37,0,32.9,0.665,46,1 -2,90,68,42,0,38.2,0.503,27,1 -4,111,72,47,207,37.1,1.39,56,1 -3,180,64,25,70,34,0.271,26,0 -7,133,84,0,0,40.2,0.696,37,0 -7,106,92,18,0,22.7,0.235,48,0 -9,171,110,24,240,45.4,0.721,54,1 -7,159,64,0,0,27.4,0.294,40,0 -0,180,66,39,0,42,1.893,25,1 -1,146,56,0,0,29.7,0.564,29,0 -2,71,70,27,0,28,0.586,22,0 -7,103,66,32,0,39.1,0.344,31,1 -7,105,0,0,0,0,0.305,24,0 -1,103,80,11,82,19.4,0.491,22,0 -1,101,50,15,36,24.2,0.526,26,0 -5,88,66,21,23,24.4,0.342,30,0 -8,176,90,34,300,33.7,0.467,58,1 -7,150,66,42,342,34.7,0.718,42,0 -1,73,50,10,0,23,0.248,21,0 -7,187,68,39,304,37.7,0.254,41,1 -0,100,88,60,110,46.8,0.962,31,0 -0,146,82,0,0,40.5,1.781,44,0 -0,105,64,41,142,41.5,0.173,22,0 -2,84,0,0,0,0,0.304,21,0 -8,133,72,0,0,32.9,0.27,39,1 -5,44,62,0,0,25,0.587,36,0 -2,141,58,34,128,25.4,0.699,24,0 -7,114,66,0,0,32.8,0.258,42,1 -5,99,74,27,0,29,0.203,32,0 -0,109,88,30,0,32.5,0.855,38,1 -2,109,92,0,0,42.7,0.845,54,0 -1,95,66,13,38,19.6,0.334,25,0 -4,146,85,27,100,28.9,0.189,27,0 -2,100,66,20,90,32.9,0.867,28,1 -5,139,64,35,140,28.6,0.411,26,0 -13,126,90,0,0,43.4,0.583,42,1 -4,129,86,20,270,35.1,0.231,23,0 -1,79,75,30,0,32,0.396,22,0 -1,0,48,20,0,24.7,0.14,22,0 -7,62,78,0,0,32.6,0.391,41,0 -5,95,72,33,0,37.7,0.37,27,0 -0,131,0,0,0,43.2,0.27,26,1 -2,112,66,22,0,25,0.307,24,0 -3,113,44,13,0,22.4,0.14,22,0 -2,74,0,0,0,0,0.102,22,0 -7,83,78,26,71,29.3,0.767,36,0 -0,101,65,28,0,24.6,0.237,22,0 -5,137,108,0,0,48.8,0.227,37,1 -2,110,74,29,125,32.4,0.698,27,0 -13,106,72,54,0,36.6,0.178,45,0 -2,100,68,25,71,38.5,0.324,26,0 -15,136,70,32,110,37.1,0.153,43,1 -1,107,68,19,0,26.5,0.165,24,0 -1,80,55,0,0,19.1,0.258,21,0 -4,123,80,15,176,32,0.443,34,0 -7,81,78,40,48,46.7,0.261,42,0 -4,134,72,0,0,23.8,0.277,60,1 -2,142,82,18,64,24.7,0.761,21,0 -6,144,72,27,228,33.9,0.255,40,0 -2,92,62,28,0,31.6,0.13,24,0 -1,71,48,18,76,20.4,0.323,22,0 -6,93,50,30,64,28.7,0.356,23,0 -1,122,90,51,220,49.7,0.325,31,1 -1,163,72,0,0,39,1.222,33,1 -1,151,60,0,0,26.1,0.179,22,0 -0,125,96,0,0,22.5,0.262,21,0 -1,81,72,18,40,26.6,0.283,24,0 -2,85,65,0,0,39.6,0.93,27,0 -1,126,56,29,152,28.7,0.801,21,0 -1,96,122,0,0,22.4,0.207,27,0 -4,144,58,28,140,29.5,0.287,37,0 -3,83,58,31,18,34.3,0.336,25,0 -0,95,85,25,36,37.4,0.247,24,1 -3,171,72,33,135,33.3,0.199,24,1 -8,155,62,26,495,34,0.543,46,1 -1,89,76,34,37,31.2,0.192,23,0 -4,76,62,0,0,34,0.391,25,0 -7,160,54,32,175,30.5,0.588,39,1 -4,146,92,0,0,31.2,0.539,61,1 -5,124,74,0,0,34,0.22,38,1 -5,78,48,0,0,33.7,0.654,25,0 -4,97,60,23,0,28.2,0.443,22,0 -4,99,76,15,51,23.2,0.223,21,0 -0,162,76,56,100,53.2,0.759,25,1 -6,111,64,39,0,34.2,0.26,24,0 -2,107,74,30,100,33.6,0.404,23,0 -5,132,80,0,0,26.8,0.186,69,0 -0,113,76,0,0,33.3,0.278,23,1 -1,88,30,42,99,55,0.496,26,1 -3,120,70,30,135,42.9,0.452,30,0 -1,118,58,36,94,33.3,0.261,23,0 -1,117,88,24,145,34.5,0.403,40,1 -0,105,84,0,0,27.9,0.741,62,1 -4,173,70,14,168,29.7,0.361,33,1 -9,122,56,0,0,33.3,1.114,33,1 -3,170,64,37,225,34.5,0.356,30,1 -8,84,74,31,0,38.3,0.457,39,0 -2,96,68,13,49,21.1,0.647,26,0 -2,125,60,20,140,33.8,0.088,31,0 -0,100,70,26,50,30.8,0.597,21,0 -0,93,60,25,92,28.7,0.532,22,0 -0,129,80,0,0,31.2,0.703,29,0 -5,105,72,29,325,36.9,0.159,28,0 -3,128,78,0,0,21.1,0.268,55,0 -5,106,82,30,0,39.5,0.286,38,0 -2,108,52,26,63,32.5,0.318,22,0 -10,108,66,0,0,32.4,0.272,42,1 -4,154,62,31,284,32.8,0.237,23,0 -0,102,75,23,0,0,0.572,21,0 -9,57,80,37,0,32.8,0.096,41,0 -2,106,64,35,119,30.5,1.4,34,0 -5,147,78,0,0,33.7,0.218,65,0 -2,90,70,17,0,27.3,0.085,22,0 -1,136,74,50,204,37.4,0.399,24,0 -4,114,65,0,0,21.9,0.432,37,0 -9,156,86,28,155,34.3,1.189,42,1 -1,153,82,42,485,40.6,0.687,23,0 -8,188,78,0,0,47.9,0.137,43,1 -7,152,88,44,0,50,0.337,36,1 -2,99,52,15,94,24.6,0.637,21,0 -1,109,56,21,135,25.2,0.833,23,0 -2,88,74,19,53,29,0.229,22,0 -17,163,72,41,114,40.9,0.817,47,1 -4,151,90,38,0,29.7,0.294,36,0 -7,102,74,40,105,37.2,0.204,45,0 -0,114,80,34,285,44.2,0.167,27,0 -2,100,64,23,0,29.7,0.368,21,0 -0,131,88,0,0,31.6,0.743,32,1 -6,104,74,18,156,29.9,0.722,41,1 -3,148,66,25,0,32.5,0.256,22,0 -4,120,68,0,0,29.6,0.709,34,0 -4,110,66,0,0,31.9,0.471,29,0 -3,111,90,12,78,28.4,0.495,29,0 -6,102,82,0,0,30.8,0.18,36,1 -6,134,70,23,130,35.4,0.542,29,1 -2,87,0,23,0,28.9,0.773,25,0 -1,79,60,42,48,43.5,0.678,23,0 -2,75,64,24,55,29.7,0.37,33,0 -8,179,72,42,130,32.7,0.719,36,1 -6,85,78,0,0,31.2,0.382,42,0 -0,129,110,46,130,67.1,0.319,26,1 -5,143,78,0,0,45,0.19,47,0 -5,130,82,0,0,39.1,0.956,37,1 -6,87,80,0,0,23.2,0.084,32,0 -0,119,64,18,92,34.9,0.725,23,0 -1,0,74,20,23,27.7,0.299,21,0 -5,73,60,0,0,26.8,0.268,27,0 -4,141,74,0,0,27.6,0.244,40,0 -7,194,68,28,0,35.9,0.745,41,1 -8,181,68,36,495,30.1,0.615,60,1 -1,128,98,41,58,32,1.321,33,1 -8,109,76,39,114,27.9,0.64,31,1 -5,139,80,35,160,31.6,0.361,25,1 -3,111,62,0,0,22.6,0.142,21,0 -9,123,70,44,94,33.1,0.374,40,0 -7,159,66,0,0,30.4,0.383,36,1 -11,135,0,0,0,52.3,0.578,40,1 -8,85,55,20,0,24.4,0.136,42,0 -5,158,84,41,210,39.4,0.395,29,1 -1,105,58,0,0,24.3,0.187,21,0 -3,107,62,13,48,22.9,0.678,23,1 -4,109,64,44,99,34.8,0.905,26,1 -4,148,60,27,318,30.9,0.15,29,1 -0,113,80,16,0,31,0.874,21,0 -1,138,82,0,0,40.1,0.236,28,0 -0,108,68,20,0,27.3,0.787,32,0 -2,99,70,16,44,20.4,0.235,27,0 -6,103,72,32,190,37.7,0.324,55,0 -5,111,72,28,0,23.9,0.407,27,0 -8,196,76,29,280,37.5,0.605,57,1 -5,162,104,0,0,37.7,0.151,52,1 -1,96,64,27,87,33.2,0.289,21,0 -7,184,84,33,0,35.5,0.355,41,1 -2,81,60,22,0,27.7,0.29,25,0 -0,147,85,54,0,42.8,0.375,24,0 -7,179,95,31,0,34.2,0.164,60,0 -0,140,65,26,130,42.6,0.431,24,1 -9,112,82,32,175,34.2,0.26,36,1 -12,151,70,40,271,41.8,0.742,38,1 -5,109,62,41,129,35.8,0.514,25,1 -6,125,68,30,120,30,0.464,32,0 -5,85,74,22,0,29,1.224,32,1 -5,112,66,0,0,37.8,0.261,41,1 -0,177,60,29,478,34.6,1.072,21,1 -2,158,90,0,0,31.6,0.805,66,1 -7,119,0,0,0,25.2,0.209,37,0 -7,142,60,33,190,28.8,0.687,61,0 -1,100,66,15,56,23.6,0.666,26,0 -1,87,78,27,32,34.6,0.101,22,0 -0,101,76,0,0,35.7,0.198,26,0 -3,162,52,38,0,37.2,0.652,24,1 -4,197,70,39,744,36.7,2.329,31,0 -0,117,80,31,53,45.2,0.089,24,0 -4,142,86,0,0,44,0.645,22,1 -6,134,80,37,370,46.2,0.238,46,1 -1,79,80,25,37,25.4,0.583,22,0 -4,122,68,0,0,35,0.394,29,0 -3,74,68,28,45,29.7,0.293,23,0 -4,171,72,0,0,43.6,0.479,26,1 -7,181,84,21,192,35.9,0.586,51,1 -0,179,90,27,0,44.1,0.686,23,1 -9,164,84,21,0,30.8,0.831,32,1 -0,104,76,0,0,18.4,0.582,27,0 -1,91,64,24,0,29.2,0.192,21,0 -4,91,70,32,88,33.1,0.446,22,0 -3,139,54,0,0,25.6,0.402,22,1 -6,119,50,22,176,27.1,1.318,33,1 -2,146,76,35,194,38.2,0.329,29,0 -9,184,85,15,0,30,1.213,49,1 -10,122,68,0,0,31.2,0.258,41,0 -0,165,90,33,680,52.3,0.427,23,0 -9,124,70,33,402,35.4,0.282,34,0 -1,111,86,19,0,30.1,0.143,23,0 -9,106,52,0,0,31.2,0.38,42,0 -2,129,84,0,0,28,0.284,27,0 -2,90,80,14,55,24.4,0.249,24,0 -0,86,68,32,0,35.8,0.238,25,0 -12,92,62,7,258,27.6,0.926,44,1 -1,113,64,35,0,33.6,0.543,21,1 -3,111,56,39,0,30.1,0.557,30,0 -2,114,68,22,0,28.7,0.092,25,0 -1,193,50,16,375,25.9,0.655,24,0 -11,155,76,28,150,33.3,1.353,51,1 -3,191,68,15,130,30.9,0.299,34,0 -3,141,0,0,0,30,0.761,27,1 -4,95,70,32,0,32.1,0.612,24,0 -3,142,80,15,0,32.4,0.2,63,0 -4,123,62,0,0,32,0.226,35,1 -5,96,74,18,67,33.6,0.997,43,0 -0,138,0,0,0,36.3,0.933,25,1 -2,128,64,42,0,40,1.101,24,0 -0,102,52,0,0,25.1,0.078,21,0 -2,146,0,0,0,27.5,0.24,28,1 -10,101,86,37,0,45.6,1.136,38,1 -2,108,62,32,56,25.2,0.128,21,0 -3,122,78,0,0,23,0.254,40,0 -1,71,78,50,45,33.2,0.422,21,0 -13,106,70,0,0,34.2,0.251,52,0 -2,100,70,52,57,40.5,0.677,25,0 -7,106,60,24,0,26.5,0.296,29,1 -0,104,64,23,116,27.8,0.454,23,0 -5,114,74,0,0,24.9,0.744,57,0 -2,108,62,10,278,25.3,0.881,22,0 -0,146,70,0,0,37.9,0.334,28,1 -10,129,76,28,122,35.9,0.28,39,0 -7,133,88,15,155,32.4,0.262,37,0 -7,161,86,0,0,30.4,0.165,47,1 -2,108,80,0,0,27,0.259,52,1 -7,136,74,26,135,26,0.647,51,0 -5,155,84,44,545,38.7,0.619,34,0 -1,119,86,39,220,45.6,0.808,29,1 -4,96,56,17,49,20.8,0.34,26,0 -5,108,72,43,75,36.1,0.263,33,0 -0,78,88,29,40,36.9,0.434,21,0 -0,107,62,30,74,36.6,0.757,25,1 -2,128,78,37,182,43.3,1.224,31,1 -1,128,48,45,194,40.5,0.613,24,1 -0,161,50,0,0,21.9,0.254,65,0 -6,151,62,31,120,35.5,0.692,28,0 -2,146,70,38,360,28,0.337,29,1 -0,126,84,29,215,30.7,0.52,24,0 -14,100,78,25,184,36.6,0.412,46,1 -8,112,72,0,0,23.6,0.84,58,0 -0,167,0,0,0,32.3,0.839,30,1 -2,144,58,33,135,31.6,0.422,25,1 -5,77,82,41,42,35.8,0.156,35,0 -5,115,98,0,0,52.9,0.209,28,1 -3,150,76,0,0,21,0.207,37,0 -2,120,76,37,105,39.7,0.215,29,0 -10,161,68,23,132,25.5,0.326,47,1 -0,137,68,14,148,24.8,0.143,21,0 -0,128,68,19,180,30.5,1.391,25,1 -2,124,68,28,205,32.9,0.875,30,1 -6,80,66,30,0,26.2,0.313,41,0 -0,106,70,37,148,39.4,0.605,22,0 -2,155,74,17,96,26.6,0.433,27,1 -3,113,50,10,85,29.5,0.626,25,0 -7,109,80,31,0,35.9,1.127,43,1 -2,112,68,22,94,34.1,0.315,26,0 -3,99,80,11,64,19.3,0.284,30,0 -3,182,74,0,0,30.5,0.345,29,1 -3,115,66,39,140,38.1,0.15,28,0 -6,194,78,0,0,23.5,0.129,59,1 -4,129,60,12,231,27.5,0.527,31,0 -3,112,74,30,0,31.6,0.197,25,1 -0,124,70,20,0,27.4,0.254,36,1 -13,152,90,33,29,26.8,0.731,43,1 -2,112,75,32,0,35.7,0.148,21,0 -1,157,72,21,168,25.6,0.123,24,0 -1,122,64,32,156,35.1,0.692,30,1 -10,179,70,0,0,35.1,0.2,37,0 -2,102,86,36,120,45.5,0.127,23,1 -6,105,70,32,68,30.8,0.122,37,0 -8,118,72,19,0,23.1,1.476,46,0 -2,87,58,16,52,32.7,0.166,25,0 -1,180,0,0,0,43.3,0.282,41,1 -12,106,80,0,0,23.6,0.137,44,0 -1,95,60,18,58,23.9,0.26,22,0 -0,165,76,43,255,47.9,0.259,26,0 -0,117,0,0,0,33.8,0.932,44,0 -5,115,76,0,0,31.2,0.343,44,1 -9,152,78,34,171,34.2,0.893,33,1 -7,178,84,0,0,39.9,0.331,41,1 -1,130,70,13,105,25.9,0.472,22,0 -1,95,74,21,73,25.9,0.673,36,0 -1,0,68,35,0,32,0.389,22,0 -5,122,86,0,0,34.7,0.29,33,0 -8,95,72,0,0,36.8,0.485,57,0 -8,126,88,36,108,38.5,0.349,49,0 -1,139,46,19,83,28.7,0.654,22,0 -3,116,0,0,0,23.5,0.187,23,0 -3,99,62,19,74,21.8,0.279,26,0 -5,0,80,32,0,41,0.346,37,1 -4,92,80,0,0,42.2,0.237,29,0 -4,137,84,0,0,31.2,0.252,30,0 -3,61,82,28,0,34.4,0.243,46,0 -1,90,62,12,43,27.2,0.58,24,0 -3,90,78,0,0,42.7,0.559,21,0 -9,165,88,0,0,30.4,0.302,49,1 -1,125,50,40,167,33.3,0.962,28,1 -13,129,0,30,0,39.9,0.569,44,1 -12,88,74,40,54,35.3,0.378,48,0 -1,196,76,36,249,36.5,0.875,29,1 -5,189,64,33,325,31.2,0.583,29,1 -5,158,70,0,0,29.8,0.207,63,0 -5,103,108,37,0,39.2,0.305,65,0 -4,146,78,0,0,38.5,0.52,67,1 -4,147,74,25,293,34.9,0.385,30,0 -5,99,54,28,83,34,0.499,30,0 -6,124,72,0,0,27.6,0.368,29,1 -0,101,64,17,0,21,0.252,21,0 -3,81,86,16,66,27.5,0.306,22,0 -1,133,102,28,140,32.8,0.234,45,1 -3,173,82,48,465,38.4,2.137,25,1 -0,118,64,23,89,0,1.731,21,0 -0,84,64,22,66,35.8,0.545,21,0 -2,105,58,40,94,34.9,0.225,25,0 -2,122,52,43,158,36.2,0.816,28,0 -12,140,82,43,325,39.2,0.528,58,1 -0,98,82,15,84,25.2,0.299,22,0 -1,87,60,37,75,37.2,0.509,22,0 -4,156,75,0,0,48.3,0.238,32,1 -0,93,100,39,72,43.4,1.021,35,0 -1,107,72,30,82,30.8,0.821,24,0 -0,105,68,22,0,20,0.236,22,0 -1,109,60,8,182,25.4,0.947,21,0 -1,90,62,18,59,25.1,1.268,25,0 -1,125,70,24,110,24.3,0.221,25,0 -1,119,54,13,50,22.3,0.205,24,0 -5,116,74,29,0,32.3,0.66,35,1 -8,105,100,36,0,43.3,0.239,45,1 -5,144,82,26,285,32,0.452,58,1 -3,100,68,23,81,31.6,0.949,28,0 -1,100,66,29,196,32,0.444,42,0 -5,166,76,0,0,45.7,0.34,27,1 -1,131,64,14,415,23.7,0.389,21,0 -4,116,72,12,87,22.1,0.463,37,0 -4,158,78,0,0,32.9,0.803,31,1 -2,127,58,24,275,27.7,1.6,25,0 -3,96,56,34,115,24.7,0.944,39,0 -0,131,66,40,0,34.3,0.196,22,1 -3,82,70,0,0,21.1,0.389,25,0 -3,193,70,31,0,34.9,0.241,25,1 -4,95,64,0,0,32,0.161,31,1 -6,137,61,0,0,24.2,0.151,55,0 -5,136,84,41,88,35,0.286,35,1 -9,72,78,25,0,31.6,0.28,38,0 -5,168,64,0,0,32.9,0.135,41,1 -2,123,48,32,165,42.1,0.52,26,0 -4,115,72,0,0,28.9,0.376,46,1 -0,101,62,0,0,21.9,0.336,25,0 -8,197,74,0,0,25.9,1.191,39,1 -1,172,68,49,579,42.4,0.702,28,1 -6,102,90,39,0,35.7,0.674,28,0 -1,112,72,30,176,34.4,0.528,25,0 -1,143,84,23,310,42.4,1.076,22,0 -1,143,74,22,61,26.2,0.256,21,0 -0,138,60,35,167,34.6,0.534,21,1 -3,173,84,33,474,35.7,0.258,22,1 -1,97,68,21,0,27.2,1.095,22,0 -4,144,82,32,0,38.5,0.554,37,1 -1,83,68,0,0,18.2,0.624,27,0 -3,129,64,29,115,26.4,0.219,28,1 -1,119,88,41,170,45.3,0.507,26,0 -2,94,68,18,76,26,0.561,21,0 -0,102,64,46,78,40.6,0.496,21,0 -2,115,64,22,0,30.8,0.421,21,0 -8,151,78,32,210,42.9,0.516,36,1 -4,184,78,39,277,37,0.264,31,1 -0,94,0,0,0,0,0.256,25,0 -1,181,64,30,180,34.1,0.328,38,1 -0,135,94,46,145,40.6,0.284,26,0 -1,95,82,25,180,35,0.233,43,1 -2,99,0,0,0,22.2,0.108,23,0 -3,89,74,16,85,30.4,0.551,38,0 -1,80,74,11,60,30,0.527,22,0 -2,139,75,0,0,25.6,0.167,29,0 -1,90,68,8,0,24.5,1.138,36,0 -0,141,0,0,0,42.4,0.205,29,1 -12,140,85,33,0,37.4,0.244,41,0 -5,147,75,0,0,29.9,0.434,28,0 -1,97,70,15,0,18.2,0.147,21,0 -6,107,88,0,0,36.8,0.727,31,0 -0,189,104,25,0,34.3,0.435,41,1 -2,83,66,23,50,32.2,0.497,22,0 -4,117,64,27,120,33.2,0.23,24,0 -8,108,70,0,0,30.5,0.955,33,1 -4,117,62,12,0,29.7,0.38,30,1 -0,180,78,63,14,59.4,2.42,25,1 -1,100,72,12,70,25.3,0.658,28,0 -0,95,80,45,92,36.5,0.33,26,0 -0,104,64,37,64,33.6,0.51,22,1 -0,120,74,18,63,30.5,0.285,26,0 -1,82,64,13,95,21.2,0.415,23,0 -2,134,70,0,0,28.9,0.542,23,1 -0,91,68,32,210,39.9,0.381,25,0 -2,119,0,0,0,19.6,0.832,72,0 -2,100,54,28,105,37.8,0.498,24,0 -14,175,62,30,0,33.6,0.212,38,1 -1,135,54,0,0,26.7,0.687,62,0 -5,86,68,28,71,30.2,0.364,24,0 -10,148,84,48,237,37.6,1.001,51,1 -9,134,74,33,60,25.9,0.46,81,0 -9,120,72,22,56,20.8,0.733,48,0 -1,71,62,0,0,21.8,0.416,26,0 -8,74,70,40,49,35.3,0.705,39,0 -5,88,78,30,0,27.6,0.258,37,0 -10,115,98,0,0,24,1.022,34,0 -0,124,56,13,105,21.8,0.452,21,0 -0,74,52,10,36,27.8,0.269,22,0 -0,97,64,36,100,36.8,0.6,25,0 -8,120,0,0,0,30,0.183,38,1 -6,154,78,41,140,46.1,0.571,27,0 -1,144,82,40,0,41.3,0.607,28,0 -0,137,70,38,0,33.2,0.17,22,0 -0,119,66,27,0,38.8,0.259,22,0 -7,136,90,0,0,29.9,0.21,50,0 -4,114,64,0,0,28.9,0.126,24,0 -0,137,84,27,0,27.3,0.231,59,0 -2,105,80,45,191,33.7,0.711,29,1 -7,114,76,17,110,23.8,0.466,31,0 -8,126,74,38,75,25.9,0.162,39,0 -4,132,86,31,0,28,0.419,63,0 -3,158,70,30,328,35.5,0.344,35,1 -0,123,88,37,0,35.2,0.197,29,0 -4,85,58,22,49,27.8,0.306,28,0 -0,84,82,31,125,38.2,0.233,23,0 -0,145,0,0,0,44.2,0.63,31,1 -0,135,68,42,250,42.3,0.365,24,1 -1,139,62,41,480,40.7,0.536,21,0 -0,173,78,32,265,46.5,1.159,58,0 -4,99,72,17,0,25.6,0.294,28,0 -8,194,80,0,0,26.1,0.551,67,0 -2,83,65,28,66,36.8,0.629,24,0 -2,89,90,30,0,33.5,0.292,42,0 -4,99,68,38,0,32.8,0.145,33,0 -4,125,70,18,122,28.9,1.144,45,1 -3,80,0,0,0,0,0.174,22,0 -6,166,74,0,0,26.6,0.304,66,0 -5,110,68,0,0,26,0.292,30,0 -2,81,72,15,76,30.1,0.547,25,0 -7,195,70,33,145,25.1,0.163,55,1 -6,154,74,32,193,29.3,0.839,39,0 -2,117,90,19,71,25.2,0.313,21,0 -3,84,72,32,0,37.2,0.267,28,0 -6,0,68,41,0,39,0.727,41,1 -7,94,64,25,79,33.3,0.738,41,0 -3,96,78,39,0,37.3,0.238,40,0 -10,75,82,0,0,33.3,0.263,38,0 -0,180,90,26,90,36.5,0.314,35,1 -1,130,60,23,170,28.6,0.692,21,0 -2,84,50,23,76,30.4,0.968,21,0 -8,120,78,0,0,25,0.409,64,0 -12,84,72,31,0,29.7,0.297,46,1 -0,139,62,17,210,22.1,0.207,21,0 -9,91,68,0,0,24.2,0.2,58,0 -2,91,62,0,0,27.3,0.525,22,0 -3,99,54,19,86,25.6,0.154,24,0 -3,163,70,18,105,31.6,0.268,28,1 -9,145,88,34,165,30.3,0.771,53,1 -7,125,86,0,0,37.6,0.304,51,0 -13,76,60,0,0,32.8,0.18,41,0 -6,129,90,7,326,19.6,0.582,60,0 -2,68,70,32,66,25,0.187,25,0 -3,124,80,33,130,33.2,0.305,26,0 -6,114,0,0,0,0,0.189,26,0 -9,130,70,0,0,34.2,0.652,45,1 -3,125,58,0,0,31.6,0.151,24,0 -3,87,60,18,0,21.8,0.444,21,0 -1,97,64,19,82,18.2,0.299,21,0 -3,116,74,15,105,26.3,0.107,24,0 -0,117,66,31,188,30.8,0.493,22,0 -0,111,65,0,0,24.6,0.66,31,0 -2,122,60,18,106,29.8,0.717,22,0 -0,107,76,0,0,45.3,0.686,24,0 -1,86,66,52,65,41.3,0.917,29,0 -6,91,0,0,0,29.8,0.501,31,0 -1,77,56,30,56,33.3,1.251,24,0 -4,132,0,0,0,32.9,0.302,23,1 -0,105,90,0,0,29.6,0.197,46,0 -0,57,60,0,0,21.7,0.735,67,0 -0,127,80,37,210,36.3,0.804,23,0 -3,129,92,49,155,36.4,0.968,32,1 -8,100,74,40,215,39.4,0.661,43,1 -3,128,72,25,190,32.4,0.549,27,1 -10,90,85,32,0,34.9,0.825,56,1 -4,84,90,23,56,39.5,0.159,25,0 -1,88,78,29,76,32,0.365,29,0 -8,186,90,35,225,34.5,0.423,37,1 -5,187,76,27,207,43.6,1.034,53,1 -4,131,68,21,166,33.1,0.16,28,0 -1,164,82,43,67,32.8,0.341,50,0 -4,189,110,31,0,28.5,0.68,37,0 -1,116,70,28,0,27.4,0.204,21,0 -3,84,68,30,106,31.9,0.591,25,0 -6,114,88,0,0,27.8,0.247,66,0 -1,88,62,24,44,29.9,0.422,23,0 -1,84,64,23,115,36.9,0.471,28,0 -7,124,70,33,215,25.5,0.161,37,0 -1,97,70,40,0,38.1,0.218,30,0 -8,110,76,0,0,27.8,0.237,58,0 -11,103,68,40,0,46.2,0.126,42,0 -11,85,74,0,0,30.1,0.3,35,0 -6,125,76,0,0,33.8,0.121,54,1 -0,198,66,32,274,41.3,0.502,28,1 -1,87,68,34,77,37.6,0.401,24,0 -6,99,60,19,54,26.9,0.497,32,0 -0,91,80,0,0,32.4,0.601,27,0 -2,95,54,14,88,26.1,0.748,22,0 -1,99,72,30,18,38.6,0.412,21,0 -6,92,62,32,126,32,0.085,46,0 -4,154,72,29,126,31.3,0.338,37,0 -0,121,66,30,165,34.3,0.203,33,1 -3,78,70,0,0,32.5,0.27,39,0 -2,130,96,0,0,22.6,0.268,21,0 -3,111,58,31,44,29.5,0.43,22,0 -2,98,60,17,120,34.7,0.198,22,0 -1,143,86,30,330,30.1,0.892,23,0 -1,119,44,47,63,35.5,0.28,25,0 -6,108,44,20,130,24,0.813,35,0 -2,118,80,0,0,42.9,0.693,21,1 -10,133,68,0,0,27,0.245,36,0 -2,197,70,99,0,34.7,0.575,62,1 -0,151,90,46,0,42.1,0.371,21,1 -6,109,60,27,0,25,0.206,27,0 -12,121,78,17,0,26.5,0.259,62,0 -8,100,76,0,0,38.7,0.19,42,0 -8,124,76,24,600,28.7,0.687,52,1 -1,93,56,11,0,22.5,0.417,22,0 -8,143,66,0,0,34.9,0.129,41,1 -6,103,66,0,0,24.3,0.249,29,0 -3,176,86,27,156,33.3,1.154,52,1 -0,73,0,0,0,21.1,0.342,25,0 -11,111,84,40,0,46.8,0.925,45,1 -2,112,78,50,140,39.4,0.175,24,0 -3,132,80,0,0,34.4,0.402,44,1 -2,82,52,22,115,28.5,1.699,25,0 -6,123,72,45,230,33.6,0.733,34,0 -0,188,82,14,185,32,0.682,22,1 -0,67,76,0,0,45.3,0.194,46,0 -1,89,24,19,25,27.8,0.559,21,0 -1,173,74,0,0,36.8,0.088,38,1 -1,109,38,18,120,23.1,0.407,26,0 -1,108,88,19,0,27.1,0.4,24,0 -6,96,0,0,0,23.7,0.19,28,0 -1,124,74,36,0,27.8,0.1,30,0 -7,150,78,29,126,35.2,0.692,54,1 -4,183,0,0,0,28.4,0.212,36,1 -1,124,60,32,0,35.8,0.514,21,0 -1,181,78,42,293,40,1.258,22,1 -1,92,62,25,41,19.5,0.482,25,0 -0,152,82,39,272,41.5,0.27,27,0 -1,111,62,13,182,24,0.138,23,0 -3,106,54,21,158,30.9,0.292,24,0 -3,174,58,22,194,32.9,0.593,36,1 -7,168,88,42,321,38.2,0.787,40,1 -6,105,80,28,0,32.5,0.878,26,0 -11,138,74,26,144,36.1,0.557,50,1 -3,106,72,0,0,25.8,0.207,27,0 -6,117,96,0,0,28.7,0.157,30,0 -2,68,62,13,15,20.1,0.257,23,0 -9,112,82,24,0,28.2,1.282,50,1 -0,119,0,0,0,32.4,0.141,24,1 -2,112,86,42,160,38.4,0.246,28,0 -2,92,76,20,0,24.2,1.698,28,0 -6,183,94,0,0,40.8,1.461,45,0 -0,94,70,27,115,43.5,0.347,21,0 -2,108,64,0,0,30.8,0.158,21,0 -4,90,88,47,54,37.7,0.362,29,0 -0,125,68,0,0,24.7,0.206,21,0 -0,132,78,0,0,32.4,0.393,21,0 -5,128,80,0,0,34.6,0.144,45,0 -4,94,65,22,0,24.7,0.148,21,0 -7,114,64,0,0,27.4,0.732,34,1 -0,102,78,40,90,34.5,0.238,24,0 -2,111,60,0,0,26.2,0.343,23,0 -1,128,82,17,183,27.5,0.115,22,0 -10,92,62,0,0,25.9,0.167,31,0 -13,104,72,0,0,31.2,0.465,38,1 -5,104,74,0,0,28.8,0.153,48,0 -2,94,76,18,66,31.6,0.649,23,0 -7,97,76,32,91,40.9,0.871,32,1 -1,100,74,12,46,19.5,0.149,28,0 -0,102,86,17,105,29.3,0.695,27,0 -4,128,70,0,0,34.3,0.303,24,0 -6,147,80,0,0,29.5,0.178,50,1 -4,90,0,0,0,28,0.61,31,0 -3,103,72,30,152,27.6,0.73,27,0 -2,157,74,35,440,39.4,0.134,30,0 -1,167,74,17,144,23.4,0.447,33,1 -0,179,50,36,159,37.8,0.455,22,1 -11,136,84,35,130,28.3,0.26,42,1 -0,107,60,25,0,26.4,0.133,23,0 -1,91,54,25,100,25.2,0.234,23,0 -1,117,60,23,106,33.8,0.466,27,0 -5,123,74,40,77,34.1,0.269,28,0 -2,120,54,0,0,26.8,0.455,27,0 -1,106,70,28,135,34.2,0.142,22,0 -2,155,52,27,540,38.7,0.24,25,1 -2,101,58,35,90,21.8,0.155,22,0 -1,120,80,48,200,38.9,1.162,41,0 -11,127,106,0,0,39,0.19,51,0 -3,80,82,31,70,34.2,1.292,27,1 -10,162,84,0,0,27.7,0.182,54,0 -1,199,76,43,0,42.9,1.394,22,1 -8,167,106,46,231,37.6,0.165,43,1 -9,145,80,46,130,37.9,0.637,40,1 -6,115,60,39,0,33.7,0.245,40,1 -1,112,80,45,132,34.8,0.217,24,0 -4,145,82,18,0,32.5,0.235,70,1 -10,111,70,27,0,27.5,0.141,40,1 -6,98,58,33,190,34,0.43,43,0 -9,154,78,30,100,30.9,0.164,45,0 -6,165,68,26,168,33.6,0.631,49,0 -1,99,58,10,0,25.4,0.551,21,0 -10,68,106,23,49,35.5,0.285,47,0 -3,123,100,35,240,57.3,0.88,22,0 -8,91,82,0,0,35.6,0.587,68,0 -6,195,70,0,0,30.9,0.328,31,1 -9,156,86,0,0,24.8,0.23,53,1 -0,93,60,0,0,35.3,0.263,25,0 -3,121,52,0,0,36,0.127,25,1 -2,101,58,17,265,24.2,0.614,23,0 -2,56,56,28,45,24.2,0.332,22,0 -0,162,76,36,0,49.6,0.364,26,1 -0,95,64,39,105,44.6,0.366,22,0 -4,125,80,0,0,32.3,0.536,27,1 -5,136,82,0,0,0,0.64,69,0 -2,129,74,26,205,33.2,0.591,25,0 -3,130,64,0,0,23.1,0.314,22,0 -1,107,50,19,0,28.3,0.181,29,0 -1,140,74,26,180,24.1,0.828,23,0 -1,144,82,46,180,46.1,0.335,46,1 -8,107,80,0,0,24.6,0.856,34,0 -13,158,114,0,0,42.3,0.257,44,1 -2,121,70,32,95,39.1,0.886,23,0 -7,129,68,49,125,38.5,0.439,43,1 -2,90,60,0,0,23.5,0.191,25,0 -7,142,90,24,480,30.4,0.128,43,1 -3,169,74,19,125,29.9,0.268,31,1 -0,99,0,0,0,25,0.253,22,0 -4,127,88,11,155,34.5,0.598,28,0 -4,118,70,0,0,44.5,0.904,26,0 -2,122,76,27,200,35.9,0.483,26,0 -6,125,78,31,0,27.6,0.565,49,1 -1,168,88,29,0,35,0.905,52,1 -2,129,0,0,0,38.5,0.304,41,0 -4,110,76,20,100,28.4,0.118,27,0 -6,80,80,36,0,39.8,0.177,28,0 -10,115,0,0,0,0,0.261,30,1 -2,127,46,21,335,34.4,0.176,22,0 -9,164,78,0,0,32.8,0.148,45,1 -2,93,64,32,160,38,0.674,23,1 -3,158,64,13,387,31.2,0.295,24,0 -5,126,78,27,22,29.6,0.439,40,0 -10,129,62,36,0,41.2,0.441,38,1 -0,134,58,20,291,26.4,0.352,21,0 -3,102,74,0,0,29.5,0.121,32,0 -7,187,50,33,392,33.9,0.826,34,1 -3,173,78,39,185,33.8,0.97,31,1 -10,94,72,18,0,23.1,0.595,56,0 -1,108,60,46,178,35.5,0.415,24,0 -5,97,76,27,0,35.6,0.378,52,1 -4,83,86,19,0,29.3,0.317,34,0 -1,114,66,36,200,38.1,0.289,21,0 -1,149,68,29,127,29.3,0.349,42,1 -5,117,86,30,105,39.1,0.251,42,0 -1,111,94,0,0,32.8,0.265,45,0 -4,112,78,40,0,39.4,0.236,38,0 -1,116,78,29,180,36.1,0.496,25,0 -0,141,84,26,0,32.4,0.433,22,0 -2,175,88,0,0,22.9,0.326,22,0 -2,92,52,0,0,30.1,0.141,22,0 -3,130,78,23,79,28.4,0.323,34,1 -8,120,86,0,0,28.4,0.259,22,1 -2,174,88,37,120,44.5,0.646,24,1 -2,106,56,27,165,29,0.426,22,0 -2,105,75,0,0,23.3,0.56,53,0 -4,95,60,32,0,35.4,0.284,28,0 -0,126,86,27,120,27.4,0.515,21,0 -8,65,72,23,0,32,0.6,42,0 -2,99,60,17,160,36.6,0.453,21,0 -1,102,74,0,0,39.5,0.293,42,1 -11,120,80,37,150,42.3,0.785,48,1 -3,102,44,20,94,30.8,0.4,26,0 -1,109,58,18,116,28.5,0.219,22,0 -9,140,94,0,0,32.7,0.734,45,1 -13,153,88,37,140,40.6,1.174,39,0 -12,100,84,33,105,30,0.488,46,0 -1,147,94,41,0,49.3,0.358,27,1 -1,81,74,41,57,46.3,1.096,32,0 -3,187,70,22,200,36.4,0.408,36,1 -6,162,62,0,0,24.3,0.178,50,1 -4,136,70,0,0,31.2,1.182,22,1 -1,121,78,39,74,39,0.261,28,0 -3,108,62,24,0,26,0.223,25,0 -0,181,88,44,510,43.3,0.222,26,1 -8,154,78,32,0,32.4,0.443,45,1 -1,128,88,39,110,36.5,1.057,37,1 -7,137,90,41,0,32,0.391,39,0 -0,123,72,0,0,36.3,0.258,52,1 -1,106,76,0,0,37.5,0.197,26,0 -6,190,92,0,0,35.5,0.278,66,1 -2,88,58,26,16,28.4,0.766,22,0 -9,170,74,31,0,44,0.403,43,1 -9,89,62,0,0,22.5,0.142,33,0 -10,101,76,48,180,32.9,0.171,63,0 -2,122,70,27,0,36.8,0.34,27,0 -5,121,72,23,112,26.2,0.245,30,0 -1,126,60,0,0,30.1,0.349,47,1 -1,93,70,31,0,30.4,0.315,23,0 diff --git a/completesolution/datascientist/diabetestree.ipynb b/completesolution/datascientist/diabetestree.ipynb deleted file mode 100644 index af27c10e..00000000 --- a/completesolution/datascientist/diabetestree.ipynb +++ /dev/null @@ -1,232 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Import Required Libraries\n", - "Import the necessary libraries, including pandas, sklearn, etc." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Importing required libraries\n", - "\n", - "# pandas for data manipulation and analysis\n", - "import pandas as pd\n", - "\n", - "\n", - "# matplotlib for data visualization\n", - "import matplotlib.pyplot as plt\n", - "\n", - "\n", - "# train_test_split for splitting the data into training and testing sets\n", - "from sklearn.model_selection import train_test_split\n", - "\n", - "# DecisionTreeClassifier for decision tree classification\n", - "from sklearn.tree import DecisionTreeClassifier\n", - "\n", - "# accuracy_score for evaluating the model\n", - "from sklearn import metrics\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Load the Diabetes Dataset\n", - "Load the diabetes dataset using pandas or from sklearn datasets." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Load the Diabetes Dataset\n", - "\n", - "\n", - "col_names = ['pregnant', 'glucose', 'bp', 'skin', 'insulin', 'bmi', 'pedigree', 'age', 'label']\n", - "\n", - "# load dataset\n", - "diabetes_df = pd.read_csv(\"diabetes.csv\", header=None, names=col_names)\n", - "\n", - "\n", - "# Display the first 5 rows of the DataFrame\n", - "diabetes_df.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Exploratory Data Analysis\n", - "Display the number of rows and columns in the dataframe.\n", - "Display the data types of each column.\n", - "Display the number of missing values in each column.\n", - "Display the number of unique values in each column.\n", - "Display the basic statistics of each column." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Exploratory Data Analysis\n", - "\n", - "# Display the first 5 rows of the DataFrame\n", - "diabetes_df.head()\n", - "\n", - "# Display the data types of each column.\n", - "diabetes_df.dtypes\n", - "\n", - "# Display the number of missing values in each column.\n", - "diabetes_df.isnull().sum()\n", - "\n", - "# Display the number of unique values in each column.\n", - "diabetes_df.nunique()\n", - "\n", - "# Display the summary statistics of the dataframe.\n", - "diabetes_df.describe()\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Feature Selection\n", - "\n", - "Select the features that you want to use for the prediction. You can use all the features or a subset of features.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# feature Selection\n", - "\n", - "#split dataset in features and target variable\n", - "feature_cols = ['pregnant', 'insulin', 'bmi', 'age','glucose','bp','pedigree']\n", - "\n", - "X = diabetes_df[feature_cols] # Features\n", - "y = diabetes_df.label # Target variable\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Splitting Data\n", - "Divide the data into training and testing sets. The training set will be used to train the model and the testing set will be used to evaluate the model." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Split dataset into training set and test set\n", - "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=1) # 70% training and 30% test\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Building Decision Tree Model\n", - "Build a decision tree model using the training set." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Building Decision Tree Model\n", - "\n", - "# Create Decision Tree classifer object\n", - "clf = DecisionTreeClassifier()\n", - "\n", - "# Train Decision Tree Classifer\n", - "clf = clf.fit(X_train,y_train)\n", - "\n", - "#Predict the response for test dataset\n", - "y_pred = clf.predict(X_test)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Evaluating Model\n", - "Evaluate the model using the testing set." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Evaluating Model\n", - "\n", - "# Model Accuracy, how often is the classifier correct?\n", - "print(\"Accuracy:\",metrics.accuracy_score(y_test, y_pred))\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Visualizing Decision Trees\n", - "\n", - "Visualize the decision tree using the graphviz library.\n", - "\n", - "pip install graphviz\n", - "\n", - "pip install pydotplus" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from sklearn.tree import export_graphviz\n", - "from sklearn.externals.six import StringIO \n", - "from IPython.display import Image \n", - "import pydotplus\n", - "\n", - "dot_data = StringIO()\n", - "export_graphviz(clf, out_file=dot_data, \n", - " filled=True, rounded=True,\n", - " special_characters=True,feature_names = feature_cols,class_names=['0','1'])\n", - "graph = pydotplus.graph_from_dot_data(dot_data.getvalue()) \n", - "graph.write_png('diabetes.png')\n", - "Image(graph.create_png())" - ] - } - ], - "metadata": { - "language_info": { - "name": "python" - }, - "orig_nbformat": 4 - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/completesolution/dotnet/.vscode/settings.json b/completesolution/dotnet/.vscode/settings.json deleted file mode 100644 index 18cd287f..00000000 --- a/completesolution/dotnet/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "dotnet.defaultSolution": "MinimalAPI.sln" -} \ No newline at end of file diff --git a/completesolution/dotnet/MinimalAPI.Tests/Helpers/TestWebApplicationFactory.cs b/completesolution/dotnet/MinimalAPI.Tests/Helpers/TestWebApplicationFactory.cs deleted file mode 100644 index 2ed7cb2f..00000000 --- a/completesolution/dotnet/MinimalAPI.Tests/Helpers/TestWebApplicationFactory.cs +++ /dev/null @@ -1,12 +0,0 @@ - - -namespace IntegrationTests; - -public class TestWebApplicationFactory - : WebApplicationFactory where TProgram : class -{ - protected override IHost CreateHost(IHostBuilder builder) - { - return base.CreateHost(builder); - } -} \ No newline at end of file diff --git a/completesolution/dotnet/MinimalAPI.Tests/IntegrationTests.cs b/completesolution/dotnet/MinimalAPI.Tests/IntegrationTests.cs deleted file mode 100644 index 6e575fb3..00000000 --- a/completesolution/dotnet/MinimalAPI.Tests/IntegrationTests.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace IntegrationTests; - -public class IntegrationTests : IClassFixture> -{ - private readonly HttpClient _client; - - public IntegrationTests(TestWebApplicationFactory factory) - { - _client = factory.CreateClient(); - } - - [Fact] - public async Task Get_ReturnsHelloWorld() - { - // Arrange - - // Act - var response = await _client.GetAsync("/"); - - // Assert - response.EnsureSuccessStatusCode(); - var content = await response.Content.ReadAsStringAsync(); - Assert.Equal("Hello World!", content); - } -} diff --git a/completesolution/dotnet/MinimalAPI.Tests/MinimalAPI.Tests.csproj b/completesolution/dotnet/MinimalAPI.Tests/MinimalAPI.Tests.csproj deleted file mode 100644 index 67976258..00000000 --- a/completesolution/dotnet/MinimalAPI.Tests/MinimalAPI.Tests.csproj +++ /dev/null @@ -1,30 +0,0 @@ - - - - net8.0 - enable - enable - - false - true - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - diff --git a/completesolution/dotnet/MinimalAPI.Tests/Usings.cs b/completesolution/dotnet/MinimalAPI.Tests/Usings.cs deleted file mode 100644 index 28389ef8..00000000 --- a/completesolution/dotnet/MinimalAPI.Tests/Usings.cs +++ /dev/null @@ -1,3 +0,0 @@ -global using Xunit; -global using Microsoft.AspNetCore.Mvc.Testing; -global using Microsoft.Extensions.Hosting; diff --git a/completesolution/dotnet/MinimalAPI.sln b/completesolution/dotnet/MinimalAPI.sln deleted file mode 100644 index 9c12c699..00000000 --- a/completesolution/dotnet/MinimalAPI.sln +++ /dev/null @@ -1,31 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.5.001.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MinimalAPI", "MinimalAPI\MinimalAPI.csproj", "{03FF4ABB-0BAB-42BA-9910-725B80C69895}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MinimalAPI.Tests", "MinimalAPI.Tests\MinimalAPI.Tests.csproj", "{0ED02B58-62C2-4474-BF04-40CF38069A84}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {03FF4ABB-0BAB-42BA-9910-725B80C69895}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {03FF4ABB-0BAB-42BA-9910-725B80C69895}.Debug|Any CPU.Build.0 = Debug|Any CPU - {03FF4ABB-0BAB-42BA-9910-725B80C69895}.Release|Any CPU.ActiveCfg = Release|Any CPU - {03FF4ABB-0BAB-42BA-9910-725B80C69895}.Release|Any CPU.Build.0 = Release|Any CPU - {0ED02B58-62C2-4474-BF04-40CF38069A84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0ED02B58-62C2-4474-BF04-40CF38069A84}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0ED02B58-62C2-4474-BF04-40CF38069A84}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0ED02B58-62C2-4474-BF04-40CF38069A84}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {9F921EE8-9C46-4602-A195-8C24938239EC} - EndGlobalSection -EndGlobal diff --git a/completesolution/dotnet/MinimalAPI/Color.cs b/completesolution/dotnet/MinimalAPI/Color.cs deleted file mode 100644 index 6a75b93e..00000000 --- a/completesolution/dotnet/MinimalAPI/Color.cs +++ /dev/null @@ -1,23 +0,0 @@ -public class Color -{ - [JsonPropertyName("name")] - public string Name { get; set; } - - [JsonPropertyName("category")] - public string Category { get; set; } - - [JsonPropertyName("type")] - public string Type { get; set; } - - [JsonPropertyName("code")] - public ColorCode Code { get; set; } -} - -public class ColorCode -{ - [JsonPropertyName("rgba")] - public int[] RGBA { get; set; } - - [JsonPropertyName("hex")] - public string HEX { get; set; } -} \ No newline at end of file diff --git a/completesolution/dotnet/MinimalAPI/Dockerfile b/completesolution/dotnet/MinimalAPI/Dockerfile deleted file mode 100644 index 53830eaf..00000000 --- a/completesolution/dotnet/MinimalAPI/Dockerfile +++ /dev/null @@ -1,28 +0,0 @@ -# create dotnet 7 image for the current project - -# Use the official .NET 7 SDK image as the base image -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS build - -# Set the working directory to /app -WORKDIR /app - -# Copy the current directory contents into the container at /app -COPY . . - -# Build the project and restore the packages -RUN dotnet restore - -# Publish the project to a folder called "out" -RUN dotnet publish -c Release -o out - -# Use the official .NET 7 runtime image as the base image -FROM mcr.microsoft.com/dotnet/runtime:7.0 AS runtime - -# Set the working directory to /app -WORKDIR /app - -# Copy the published output from the build image to the runtime image -COPY --from=build /app/out ./ - -# Set the entry point to the application -ENTRYPOINT ["dotnet", "/app/MinimalAPI.dll"] diff --git a/completesolution/dotnet/MinimalAPI/MinimalAPI.csproj b/completesolution/dotnet/MinimalAPI/MinimalAPI.csproj deleted file mode 100644 index 06c5c763..00000000 --- a/completesolution/dotnet/MinimalAPI/MinimalAPI.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - net8.0 - enable - enable - - - - - - - - - diff --git a/completesolution/dotnet/MinimalAPI/Program.cs b/completesolution/dotnet/MinimalAPI/Program.cs deleted file mode 100644 index 56855869..00000000 --- a/completesolution/dotnet/MinimalAPI/Program.cs +++ /dev/null @@ -1,107 +0,0 @@ -var builder = WebApplication.CreateBuilder(args); - -// Add services to the container. -// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); - -// Register a HttpClient so IHttpClientFactory can be used to create HttpClient instances. -builder.Services.AddHttpClient(); - -var app = builder.Build(); - -// Configure the HTTP request pipeline. -if (app.Environment.IsDevelopment()) -{ - app.UseSwagger(); - app.UseSwaggerUI(); -} - -app.UseHttpsRedirection(); - -// Hello World Get endpoint -app.MapGet("/", () => "Hello World!"); - -// Endpoint daysdetweendates. Reads startdate and enddate from the query string and returns the number of days between the dates -app.MapGet("/daysbetweendates", (DateTime startdate, DateTime enddate) => (enddate - startdate).TotalDays.ToString()); - -// Endpoint validatephonenumber. Reads phonenumber from the query string and returns true if the number is valid using regex -app.MapGet("/validatephonenumber", (string phonenumber) => Regex.IsMatch(phonenumber, @"^(\+[0-9]{9})$").ToString()); - -// Endpopoint validatespanishdni. Reads dni from the query string and returns true if the dni is valid. Implement inline. -app.MapGet("/validatespanishdni", (string dni) => -{ - if (dni.Length != 9) return false; - var dniLetters = "TRWAGMYFPDXBNJZSQVHLCKE"; - var dniNumber = int.Parse(dni.Substring(0, 8)); - var dniLetter = dniLetters[dniNumber % 23]; - return dniLetter == dni[8]; -}); - -// Endpoint color. -// Reads color from the query string. -// Read colors.json into a Coloresarray and iterated to find the correct color -// Return the Hex code. -// Implement inline. -app.MapGet("/color", (string color) => -{ - var colors = JsonSerializer.Deserialize(File.ReadAllText("colors.json")); - return colors.First(c => c.Name == color).Code.HEX; -}); - -// tellmeajoke endpoint. Calls jokeapi and returns a sinlgle joke. -// Deserialize to dynamic. -// Make sure to use IHttpClientFactory to create the HttpClient instance. -app.MapGet("/tellmeajoke", async (IHttpClientFactory httpClientFactory) => -{ - var client = httpClientFactory.CreateClient(); - var response = await client.GetAsync("https://v2.jokeapi.dev/joke/Any"); - var content = await response.Content.ReadAsStringAsync(); - return JsonSerializer.Deserialize(content); -}); - -// moviesbydirector endpoint. -// Calls omdbapi with an apikey specified in code and returns a list of movies -// for the director specified in the query string. Deserialize to dynamic. -// Make sure to use IHttpClientFactory to create the HttpClient instance. -app.MapGet("/moviesbydirector", async (string director, IHttpClientFactory httpClientFactory) => -{ - var client = httpClientFactory.CreateClient(); - var response = await client.GetAsync($"http://www.omdbapi.com/?apikey=4e3b711b&r=json&s={director}"); - var content = await response.Content.ReadAsStringAsync(); - return JsonSerializer.Deserialize(content); -}); - -// parseurl endpoint. Reads url from the query string and returns the protocol, host and path in a json object. Implement inline. -app.MapGet("/parseurl", (string url) => -{ - var uri = new Uri(url); - return new { Protocol = uri.Scheme, Host = uri.Host, Path = uri.AbsolutePath }; -}); - -// listfiles endpoint. Read contents of the current directory and return a list of files. Implement inline. -app.MapGet("/listfiles", () => -{ - var files = Directory.GetFileSystemEntries(Directory.GetCurrentDirectory()); - return JsonSerializer.Serialize(files); -}); - -// calculate memory usage endpoint. Return the current memory usage of the process in GB. Implement inline. -app.MapGet("/calculatememoryusage", () => -{ - var process = Process.GetCurrentProcess(); - return $"{process.WorkingSet64 / 1024 / 1024 / 1024} GB"; -}); - -// random european country endpoint. Return a random european country from an array. Implement inline. -app.MapGet("/randomeuropeancountry", () => -{ - var countries = new[] { "Spain", "France", "Germany", "Italy", "Portugal", "Sweden", "Norway", "Denmark", "Finland", "Iceland", "Ireland", "United Kingdom", "Greece", "Austria", "Belgium", "Bulgaria", "Croatia", "Cyprus", "Czech Republic", "Estonia", "Hungary", "Latvia", "Lithuania", "Luxembourg", "Malta", "Netherlands", "Poland", "Romania", "Slovakia", "Slovenia" }; - return countries[new Random().Next(0, countries.Length)]; -}); - -app.Run(); - -// Needed to be able to access this type from the MinimalAPI.Tests project. -public partial class Program -{ } diff --git a/completesolution/dotnet/MinimalAPI/Properties/launchSettings.json b/completesolution/dotnet/MinimalAPI/Properties/launchSettings.json deleted file mode 100644 index 1632a63a..00000000 --- a/completesolution/dotnet/MinimalAPI/Properties/launchSettings.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:51750", - "sslPort": 44373 - } - }, - "profiles": { - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "launchUrl": "swagger", - "applicationUrl": "http://localhost:5163", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "https": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "launchUrl": "swagger", - "applicationUrl": "https://localhost:7007;http://localhost:5163", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/completesolution/dotnet/MinimalAPI/Usings.cs b/completesolution/dotnet/MinimalAPI/Usings.cs deleted file mode 100644 index 09b16c96..00000000 --- a/completesolution/dotnet/MinimalAPI/Usings.cs +++ /dev/null @@ -1,4 +0,0 @@ -global using System.Diagnostics; -global using System.Text.RegularExpressions; -global using System.Text.Json; -global using System.Text.Json.Serialization; \ No newline at end of file diff --git a/completesolution/dotnet/MinimalAPI/appsettings.Development.json b/completesolution/dotnet/MinimalAPI/appsettings.Development.json deleted file mode 100644 index 0c208ae9..00000000 --- a/completesolution/dotnet/MinimalAPI/appsettings.Development.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - } -} diff --git a/completesolution/dotnet/MinimalAPI/appsettings.json b/completesolution/dotnet/MinimalAPI/appsettings.json deleted file mode 100644 index 10f68b8c..00000000 --- a/completesolution/dotnet/MinimalAPI/appsettings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*" -} diff --git a/completesolution/dotnet/MinimalAPI/colors.json b/completesolution/dotnet/MinimalAPI/colors.json deleted file mode 100644 index e7283ffa..00000000 --- a/completesolution/dotnet/MinimalAPI/colors.json +++ /dev/null @@ -1,56 +0,0 @@ -[ - { - "name": "black", - "category": "hue", - "type": "primary", - "code": { - "rgba": [255,255,255,1], - "hex": "#000000" - } - }, - { - "name": "white", - "category": "value", - "code": { - "rgba": [0,0,0,1], - "hex": "#FFFFFF" - } - }, - { - "name": "red", - "category": "hue", - "type": "primary", - "code": { - "rgba": [255,0,0,1], - "hex": "#FF0000" - } - }, - { - "name": "blue", - "category": "hue", - "type": "primary", - "code": { - "rgba": [0,0,255,1], - "hex": "#0000FF" - } - }, - { - "name": "yellow", - "category": "hue", - "type": "primary", - "code": { - "rgba": [255,255,0,1], - "hex": "#FFFF00" - } - }, - { - "name": "green", - "category": "hue", - "type": "secondary", - "code": { - "rgba": [0,255,0,1], - "hex": "#00FF00" - } - } - ] - \ No newline at end of file diff --git a/completesolution/node/.dockerignore b/completesolution/node/.dockerignore deleted file mode 100644 index 5309493a..00000000 --- a/completesolution/node/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -[D|d]ockerfile \ No newline at end of file diff --git a/completesolution/node/Dockerfile b/completesolution/node/Dockerfile deleted file mode 100644 index 0086afcd..00000000 --- a/completesolution/node/Dockerfile +++ /dev/null @@ -1,29 +0,0 @@ -# Create a dockerfile with node image -FROM node:latest - -# Create a directory to hold the application code inside the image, this will be the working directory for your application -RUN mkdir -p /usr/src/app - -# Set the working directory to /usr/src/app -WORKDIR /usr/src/app - -# Copy package.json and package-lock.json to the working directory -COPY package*.json ./ - -# Install npm -RUN npm install - -# Copy the current directory contents into the container at /usr/src/app -COPY . . - -# Make port 3000 available to the world outside this container -EXPOSE 3000 - -# Run Nodeserver.js when the container launches -CMD ["node", "nodeserver.js"] - -# Write a docker comand to build the image and tag it as mynodeapp -#docker build -t mynodeapp . - -# Write command to run docker in port 4000 -#docker run -p 4000:3000 -d diff --git a/completesolution/node/colors.json b/completesolution/node/colors.json deleted file mode 100644 index 395bf81f..00000000 --- a/completesolution/node/colors.json +++ /dev/null @@ -1,55 +0,0 @@ -[ - { - "color": "black", - "category": "hue", - "type": "primary", - "code": { - "rgba": [255,255,255,1], - "hex": "#000000" - } - }, - { - "color": "white", - "category": "value", - "code": { - "rgba": [0,0,0,1], - "hex": "#FFFFFF" - } - }, - { - "color": "red", - "category": "hue", - "type": "primary", - "code": { - "rgba": [255,0,0,1], - "hex": "#FF0000" - } - }, - { - "color": "blue", - "category": "hue", - "type": "primary", - "code": { - "rgba": [0,0,255,1], - "hex": "#0000FF" - } - }, - { - "color": "yellow", - "category": "hue", - "type": "primary", - "code": { - "rgba": [255,255,0,1], - "hex": "#FFFF00" - } - }, - { - "color": "green", - "category": "hue", - "type": "secondary", - "code": { - "rgba": [0,255,0,1], - "hex": "#00FF00" - } - } -] diff --git a/completesolution/node/nodeserver.js b/completesolution/node/nodeserver.js deleted file mode 100644 index 7d5b8787..00000000 --- a/completesolution/node/nodeserver.js +++ /dev/null @@ -1,327 +0,0 @@ -// write a nodejs server that will expose a method call "get" that will return the value of the key passed in the query string -// example: http://localhost:3000/get?key=hello -// should return the value of foo -// if the key is not passed, return "key not passed" -// if the key is passed, return "hello" key - -const http = require('http'); -const url = require('url'); -const fs = require('fs'); -const escape = require('escape-html'); - -const server = http.createServer((req, res) => { - if (req.url.startsWith('/DaysBetweenDates')) { - //calculate days between two dates - - //get dates from querystring - var queryData = url.parse(req.url, true).query; - var date1 = queryData.date1; - var date2 = queryData.date2; - - //convert dates to milliseconds - var date1_ms = Date.parse(date1); - var date2_ms = Date.parse(date2); - - //calculate difference in milliseconds - var difference_ms = date2_ms - date1_ms; - - //convert to days and return - res.end(Math.round(difference_ms / 86400000) + " days"); - - } else if (req.url.startsWith('/Validatephonenumber')) { - - //get phoneNumber var from querystring - var queryData = url.parse(req.url, true).query; - var phoneNumber = queryData.phoneNumber; - - - //validate phoneNumber with Spanish format - var regex = /^(\+34|0034|34)?[ -]*(6|7)[ -]*([0-9][ -]*){8}$/; - - //if phoneNumber is valid return "valid" - if (regex.test(phoneNumber)) { - res.end("valid"); - } - //if phoneNumber is not valid return "invalid" - else { - res.end("invalid"); - } - } else if (req.url.startsWith('/ValidateSpanishDNI')) { - var queryData = url.parse(req.url, true).query; - var dni = queryData.dni; - - // calculate DNI letter - var dniLetter = dni.charAt(dni.length - 1); - var dniNumber = dni.substring(0, dni.length - 1); - var dniLetterCalc = "TRWAGMYFPDXBNJZSQVHLCKE".charAt(dniNumber % 23); - - //if DNI is valid return "valid" - if (dniLetter == dniLetterCalc) { - res.end("valid"); - } - //if DNI is not valid return "invalid" - else { - res.end("invalid"); - } - - } else if (req.url.startsWith('/ReturnColorCode')) { - - //read colors.json file and return the rgba field - var colors = fs.readFileSync('colors.json', 'utf-8'); - var colorsObj = JSON.parse(colors); - - //get color var from querystring - var queryData = url.parse(req.url, true).query; - var color = queryData.color; - var colorFound = "not found"; - - //for each color in colors.json - for (var i = 1; i < colorsObj.length; i++) { - //if color is found return the color code - if (colorsObj[i].color == color) { - colorFound = colorsObj[i].code.hex; - } - } - - res.end(colorFound); - - } - else if (req.url.startsWith('/SendEmail')) { - } - else if (req.url.startsWith('/TellMeAJoke')) { - - //make a call to the joke api and return a random joke using axios - const axios = require('axios'); - - axios.get('https://official-joke-api.appspot.com/random_joke') - .then(function (response) { - // handle success - res.end(response.data.setup + " " + response.data.punchline); - } - ) - .catch(function (error) { - // handle error - console.log(error); - }) - .then(function () { - // always executed - }); - } - - //method that gets the name of a director and retrieves from an api the list of movies of that director - else if (req.url.startsWith('/MoviesByDirector')) { - - //get a director name from querystring - var queryData = url.parse(req.url, true).query; - var director = queryData.director; - - //make a call to the movie api omdbapi.com and return a list of movies of that director using axios - const axios = require('axios'); - - axios.get('http://www.omdbapi.com/?apikey=XXXXXXX&s=' + director) - .then(function (response) { - - //return the full list of movies - var movies = ""; - for (var i = 0; i < response.data.Search.length; i++) { - movies = movies + response.data.Search[i].Title + ", "; - } - - res.end(movies); - - } - ) - .catch(function (error) { - // handle error - console.log(error); - } - ) - .then(function () { - // always executed - } - ); - } - //If url equals to ParseUrl - else if (req.url.startsWith('/ParseUrl')) { - - //retrieves a parameter from querystring called someurl - var queryData = url.parse(req.url, true).query; - var someUrl = queryData.someurl; - - //parse the url and return the protocol, host, port, path, querystring and hash - var urlObj = new URL(someUrl); - - var protocol = urlObj.protocol; - var host = urlObj.host; - var port = urlObj.port; - var path = urlObj.pathname; - var querystring = urlObj.search; - var hash = urlObj.hash; - - //return the parsed host - res.end("host: " + host); - - } - //if url contains listFiles in current directory - else if (req.url.startsWith('/ListFiles')) { - - //get the current directory - var currentDir = __dirname; - - //get the list of files in the current directory - var files = fs.readdirSync(currentDir); - - //return the list of files - res.end(escape(files.toString())); - - } - else if (req.url.startsWith('/GetFullTextFile')) { - - //read sample.txt and return lines that contains the word "Fusce" - var text = fs.readFileSync('sample.txt', 'utf-8'); - var lines = text.split("\r"); - - var linesFound = ""; - for (var i = 1; i < lines.length; i++) { - if (lines[i].includes("Fusce")) { - linesFound = linesFound + lines[i] + ", "; - } - } - - res.end(linesFound); - } - else if (req.url.startsWith('/GetLineByLinefromtTextFile')) { - - //read sample.txt line by line - var lineReader = require('readline').createInterface({ - input: require('fs').createReadStream('sample.txt') - }); - - //create a promise to read the file line by line, and return a list of lines that contains the word "Fusce" - var promise = new Promise(function (resolve, reject) { - var lines = []; - lineReader.on('line', function (line) { - if (line.includes("Fusce")) { - lines.push(line); - } - }); - lineReader.on('close', function () { - resolve(lines); - }); - }); - - //return the list of lines - promise.then(function (lines) { - res.end(lines.toString()); - }); - } - else if (req.url.startsWith('/CalculateMemoryConsumption')) { - - //return the memory consumption of the process in GB, rounded to 2 decimals - var memory = process.memoryUsage().heapUsed / 1024 / 1024; - - res.end(memory.toFixed(2) + " GB"); - - } - else if (req.url.startsWith('/MakeZipFile')) { - - //using zlib create a zip file called sample.gz that contains sample.txt - var zlib = require('zlib'); - - var gzip = zlib.createGzip(); - var input = fs.createReadStream('sample.txt'); - var output = fs.createWriteStream('sample.gz'); - - input.pipe(gzip).pipe(output); - - res.end("sample.gz created"); - - } - else if (req.url.startsWith('/RandomEuropeanCountry')) { - - //make an array of european countries and its iso codes - var countries = [ - { country: "Italy", iso: "IT" }, - { country: "France", iso: "FR" }, - { country: "Spain", iso: "ES" }, - { country: "Germany", iso: "DE" }, - { country: "United Kingdom", iso: "GB" }, - { country: "Greece", iso: "GR" }, - { country: "Portugal", iso: "PT" }, - { country: "Romania", iso: "RO" }, - { country: "Bulgaria", iso: "BG" }, - { country: "Croatia", iso: "HR" }, - { country: "Czech Republic", iso: "CZ" }, - { country: "Denmark", iso: "DK" }, - { country: "Estonia", iso: "EE" }, - { country: "Finland", iso: "FI" }, - { country: "Hungary", iso: "HU" }, - { country: "Ireland", iso: "IE" }, - { country: "Latvia", iso: "LV" }, - { country: "Lithuania", iso: "LT" }, - { country: "Luxembourg", iso: "LU" }, - { country: "Malta", iso: "MT" }, - { country: "Netherlands", iso: "NL" }, - { country: "Poland", iso: "PL" }, - { country: "Slovakia", iso: "SK" }, - { country: "Slovenia", iso: "SI" }, - { country: "Sweden", iso: "SE" }, - { country: "Belgium", iso: "BE" }, - { country: "Austria", iso: "AT" }, - { country: "Switzerland", iso: "CH" }, - { country: "Cyprus", iso: "CY" }, - { country: "Iceland", iso: "IS" }, - { country: "Norway", iso: "NO" }, - { country: "Albania", iso: "AL" }, - { country: "Andorra", iso: "AD" }, - { country: "Armenia", iso: "AM" }, - { country: "Azerbaijan", iso: "AZ" }, - { country: "Belarus", iso: "BY" }, - { country: "Bosnia and Herzegovina", iso: "BA" }, - { country: "Georgia", iso: "GE" }, - { country: "Kazakhstan", iso: "KZ" }, - { country: "Kosovo", iso: "XK" }, - { country: "Liechtenstein", iso: "LI" }, - { country: "Macedonia", iso: "MK" }, - { country: "Moldova", iso: "MD" }, - { country: "Monaco", iso: "MC" }, - { country: "Montenegro", iso: "ME" }, - { country: "Russia", iso: "RU" }, - { country: "San Marino", iso: "SM" }, - { country: "Serbia", iso: "RS" }, - { country: "Turkey", iso: "TR" }, - { country: "Ukraine", iso: "UA" }, - { country: "Vatican City", iso: "VA" } - ]; - - //return a random country from the array - var randomCountry = countries[Math.floor(Math.random() * countries.length)]; - - //return the country and its iso code - res.end(randomCountry.country + " " + randomCountry.iso); - - } - else if (req.url.startsWith('/Get')) { - const { query } = url.parse(req.url, true); - const { key } = query; - - if (!key) { - res.end('key not passed'); - } else { - res.end('hello ' + escape(key)); - } - } - else { - res.end('Called method not found'); - } -}); - -server.listen(3000, () => { - console.log('server is listening on port 3000'); -}); - -//write command line to generate package.json -//npm init -y - -//write curl command to getMoviesByDirector -//curl http://localhost:3000/getMoviesByDirector?director=Quentin%20Tarantino diff --git a/completesolution/node/package-lock.json b/completesolution/node/package-lock.json deleted file mode 100644 index 1f2a147c..00000000 --- a/completesolution/node/package-lock.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "nodeserver", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "nodeserver", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "escape-html": "^1.0.3" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - } - }, - "dependencies": { - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - } - } -} diff --git a/completesolution/node/package.json b/completesolution/node/package.json deleted file mode 100644 index 4cc7c3f8..00000000 --- a/completesolution/node/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "nodeserver", - "version": "1.0.0", - "main": "nodeserver.js", - "scripts": { - "start": "node nodeserver.js", - "test": "mocha test.js" - }, - "keywords": [], - "author": "", - "license": "ISC", - "description": "", - "dependencies": { - "escape-html": "^1.0.3" - } -} diff --git a/completesolution/node/test.js b/completesolution/node/test.js deleted file mode 100644 index 92a2543b..00000000 --- a/completesolution/node/test.js +++ /dev/null @@ -1,131 +0,0 @@ -//write npm command line to install mocha -//npm install --global mocha - -//command to run this test file -//mocha test.js - -const assert = require('assert'); -const http = require('http'); - -const server = require('./nodeserver'); - - - -describe('Node Server', () => { - it('should return "key not passed" if key is not passed', (done) => { - http - .get('http://localhost:3000/Get' , (res) => { - let data = ''; - res.on('data', (chunk) => { - data += chunk; - }); - res.on('end', () => { - assert.equal(data, 'key not passed'); - done(); - }); - }); - }); - - - it('should return the value of the key if key is found', (done) => { - http.get('http://localhost:3000/Get?key=world', (res) => { - let data = ''; - res.on('data', (chunk) => { - data += chunk; - }); - res.on('end', () => { - assert.equal(data, 'hello world'); - done(); - }); - }); - }); - - //add test to check validatephoneNumber - it('should return "valid" if phoneNumber is valid', (done) => { - http.get('http://localhost:3000/Validatephonenumber?phoneNumber=34666666666', (res) => { - let data = ''; - res.on('data', (chunk) => { - data += chunk; - }); - res.on('end', () => { - assert.equal(data, 'valid'); - done(); - }); - }); - }); - - //write test to validate spanish DNI - it('should return "valid" if spanish DNI 86471508H is valid', (done) => { - http.get('http://localhost:3000/ValidateSpanishDNI?dni=86471508H', (res) => { - let data = ''; - res.on('data', (chunk) => { - data += chunk; - }); - res.on('end', () => { - assert.equal(data, 'valid'); - done(); - }); - }); - }); - - - - //write test to validate spanish DNI - it('should return "valid" if spanish DNI 24153149K is valid', (done) => { - http.get('http://localhost:3000/ValidateSpanishDNI?dni=24153149K', (res) => { - let data = ''; - res.on('data', (chunk) => { - data += chunk; - }); - res.on('end', () => { - assert.equal(data, 'valid'); - done(); - }); - }); - }); - - - //write test to validate spanish DNI - it('should return "valid" if spanish DNI 12345678A is invalid', (done) => { - http.get('http://localhost:3000/ValidateSpanishDNI?dni=12345678A', (res) => { - let data = ''; - res.on('data', (chunk) => { - data += chunk; - }); - res.on('end', () => { - assert.equal(data, 'invalid'); - done(); - }); - }); - }); - - //write test for returnColorCode - it('should return "red" if color is red', (done) => { - http.get('http://localhost:3000/ReturnColorCode?color=red', (res) => { - let data = ''; - res.on('data', (chunk) => { - data += chunk; - }); - res.on('end', () => { - assert.equal(data, '#FF0000'); - done(); - }); - }); - }); - - //write test for daysBetweenDates - it('should return "1" if dates are 2020-01-01 and 2020-01-02', (done) => { - http.get('http://localhost:3000/DaysBetweenDates?date1=2020-01-01&date2=2020-01-02', (res) => { - let data = ''; - res.on('data', (chunk) => { - data += chunk; - }); - res.on('end', () => { - assert.equal(data, '1 days'); - done(); - }); - }); - }); - - -}); \ No newline at end of file diff --git a/completesolution/quarkus/README.md b/completesolution/quarkus/README.md deleted file mode 100644 index 2ae464d5..00000000 --- a/completesolution/quarkus/README.md +++ /dev/null @@ -1,85 +0,0 @@ -# Quarkus REST API Exercise - -## Goal - -The goal of this exercise is to learn how to use GitHub Copilot, using an exercise that consist of building a REST API using [Quarkus](https://quarkus.io/). - -## Exercises - -We have created a Quarkus project with some files already created, you can find the project in the folder **exercisefiles/quarkus**. - -Let's start copiloting!!! - -### 1. Create the code to handle a simple GET request - -Move to the 'DemoResource.java' file and start writing the code to handle a simple GET request. In this first exercise, we have provided a comment that describes the code you need to generate. Just press enter and wait a couple of seconds, Copilot will generate the code for you. If you are not happy with the code generated, you can press enter again and Copilot will generate a new code - -There is already a unit test implemented for this exercise, you can run it using the command `mvn test` before and after to validate that the code generated by Copilot is correct. - -Then, create a new unit test for the case when no key is provided in the request. - -After every exercise, feel free to package and run your application to test it. - -Package: `mvn package` - -Run: `mvn quarkus:dev` - -Test: `curl -v http://localhost:8080/hello?key=world` - -### 2. Dates comparison - -New operation under /diffdates that calculates the difference between two dates. The operation should receive two dates as parameter in format dd-MM-yyyy and return the difference in days. - -Additionally, create a unit test that validates the operation. - -From now on, you will have to create the unit tests for every new operation. Wasn't it easy with Copilot? - -### 3. Validate the format of a spanish phone - -Validate the format of a spanish phone number (+34 prefix, then 9 digits, starting with 6, 7 or 9). The operation should receive a phone number as parameter and return true if the format is correct, false otherwise. - -### 4. Validate the format of a spanish DNI - -Validate the format of a spanish DNI (8 digits and 1 letter). The operation should receive a DNI as parameter and return true if the format is correct, false otherwise. - -### 5. From color name to hexadecimal code - -Based on existing colors.json file under resources, given the name of the color as path parameter, return the hexadecimal code. If the color is not found, return 404 - -Hint: Use TDD. Start by creating the unit test and then implement the code. - -### 6. Jokes creator - -Create a new operation that call the API https://api.chucknorris.io/jokes/random and return the joke. - -### 7. URL parsing - -Given a url as query parameter, parse it and return the protocol, host, port, path and query parameters. The response should be in Json format. - -### 8. List files and folders - -List files and folders under a given path. The path should be a query parameter. The response should be in Json format. - -### 9. Word counting - -Given the path of a file and count the number of occurrence of a provided word. The path and the word should be query parameters. The response should be in Json format. - -### 10. Zipping - -Create a zip file with the content of a given folder. The path of the folder should be a query parameter. - -### 11. Containerize the application - -Use the Dockerfile provided to create a docker image of the application. In this case, the full content is provided, but in order build, run and test the docker image, you will use Copilot as well to generate the commands. - -I have created a DOCKER.md file where we will document the steps to build the application (native), build the container image, yun the container and test the container. - - - - - - - - - - diff --git a/completesolution/quarkus/copilot-demo/.dockerignore b/completesolution/quarkus/copilot-demo/.dockerignore deleted file mode 100644 index 94810d00..00000000 --- a/completesolution/quarkus/copilot-demo/.dockerignore +++ /dev/null @@ -1,5 +0,0 @@ -* -!target/*-runner -!target/*-runner.jar -!target/lib/* -!target/quarkus-app/* \ No newline at end of file diff --git a/completesolution/quarkus/copilot-demo/.gitignore b/completesolution/quarkus/copilot-demo/.gitignore deleted file mode 100644 index 8c7863e7..00000000 --- a/completesolution/quarkus/copilot-demo/.gitignore +++ /dev/null @@ -1,43 +0,0 @@ -#Maven -target/ -pom.xml.tag -pom.xml.releaseBackup -pom.xml.versionsBackup -release.properties -.flattened-pom.xml - -# Eclipse -.project -.classpath -.settings/ -bin/ - -# IntelliJ -.idea -*.ipr -*.iml -*.iws - -# NetBeans -nb-configuration.xml - -# Visual Studio Code -.vscode -.factorypath - -# OSX -.DS_Store - -# Vim -*.swp -*.swo - -# patch -*.orig -*.rej - -# Local environment -.env - -# Plugin directory -/.quarkus/cli/plugins/ diff --git a/completesolution/quarkus/copilot-demo/.mvn/wrapper/.gitignore b/completesolution/quarkus/copilot-demo/.mvn/wrapper/.gitignore deleted file mode 100644 index e72f5e8b..00000000 --- a/completesolution/quarkus/copilot-demo/.mvn/wrapper/.gitignore +++ /dev/null @@ -1 +0,0 @@ -maven-wrapper.jar diff --git a/completesolution/quarkus/copilot-demo/.mvn/wrapper/MavenWrapperDownloader.java b/completesolution/quarkus/copilot-demo/.mvn/wrapper/MavenWrapperDownloader.java deleted file mode 100644 index 84d1e60d..00000000 --- a/completesolution/quarkus/copilot-demo/.mvn/wrapper/MavenWrapperDownloader.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import java.io.IOException; -import java.io.InputStream; -import java.net.Authenticator; -import java.net.PasswordAuthentication; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; - -public final class MavenWrapperDownloader -{ - private static final String WRAPPER_VERSION = "3.2.0"; - - private static final boolean VERBOSE = Boolean.parseBoolean( System.getenv( "MVNW_VERBOSE" ) ); - - public static void main( String[] args ) - { - log( "Apache Maven Wrapper Downloader " + WRAPPER_VERSION ); - - if ( args.length != 2 ) - { - System.err.println( " - ERROR wrapperUrl or wrapperJarPath parameter missing" ); - System.exit( 1 ); - } - - try - { - log( " - Downloader started" ); - final URL wrapperUrl = new URL( args[0] ); - final String jarPath = args[1].replace( "..", "" ); // Sanitize path - final Path wrapperJarPath = Paths.get( jarPath ).toAbsolutePath().normalize(); - downloadFileFromURL( wrapperUrl, wrapperJarPath ); - log( "Done" ); - } - catch ( IOException e ) - { - System.err.println( "- Error downloading: " + e.getMessage() ); - if ( VERBOSE ) - { - e.printStackTrace(); - } - System.exit( 1 ); - } - } - - private static void downloadFileFromURL( URL wrapperUrl, Path wrapperJarPath ) - throws IOException - { - log( " - Downloading to: " + wrapperJarPath ); - if ( System.getenv( "MVNW_USERNAME" ) != null && System.getenv( "MVNW_PASSWORD" ) != null ) - { - final String username = System.getenv( "MVNW_USERNAME" ); - final char[] password = System.getenv( "MVNW_PASSWORD" ).toCharArray(); - Authenticator.setDefault( new Authenticator() - { - @Override - protected PasswordAuthentication getPasswordAuthentication() - { - return new PasswordAuthentication( username, password ); - } - } ); - } - try ( InputStream inStream = wrapperUrl.openStream() ) - { - Files.copy( inStream, wrapperJarPath, StandardCopyOption.REPLACE_EXISTING ); - } - log( " - Downloader complete" ); - } - - private static void log( String msg ) - { - if ( VERBOSE ) - { - System.out.println( msg ); - } - } - -} diff --git a/completesolution/quarkus/copilot-demo/.mvn/wrapper/maven-wrapper.properties b/completesolution/quarkus/copilot-demo/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index 70f4f50f..00000000 --- a/completesolution/quarkus/copilot-demo/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,18 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.8/apache-maven-3.8.8-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/completesolution/quarkus/copilot-demo/DOCKER.md b/completesolution/quarkus/copilot-demo/DOCKER.md deleted file mode 100644 index 67c8d517..00000000 --- a/completesolution/quarkus/copilot-demo/DOCKER.md +++ /dev/null @@ -1,26 +0,0 @@ -# Build a Quarkus native image - -## Build the native executable - -```bash -mvn clean package -Pnative -``` - -## Build the docker image using the Dockerfile.native-micro - -```bash -docker build -f Dockerfile.native-micro -t quarkus/quarkus-native-micro . -``` - -## Run the docker image - -```bash -docker run -i --rm -p 8080:8080 quarkus/quarkus-native-micro -``` - -## Test the application - -```bash -curl -v http://localhost:8080/hello -``` - diff --git a/completesolution/quarkus/copilot-demo/Dockerfile.native-micro b/completesolution/quarkus/copilot-demo/Dockerfile.native-micro deleted file mode 100644 index 1807ff8b..00000000 --- a/completesolution/quarkus/copilot-demo/Dockerfile.native-micro +++ /dev/null @@ -1,16 +0,0 @@ -#### -# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. -# It uses a micro base image, tuned for Quarkus native executables. -# -### -FROM quay.io/quarkus/quarkus-micro-image:2.0 -WORKDIR /work/ -RUN chown 1001 /work \ - && chmod "g+rwX" /work \ - && chown 1001:root /work -COPY --chown=1001:root target/*-runner /work/application - -EXPOSE 8080 -USER 1001 - -CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] diff --git a/completesolution/quarkus/copilot-demo/mvnw b/completesolution/quarkus/copilot-demo/mvnw deleted file mode 100644 index 8d937f4c..00000000 --- a/completesolution/quarkus/copilot-demo/mvnw +++ /dev/null @@ -1,308 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.2.0 -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir -# -# Optional ENV vars -# ----------------- -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files -# ---------------------------------------------------------------------------- - -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /usr/local/etc/mavenrc ] ; then - . /usr/local/etc/mavenrc - fi - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi - -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false -case "$(uname)" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME - else - JAVA_HOME="/Library/Java/Home"; export JAVA_HOME - fi - fi - ;; -esac - -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=$(java-config --jre-home) - fi -fi - -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$JAVA_HOME" ] && - JAVA_HOME=$(cygpath --unix "$JAVA_HOME") - [ -n "$CLASSPATH" ] && - CLASSPATH=$(cygpath --path --unix "$CLASSPATH") -fi - -# For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && - JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" -fi - -if [ -z "$JAVA_HOME" ]; then - javaExecutable="$(which javac)" - if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=$(which readlink) - if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then - if $darwin ; then - javaHome="$(dirname "\"$javaExecutable\"")" - javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" - else - javaExecutable="$(readlink -f "\"$javaExecutable\"")" - fi - javaHome="$(dirname "\"$javaExecutable\"")" - javaHome=$(expr "$javaHome" : '\(.*\)/bin') - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi - -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - else - JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" - fi -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi - -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 - fi - - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=$(cd "$wdir/.." || exit 1; pwd) - fi - # end of workaround - done - printf '%s' "$(cd "$basedir" || exit 1; pwd)" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - # Remove \r in case we run on Windows within Git Bash - # and check out the repository with auto CRLF management - # enabled. Otherwise, we may read lines that are delimited with - # \r\n and produce $'-Xarg\r' rather than -Xarg due to word - # splitting rules. - tr -s '\r\n' ' ' < "$1" - fi -} - -log() { - if [ "$MVNW_VERBOSE" = true ]; then - printf '%s\n' "$1" - fi -} - -BASE_DIR=$(find_maven_basedir "$(dirname "$0")") -if [ -z "$BASE_DIR" ]; then - exit 1; -fi - -MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR -log "$MAVEN_PROJECTBASEDIR" - -########################################################################################## -# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -# This allows using the maven wrapper in projects that prohibit checking in binary data. -########################################################################################## -wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" -if [ -r "$wrapperJarPath" ]; then - log "Found $wrapperJarPath" -else - log "Couldn't find $wrapperJarPath, downloading it ..." - - if [ -n "$MVNW_REPOURL" ]; then - wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - else - wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - fi - while IFS="=" read -r key value; do - # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) - safeValue=$(echo "$value" | tr -d '\r') - case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; - esac - done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" - log "Downloading from: $wrapperUrl" - - if $cygwin; then - wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") - fi - - if command -v wget > /dev/null; then - log "Found wget ... using wget" - [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" - else - wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" - fi - elif command -v curl > /dev/null; then - log "Found curl ... using curl" - [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" - else - curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" - fi - else - log "Falling back to using Java to download" - javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" - javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" - # For Cygwin, switch paths to Windows format before running javac - if $cygwin; then - javaSource=$(cygpath --path --windows "$javaSource") - javaClass=$(cygpath --path --windows "$javaClass") - fi - if [ -e "$javaSource" ]; then - if [ ! -e "$javaClass" ]; then - log " - Compiling MavenWrapperDownloader.java ..." - ("$JAVA_HOME/bin/javac" "$javaSource") - fi - if [ -e "$javaClass" ]; then - log " - Running MavenWrapperDownloader.java ..." - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" - fi - fi - fi -fi -########################################################################################## -# End of extension -########################################################################################## - -# If specified, validate the SHA-256 sum of the Maven wrapper jar file -wrapperSha256Sum="" -while IFS="=" read -r key value; do - case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; - esac -done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" -if [ -n "$wrapperSha256Sum" ]; then - wrapperSha256Result=false - if command -v sha256sum > /dev/null; then - if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then - wrapperSha256Result=true - fi - elif command -v shasum > /dev/null; then - if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then - wrapperSha256Result=true - fi - else - echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." - echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." - exit 1 - fi - if [ $wrapperSha256Result = false ]; then - echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 - echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 - echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 - exit 1 - fi -fi - -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$JAVA_HOME" ] && - JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") - [ -n "$CLASSPATH" ] && - CLASSPATH=$(cygpath --path --windows "$CLASSPATH") - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") -fi - -# Provide a "standardized" way to retrieve the CLI args that will -# work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" -export MAVEN_CMD_LINE_ARGS - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -# shellcheck disable=SC2086 # safe args -exec "$JAVACMD" \ - $MAVEN_OPTS \ - $MAVEN_DEBUG_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/completesolution/quarkus/copilot-demo/mvnw.cmd b/completesolution/quarkus/copilot-demo/mvnw.cmd deleted file mode 100644 index c4586b56..00000000 --- a/completesolution/quarkus/copilot-demo/mvnw.cmd +++ /dev/null @@ -1,205 +0,0 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.2.0 -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM set title of command window -title %0 -@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* -if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - -FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B -) - -@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -@REM This allows using the maven wrapper in projects that prohibit checking in binary data. -if exist %WRAPPER_JAR% ( - if "%MVNW_VERBOSE%" == "true" ( - echo Found %WRAPPER_JAR% - ) -) else ( - if not "%MVNW_REPOURL%" == "" ( - SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - ) - if "%MVNW_VERBOSE%" == "true" ( - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %WRAPPER_URL% - ) - - powershell -Command "&{"^ - "$webclient = new-object System.Net.WebClient;"^ - "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ - "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ - "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ - "}" - if "%MVNW_VERBOSE%" == "true" ( - echo Finished downloading %WRAPPER_JAR% - ) -) -@REM End of extension - -@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file -SET WRAPPER_SHA_256_SUM="" -FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B -) -IF NOT %WRAPPER_SHA_256_SUM%=="" ( - powershell -Command "&{"^ - "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ - "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ - " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ - " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ - " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ - " exit 1;"^ - "}"^ - "}" - if ERRORLEVEL 1 goto error -) - -@REM Provide a "standardized" way to retrieve the CLI args that will -@REM work with both Windows and non-Windows executions. -set MAVEN_CMD_LINE_ARGS=%* - -%MAVEN_JAVA_EXE% ^ - %JVM_CONFIG_MAVEN_PROPS% ^ - %MAVEN_OPTS% ^ - %MAVEN_DEBUG_OPTS% ^ - -classpath %WRAPPER_JAR% ^ - "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ - %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" -if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%"=="on" pause - -if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% - -cmd /C exit /B %ERROR_CODE% diff --git a/completesolution/quarkus/copilot-demo/pom.xml b/completesolution/quarkus/copilot-demo/pom.xml deleted file mode 100644 index 690d6632..00000000 --- a/completesolution/quarkus/copilot-demo/pom.xml +++ /dev/null @@ -1,129 +0,0 @@ - - - 4.0.0 - com.microsoft.hackathon.quarkus - copilot-demo - 1.0.0-SNAPSHOT - - 3.11.0 - 17 - UTF-8 - UTF-8 - quarkus-bom - io.quarkus.platform - 3.2.0.Final - true - 3.0.0 - - - - - ${quarkus.platform.group-id} - ${quarkus.platform.artifact-id} - ${quarkus.platform.version} - pom - import - - - - - - io.quarkus - quarkus-resteasy - - - io.quarkus - quarkus-arc - - - io.quarkus - quarkus-rest-client - - - io.quarkus - quarkus-rest-client-jackson - - - io.quarkus - quarkus-junit5 - test - - - io.rest-assured - rest-assured - test - - - - - - ${quarkus.platform.group-id} - quarkus-maven-plugin - ${quarkus.platform.version} - true - - - - build - generate-code - generate-code-tests - - - - - - maven-compiler-plugin - ${compiler-plugin.version} - - - -parameters - - - - - maven-surefire-plugin - ${surefire-plugin.version} - - - org.jboss.logmanager.LogManager - ${maven.home} - - - - - maven-failsafe-plugin - ${surefire-plugin.version} - - - - integration-test - verify - - - - ${project.build.directory}/${project.build.finalName}-runner - org.jboss.logmanager.LogManager - ${maven.home} - - - - - - - - - - native - - - native - - - - false - native - - - - diff --git a/completesolution/quarkus/copilot-demo/src/main/resources/META-INF/resources/colors.json b/completesolution/quarkus/copilot-demo/src/main/resources/META-INF/resources/colors.json deleted file mode 100644 index 9d8e8adb..00000000 --- a/completesolution/quarkus/copilot-demo/src/main/resources/META-INF/resources/colors.json +++ /dev/null @@ -1,56 +0,0 @@ - [ - { - "color": "black", - "category": "hue", - "type": "primary", - "code": { - "rgba": [0,0,0,1], - "hex": "#000000" - } - }, - { - "color": "white", - "category": "value", - "code": { - "rgba": [255,255,255,1], - "hex": "#FFFFFF" - } - }, - { - "color": "red", - "category": "hue", - "type": "primary", - "code": { - "rgba": [255,0,0,1], - "hex": "#FF0000" - } - }, - { - "color": "blue", - "category": "hue", - "type": "primary", - "code": { - "rgba": [0,0,255,1], - "hex": "#0000FF" - } - }, - { - "color": "yellow", - "category": "hue", - "type": "primary", - "code": { - "rgba": [255,255,0,1], - "hex": "#FFFF00" - } - }, - { - "color": "green", - "category": "hue", - "type": "secondary", - "code": { - "rgba": [0,255,0,1], - "hex": "#00FF00" - } - } - ] - \ No newline at end of file diff --git a/completesolution/quarkus/copilot-demo/src/main/resources/META-INF/resources/index.html b/completesolution/quarkus/copilot-demo/src/main/resources/META-INF/resources/index.html deleted file mode 100644 index 98c539e7..00000000 --- a/completesolution/quarkus/copilot-demo/src/main/resources/META-INF/resources/index.html +++ /dev/null @@ -1,284 +0,0 @@ - - - - - copilot-demo - 1.0.0-SNAPSHOT - - - -
-
-
- - - - - quarkus_logo_horizontal_rgb_1280px_reverse - - - - - - - - - - - - - - - - - - -
-
-
- -
-
-
-

You just made a Quarkus application.

-

This page is served by Quarkus.

- Visit the Dev UI -

This page: src/main/resources/META-INF/resources/index.html

-

App configuration: src/main/resources/application.properties

-

Static assets: src/main/resources/META-INF/resources/

-

Code: src/main/java

-

Dev UI V1: /q/dev-v1

-

Generated starter code:

-
    -
  • - RESTEasy JAX-RS Easily start your RESTful Web Services -
    @Path: /hello -
    Related guide -
  • - -
-
-
-

Selected extensions

-
    -
  • RESTEasy Classic (guide)
  • -
-
Documentation
-

Practical step-by-step guides to help you achieve a specific goal. Use them to help get your work - done.

-
Set up your IDE
-

Everyone has a favorite IDE they like to use to code. Learn how to configure yours to maximize your - Quarkus productivity.

-
-
-
- - diff --git a/completesolution/quarkus/copilot-demo/src/main/resources/application.properties b/completesolution/quarkus/copilot-demo/src/main/resources/application.properties deleted file mode 100644 index e69de29b..00000000 diff --git a/completesolution/quarkus/copilot-demo/src/main/resources/colors.json b/completesolution/quarkus/copilot-demo/src/main/resources/colors.json deleted file mode 100644 index 9d8e8adb..00000000 --- a/completesolution/quarkus/copilot-demo/src/main/resources/colors.json +++ /dev/null @@ -1,56 +0,0 @@ - [ - { - "color": "black", - "category": "hue", - "type": "primary", - "code": { - "rgba": [0,0,0,1], - "hex": "#000000" - } - }, - { - "color": "white", - "category": "value", - "code": { - "rgba": [255,255,255,1], - "hex": "#FFFFFF" - } - }, - { - "color": "red", - "category": "hue", - "type": "primary", - "code": { - "rgba": [255,0,0,1], - "hex": "#FF0000" - } - }, - { - "color": "blue", - "category": "hue", - "type": "primary", - "code": { - "rgba": [0,0,255,1], - "hex": "#0000FF" - } - }, - { - "color": "yellow", - "category": "hue", - "type": "primary", - "code": { - "rgba": [255,255,0,1], - "hex": "#FFFF00" - } - }, - { - "color": "green", - "category": "hue", - "type": "secondary", - "code": { - "rgba": [0,255,0,1], - "hex": "#00FF00" - } - } - ] - \ No newline at end of file diff --git a/completesolution/quarkus/copilot-demo/src/test/java/com/microsoft/hackathon/quarkus/DemoResourceIT.java b/completesolution/quarkus/copilot-demo/src/test/java/com/microsoft/hackathon/quarkus/DemoResourceIT.java deleted file mode 100644 index 9b7c5307..00000000 --- a/completesolution/quarkus/copilot-demo/src/test/java/com/microsoft/hackathon/quarkus/DemoResourceIT.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.microsoft.hackathon.quarkus; - -import io.quarkus.test.junit.QuarkusIntegrationTest; - -@QuarkusIntegrationTest -public class DemoResourceIT extends DemoResourceTest { - // Execute the same tests but in packaged mode. -} diff --git a/completesolution/quarkus/copilot-demo/src/test/java/com/microsoft/hackathon/quarkus/DemoResourceTest.java b/completesolution/quarkus/copilot-demo/src/test/java/com/microsoft/hackathon/quarkus/DemoResourceTest.java deleted file mode 100644 index 31a9e3d7..00000000 --- a/completesolution/quarkus/copilot-demo/src/test/java/com/microsoft/hackathon/quarkus/DemoResourceTest.java +++ /dev/null @@ -1,200 +0,0 @@ -package com.microsoft.hackathon.quarkus; - -import io.quarkus.test.junit.QuarkusTest; -import org.junit.jupiter.api.Test; - -import static io.restassured.RestAssured.given; -import static org.hamcrest.CoreMatchers.is; - -@QuarkusTest -public class DemoResourceTest { - - @Test - public void testHelloEndpoint() { - given() - .when().get("/hello?key=world") - .then() - .statusCode(200) - .body(is("hello world")); - } - - @Test - public void testHelloEndpointNoKey() { - given() - .when().get("/hello") - .then() - .statusCode(200) - .body(is("key not passed")); - } - - @Test - public void testDiffDatesEndpoint() { - given() - .when().get("/diffdates?date1=01-01-2021&date2=01-02-2021") - .then() - .statusCode(200) - .body(is("31")); - } - - @Test - public void testDiffDatesEndpointInvalidDate() { - given() - .when().get("/diffdates?date1=01-01201&date2=01-02-2021") - .then() - .statusCode(200) - .body(is("invalid date format")); - } - - @Test - public void testDiffDatesEndpointNoDate1() { - given() - .when().get("/diffdates?date2=01-02-2021") - .then() - .statusCode(500); - } - - @Test - public void testDiffDatesEndpointNoDate2() { - given() - .when().get("/diffdates?date1=01-02-2021") - .then() - .statusCode(500); - } - - @Test - public void testDiffDatesEndpointNoDates() { - given() - .when().get("/diffdates") - .then() - .statusCode(500); - } - - @Test - public void testValidatePhoneEndpoint() { - given() - .when().get("/validatephone?phone=+34666666666") - .then() - .statusCode(200) - .body(is("true")); - - given() - .when().get("/validatephone?phone=+34766666666") - .then() - .statusCode(200) - .body(is("true")); - - given() - .when().get("/validatephone?phone=+34966666666") - .then() - .statusCode(200) - .body(is("true")); - } - - @Test - public void testValidatePhoneEndpointInvalidPhone() { - given() - .when().get("/validatephone?phone=+3466666666") - .then() - .statusCode(200) - .body(is("false")); - } - - @Test - public void testValidatePhoneEndpointNoPhone() { - given() - .when().get("/validatephone") - .then() - .statusCode(500); - } - - - @Test - public void testValidatePhoneEndpointNoPrefix() { - given() - .when().get("/validatephone?phone=666666666") - .then() - .statusCode(200) - .body(is("false")); - } - - @Test - public void testValidateDNIEndpoint () { - given() - .when().get("/validatedni?dni=12345678Z") - .then() - .statusCode(200) - .body(is("true")); - } - - @Test - public void testValidateDNIEndpointInvalidDNI () { - given() - .when().get("/validatedni?dni=12345678") - .then() - .statusCode(200) - .body(is("false")); - } - - @Test - public void testValidateDNIEndpointNoDNI () { - given() - .when().get("/validatedni") - .then() - .statusCode(500); - } - - @Test - public void testGetHexColorEndpoint () { - given() - .when().get("/hexcolor?name=red") - .then() - .statusCode(200) - .body(is("#FF0000")); - } - - @Test - public void testGetHexColorEndpointInvalidColor () { - given() - .when().get("/hexcolor?name=red1") - .then() - .statusCode(404); - } - - @Test - public void testGetHexColorEndpointNoColor () { - given() - .when().get("/hexcolor") - .then() - .statusCode(500); - } - - @Test - public void testChuckNorrisEndpoint () { - given() - .when().get("/chucknorris") - .then() - .statusCode(200); - } - - @Test - public void testParseUrlEndpoint () { - given() - .when().get("/parseurl?url=https://learn.microsoft.com/en-us/azure/aks/concepts-clusters-workloads?source=recommendations") - .then() - .statusCode(200) - .body("protocol", is("https")) - .body("host", is("learn.microsoft.com")) - .body("port", is(-1)) - .body("path", is("/en-us/azure/aks/concepts-clusters-workloads")) - .body("query", is("source=recommendations")); - } - - @Test - public void testParseUrlEndpointNoUrl () { - given() - .when().get("/parseurl") - .then() - .statusCode(500); - } - -} \ No newline at end of file diff --git a/completesolution/springboot/copilot-demo/.gitignore b/completesolution/springboot/copilot-demo/.gitignore deleted file mode 100644 index 549e00a2..00000000 --- a/completesolution/springboot/copilot-demo/.gitignore +++ /dev/null @@ -1,33 +0,0 @@ -HELP.md -target/ -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/**/target/ -!**/src/test/**/target/ - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ -build/ -!**/src/main/**/build/ -!**/src/test/**/build/ - -### VS Code ### -.vscode/ diff --git a/completesolution/springboot/copilot-demo/.mvn/wrapper/maven-wrapper.jar b/completesolution/springboot/copilot-demo/.mvn/wrapper/maven-wrapper.jar deleted file mode 100644 index cb28b0e3..00000000 Binary files a/completesolution/springboot/copilot-demo/.mvn/wrapper/maven-wrapper.jar and /dev/null differ diff --git a/completesolution/springboot/copilot-demo/.mvn/wrapper/maven-wrapper.properties b/completesolution/springboot/copilot-demo/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index b27a13f1..00000000 --- a/completesolution/springboot/copilot-demo/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,2 +0,0 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.2/apache-maven-3.9.2-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/completesolution/springboot/copilot-demo/Dockerfile b/completesolution/springboot/copilot-demo/Dockerfile deleted file mode 100644 index f83e60ae..00000000 --- a/completesolution/springboot/copilot-demo/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -# Build a java application image based on openjdk 17 and run it on port 8080 -FROM openjdk:17-jdk-alpine -EXPOSE 8080 -COPY target/*.jar app.jar -ENTRYPOINT ["java","-jar","/app.jar"] \ No newline at end of file diff --git a/completesolution/springboot/copilot-demo/mvnw b/completesolution/springboot/copilot-demo/mvnw deleted file mode 100644 index 66df2854..00000000 --- a/completesolution/springboot/copilot-demo/mvnw +++ /dev/null @@ -1,308 +0,0 @@ -#!/bin/sh -# ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# ---------------------------------------------------------------------------- - -# ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.2.0 -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir -# -# Optional ENV vars -# ----------------- -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files -# ---------------------------------------------------------------------------- - -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /usr/local/etc/mavenrc ] ; then - . /usr/local/etc/mavenrc - fi - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi - -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false -case "$(uname)" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME - else - JAVA_HOME="/Library/Java/Home"; export JAVA_HOME - fi - fi - ;; -esac - -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=$(java-config --jre-home) - fi -fi - -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$JAVA_HOME" ] && - JAVA_HOME=$(cygpath --unix "$JAVA_HOME") - [ -n "$CLASSPATH" ] && - CLASSPATH=$(cygpath --path --unix "$CLASSPATH") -fi - -# For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && - JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" -fi - -if [ -z "$JAVA_HOME" ]; then - javaExecutable="$(which javac)" - if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=$(which readlink) - if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then - if $darwin ; then - javaHome="$(dirname "\"$javaExecutable\"")" - javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" - else - javaExecutable="$(readlink -f "\"$javaExecutable\"")" - fi - javaHome="$(dirname "\"$javaExecutable\"")" - javaHome=$(expr "$javaHome" : '\(.*\)/bin') - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi - -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - else - JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" - fi -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi - -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 - fi - - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=$(cd "$wdir/.." || exit 1; pwd) - fi - # end of workaround - done - printf '%s' "$(cd "$basedir" || exit 1; pwd)" -} - -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - # Remove \r in case we run on Windows within Git Bash - # and check out the repository with auto CRLF management - # enabled. Otherwise, we may read lines that are delimited with - # \r\n and produce $'-Xarg\r' rather than -Xarg due to word - # splitting rules. - tr -s '\r\n' ' ' < "$1" - fi -} - -log() { - if [ "$MVNW_VERBOSE" = true ]; then - printf '%s\n' "$1" - fi -} - -BASE_DIR=$(find_maven_basedir "$(dirname "$0")") -if [ -z "$BASE_DIR" ]; then - exit 1; -fi - -MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR -log "$MAVEN_PROJECTBASEDIR" - -########################################################################################## -# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -# This allows using the maven wrapper in projects that prohibit checking in binary data. -########################################################################################## -wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" -if [ -r "$wrapperJarPath" ]; then - log "Found $wrapperJarPath" -else - log "Couldn't find $wrapperJarPath, downloading it ..." - - if [ -n "$MVNW_REPOURL" ]; then - wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - else - wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - fi - while IFS="=" read -r key value; do - # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) - safeValue=$(echo "$value" | tr -d '\r') - case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; - esac - done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" - log "Downloading from: $wrapperUrl" - - if $cygwin; then - wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") - fi - - if command -v wget > /dev/null; then - log "Found wget ... using wget" - [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" - else - wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" - fi - elif command -v curl > /dev/null; then - log "Found curl ... using curl" - [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" - else - curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" - fi - else - log "Falling back to using Java to download" - javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" - javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" - # For Cygwin, switch paths to Windows format before running javac - if $cygwin; then - javaSource=$(cygpath --path --windows "$javaSource") - javaClass=$(cygpath --path --windows "$javaClass") - fi - if [ -e "$javaSource" ]; then - if [ ! -e "$javaClass" ]; then - log " - Compiling MavenWrapperDownloader.java ..." - ("$JAVA_HOME/bin/javac" "$javaSource") - fi - if [ -e "$javaClass" ]; then - log " - Running MavenWrapperDownloader.java ..." - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" - fi - fi - fi -fi -########################################################################################## -# End of extension -########################################################################################## - -# If specified, validate the SHA-256 sum of the Maven wrapper jar file -wrapperSha256Sum="" -while IFS="=" read -r key value; do - case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; - esac -done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" -if [ -n "$wrapperSha256Sum" ]; then - wrapperSha256Result=false - if command -v sha256sum > /dev/null; then - if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then - wrapperSha256Result=true - fi - elif command -v shasum > /dev/null; then - if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then - wrapperSha256Result=true - fi - else - echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." - echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." - exit 1 - fi - if [ $wrapperSha256Result = false ]; then - echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 - echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 - echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 - exit 1 - fi -fi - -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" - -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$JAVA_HOME" ] && - JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") - [ -n "$CLASSPATH" ] && - CLASSPATH=$(cygpath --path --windows "$CLASSPATH") - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") -fi - -# Provide a "standardized" way to retrieve the CLI args that will -# work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" -export MAVEN_CMD_LINE_ARGS - -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -# shellcheck disable=SC2086 # safe args -exec "$JAVACMD" \ - $MAVEN_OPTS \ - $MAVEN_DEBUG_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/completesolution/springboot/copilot-demo/mvnw.cmd b/completesolution/springboot/copilot-demo/mvnw.cmd deleted file mode 100644 index 95ba6f54..00000000 --- a/completesolution/springboot/copilot-demo/mvnw.cmd +++ /dev/null @@ -1,205 +0,0 @@ -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM https://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.2.0 -@REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir -@REM -@REM Optional ENV vars -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files -@REM ---------------------------------------------------------------------------- - -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM set title of command window -title %0 -@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* -if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - -FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B -) - -@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -@REM This allows using the maven wrapper in projects that prohibit checking in binary data. -if exist %WRAPPER_JAR% ( - if "%MVNW_VERBOSE%" == "true" ( - echo Found %WRAPPER_JAR% - ) -) else ( - if not "%MVNW_REPOURL%" == "" ( - SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - ) - if "%MVNW_VERBOSE%" == "true" ( - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %WRAPPER_URL% - ) - - powershell -Command "&{"^ - "$webclient = new-object System.Net.WebClient;"^ - "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ - "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ - "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ - "}" - if "%MVNW_VERBOSE%" == "true" ( - echo Finished downloading %WRAPPER_JAR% - ) -) -@REM End of extension - -@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file -SET WRAPPER_SHA_256_SUM="" -FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B -) -IF NOT %WRAPPER_SHA_256_SUM%=="" ( - powershell -Command "&{"^ - "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ - "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ - " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ - " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ - " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ - " exit 1;"^ - "}"^ - "}" - if ERRORLEVEL 1 goto error -) - -@REM Provide a "standardized" way to retrieve the CLI args that will -@REM work with both Windows and non-Windows executions. -set MAVEN_CMD_LINE_ARGS=%* - -%MAVEN_JAVA_EXE% ^ - %JVM_CONFIG_MAVEN_PROPS% ^ - %MAVEN_OPTS% ^ - %MAVEN_DEBUG_OPTS% ^ - -classpath %WRAPPER_JAR% ^ - "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ - %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" -if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%"=="on" pause - -if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% - -cmd /C exit /B %ERROR_CODE% diff --git a/completesolution/springboot/copilot-demo/pom.xml b/completesolution/springboot/copilot-demo/pom.xml deleted file mode 100644 index 639d30ae..00000000 --- a/completesolution/springboot/copilot-demo/pom.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.1.1 - - - com.microsoft.hackathon - copilot-demo - 0.0.1-SNAPSHOT - copilot-demo - Demo project for Spring Boot - - 17 - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.json - json - 20231013 - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - 2.3.0 - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/completesolution/springboot/copilot-demo/src/main/java/com/microsoft/hackathon/copilotdemo/CopilotDemoApplication.java b/completesolution/springboot/copilot-demo/src/main/java/com/microsoft/hackathon/copilotdemo/CopilotDemoApplication.java deleted file mode 100644 index a828eaf7..00000000 --- a/completesolution/springboot/copilot-demo/src/main/java/com/microsoft/hackathon/copilotdemo/CopilotDemoApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.microsoft.hackathon.copilotdemo; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class CopilotDemoApplication { - - public static void main(String[] args) { - SpringApplication.run(CopilotDemoApplication.class, args); - } - -} diff --git a/completesolution/springboot/copilot-demo/src/main/resources/application.properties b/completesolution/springboot/copilot-demo/src/main/resources/application.properties deleted file mode 100644 index 8b137891..00000000 --- a/completesolution/springboot/copilot-demo/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ - diff --git a/completesolution/springboot/copilot-demo/src/main/resources/colors.json b/completesolution/springboot/copilot-demo/src/main/resources/colors.json deleted file mode 100644 index 9d8e8adb..00000000 --- a/completesolution/springboot/copilot-demo/src/main/resources/colors.json +++ /dev/null @@ -1,56 +0,0 @@ - [ - { - "color": "black", - "category": "hue", - "type": "primary", - "code": { - "rgba": [0,0,0,1], - "hex": "#000000" - } - }, - { - "color": "white", - "category": "value", - "code": { - "rgba": [255,255,255,1], - "hex": "#FFFFFF" - } - }, - { - "color": "red", - "category": "hue", - "type": "primary", - "code": { - "rgba": [255,0,0,1], - "hex": "#FF0000" - } - }, - { - "color": "blue", - "category": "hue", - "type": "primary", - "code": { - "rgba": [0,0,255,1], - "hex": "#0000FF" - } - }, - { - "color": "yellow", - "category": "hue", - "type": "primary", - "code": { - "rgba": [255,255,0,1], - "hex": "#FFFF00" - } - }, - { - "color": "green", - "category": "hue", - "type": "secondary", - "code": { - "rgba": [0,255,0,1], - "hex": "#00FF00" - } - } - ] - \ No newline at end of file diff --git a/completesolution/springboot/copilot-demo/src/test/java/com/microsoft/hackathon/copilotdemo/CopilotDemoApplicationTests.java b/completesolution/springboot/copilot-demo/src/test/java/com/microsoft/hackathon/copilotdemo/CopilotDemoApplicationTests.java deleted file mode 100644 index 45048061..00000000 --- a/completesolution/springboot/copilot-demo/src/test/java/com/microsoft/hackathon/copilotdemo/CopilotDemoApplicationTests.java +++ /dev/null @@ -1,168 +0,0 @@ -package com.microsoft.hackathon.copilotdemo; - -import static org.mockito.Mockito.mock; - -import org.hamcrest.Matchers; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import org.springframework.test.web.servlet.result.MockMvcResultMatchers;; - - -@SpringBootTest() -@AutoConfigureMockMvc -class CopilotDemoApplicationTests { - - @Autowired - private MockMvc mockMvc; - - @Test - void hello() throws Exception { - mockMvc.perform(MockMvcRequestBuilders.get("/hello?key=world")) - .andExpect(MockMvcResultMatchers.status().isOk()) - .andExpect(MockMvcResultMatchers.content().string("hello world")); - } - - @Test - void helloNoKey() throws Exception { - mockMvc.perform(MockMvcRequestBuilders.get("/hello")) - .andExpect(MockMvcResultMatchers.status().isOk()) - .andExpect(MockMvcResultMatchers.content().string("key not passed")); - } - - @Test - void diffdates() throws Exception { - mockMvc.perform(MockMvcRequestBuilders.get("/diffdates?date1=01-01-2021&date2=01-02-2021")) - .andExpect(MockMvcResultMatchers.status().isOk()) - .andExpect(MockMvcResultMatchers.content().string("difference in days: 31")); - } - - @Test - void diffdatesNoDate1() throws Exception { - mockMvc.perform(MockMvcRequestBuilders.get("/diffdates?date2=01-02-2021")) - .andExpect(MockMvcResultMatchers.status().isOk()) - .andExpect(MockMvcResultMatchers.content().string("date not passed")); - } - - @Test - void diffdatesNoDate2() throws Exception { - mockMvc.perform(MockMvcRequestBuilders.get("/diffdates?date1=01-01-2021")) - .andExpect(MockMvcResultMatchers.status().isOk()) - .andExpect(MockMvcResultMatchers.content().string("date not passed")); - } - - @Test - void validatephone() throws Exception { - mockMvc.perform(MockMvcRequestBuilders.get("/validatephone?phone=+34666666666")) - .andExpect(MockMvcResultMatchers.status().isOk()) - .andExpect(MockMvcResultMatchers.content().string("true")); - - mockMvc.perform(MockMvcRequestBuilders.get("/validatephone?phone=+34766666666")) - .andExpect(MockMvcResultMatchers.status().isOk()) - .andExpect(MockMvcResultMatchers.content().string("true")); - - mockMvc.perform(MockMvcRequestBuilders.get("/validatephone?phone=+34966666666")) - .andExpect(MockMvcResultMatchers.status().isOk()) - .andExpect(MockMvcResultMatchers.content().string("true")); - - mockMvc.perform(MockMvcRequestBuilders.get("/validatephone?phone=+3466666666")) - .andExpect(MockMvcResultMatchers.status().isOk()) - .andExpect(MockMvcResultMatchers.content().string("false")); - - mockMvc.perform(MockMvcRequestBuilders.get("/validatephone?phone=+346666666666")) - .andExpect(MockMvcResultMatchers.status().isOk()) - .andExpect(MockMvcResultMatchers.content().string("false")); - - mockMvc.perform(MockMvcRequestBuilders.get("/validatephone?phone=+3466666666a")) - .andExpect(MockMvcResultMatchers.status().isOk()) - .andExpect(MockMvcResultMatchers.content().string("false")); - - mockMvc.perform(MockMvcRequestBuilders.get("/validatephone?phone=+34866666666")) - .andExpect(MockMvcResultMatchers.status().isOk()) - .andExpect(MockMvcResultMatchers.content().string("false")); - } - - @Test - void validatephoneNoPhone() throws Exception { - mockMvc.perform(MockMvcRequestBuilders.get("/validatephone")) - .andExpect(MockMvcResultMatchers.status().isOk()) - .andExpect(MockMvcResultMatchers.content().string("false")); - } - - @Test - void validatedni() throws Exception { - mockMvc.perform(MockMvcRequestBuilders.get("/validatedni?dni=12345678A")) - .andExpect(MockMvcResultMatchers.status().isOk()) - .andExpect(MockMvcResultMatchers.content().string("true")); - - mockMvc.perform(MockMvcRequestBuilders.get("/validatedni?dni=12345678a")) - .andExpect(MockMvcResultMatchers.status().isOk()) - .andExpect(MockMvcResultMatchers.content().string("false")); - - mockMvc.perform(MockMvcRequestBuilders.get("/validatedni?dni=1234567A")) - .andExpect(MockMvcResultMatchers.status().isOk()) - .andExpect(MockMvcResultMatchers.content().string("false")); - - mockMvc.perform(MockMvcRequestBuilders.get("/validatedni?dni=123456789A")) - .andExpect(MockMvcResultMatchers.status().isOk()) - .andExpect(MockMvcResultMatchers.content().string("false")); - - mockMvc.perform(MockMvcRequestBuilders.get("/validatedni?dni=12345678")) - .andExpect(MockMvcResultMatchers.status().isOk()) - .andExpect(MockMvcResultMatchers.content().string("false")); - - } - - @Test - void validatedniNoDni() throws Exception { - mockMvc.perform(MockMvcRequestBuilders.get("/validatedni")) - .andExpect(MockMvcResultMatchers.status().isOk()) - .andExpect(MockMvcResultMatchers.content().string("false")); - } - - // test for /color/{color} endpoint - @Test - void color() throws Exception { - mockMvc.perform(MockMvcRequestBuilders.get("/color/red")) - .andExpect(MockMvcResultMatchers.status().isOk()) - .andExpect(MockMvcResultMatchers.content().string("#FF0000")); - } - - @Test - void colorNotFound() throws Exception { - mockMvc.perform(MockMvcRequestBuilders.get("/color/purple")) - .andExpect(MockMvcResultMatchers.status().isNotFound()); - } - - @Test - void joke() throws Exception{ - mockMvc.perform(MockMvcRequestBuilders.get("/joke")) - .andExpect(MockMvcResultMatchers.status().isOk()) - // check that content is a string - .andExpect(MockMvcResultMatchers.content().string(Matchers.any(String.class))); - - } - - @Test - void parseUrl() throws Exception{ - mockMvc.perform(MockMvcRequestBuilders.get("/parseurl?url=https://learn.microsoft.com/en-us/azure/aks/concepts-clusters-workloads?source=recommendations")) - .andExpect(MockMvcResultMatchers.status().isOk()) - // validate json fields - .andExpect(MockMvcResultMatchers.jsonPath("$.protocol").value("https")) - .andExpect(MockMvcResultMatchers.jsonPath("$.host").value("learn.microsoft.com")) - .andExpect(MockMvcResultMatchers.jsonPath("$.path").value("/en-us/azure/aks/concepts-clusters-workloads")) - .andExpect(MockMvcResultMatchers.jsonPath("$.query").value("source=recommendations")); - - } - - @Test - void parseUrlNoUrl() throws Exception{ - mockMvc.perform(MockMvcRequestBuilders.get("/parseurl")) - .andExpect(MockMvcResultMatchers.status().isOk()) - .andExpect(MockMvcResultMatchers.content().string("url not passed")); - } - -} diff --git a/exercisefiles/dataengineer/COVID19 Worldwide Testing Data.ipynb b/exercisefiles/dataengineer/COVID19 Worldwide Testing Data.ipynb new file mode 100644 index 00000000..0f57c788 --- /dev/null +++ b/exercisefiles/dataengineer/COVID19 Worldwide Testing Data.ipynb @@ -0,0 +1,673 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "83ed0780", + "metadata": {}, + "source": [ + "# COVID19 Worldwide Testing Data\n", + "\n", + "This notebook loads and performs an initial exploratory analysis of the `tested_worldwide.csv` dataset located in `exercisefiles/dataengineer/`.\n", + "\n", + "Tasks included:\n", + "- Load the CSV into a pandas DataFrame\n", + "- Display `head()`, `info()`, and `describe()`\n", + "- Basic cleaning (date parsing, missing values)\n", + "- A simple plot of tests over time for a selected country and a world summary plot" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "c3d3e6fd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Date Country_Region Province_State positive active hospitalized \\\n", + "0 2020-01-16 Iceland All States 3.0 NaN NaN \n", + "1 2020-01-17 Iceland All States 4.0 NaN NaN \n", + "2 2020-01-18 Iceland All States 7.0 NaN NaN \n", + "3 2020-01-20 South Korea All States 1.0 NaN NaN \n", + "4 2020-01-22 United States All States 0.0 NaN NaN \n", + "\n", + " hospitalizedCurr recovered death total_tested daily_tested \\\n", + "0 NaN NaN NaN NaN NaN \n", + "1 NaN NaN NaN NaN NaN \n", + "2 NaN NaN NaN NaN NaN \n", + "3 NaN NaN NaN 4.0 NaN \n", + "4 NaN NaN 0.0 0.0 NaN \n", + "\n", + " daily_positive \n", + "0 NaN \n", + "1 1.0 \n", + "2 3.0 \n", + "3 NaN \n", + "4 NaN \n" + ] + } + ], + "source": [ + "# Import required libraries (including pandas, numpy, plotting and display helpers)\n", + "import pandas as pd\n", + "\n", + "df = pd.read_csv('./tested_worldwide.csv')\n", + "\n", + "print(df.head())\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "34eeba80", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(27641, 12)" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "ec211eff", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Date object\n", + "Country_Region object\n", + "Province_State object\n", + "positive float64\n", + "active float64\n", + "hospitalized float64\n", + "hospitalizedCurr float64\n", + "recovered float64\n", + "death float64\n", + "total_tested float64\n", + "daily_tested float64\n", + "daily_positive float64\n", + "dtype: object" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df.dtypes" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "38b582df", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "the dataset has 27641 rows and 12 columns.\n" + ] + } + ], + "source": [ + "print(f'the dataset has {df.shape[0]} rows and {df.shape[1]} columns.')" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "0f2bf450", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Date 0\n", + "Country_Region 0\n", + "Province_State 0\n", + "positive 4242\n", + "active 9833\n", + "hospitalized 19231\n", + "hospitalizedCurr 13080\n", + "recovered 9626\n", + "death 4010\n", + "total_tested 912\n", + "daily_tested 1174\n", + "daily_positive 4557\n", + "dtype: int64\n" + ] + } + ], + "source": [ + "print(df.isnull().sum())" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "f07ae6e5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Date 297\n", + "Country_Region 147\n", + "Province_State 81\n", + "positive 14998\n", + "active 9554\n", + "hospitalized 4862\n", + "hospitalizedCurr 2904\n", + "recovered 9183\n", + "death 5641\n", + "total_tested 23610\n", + "daily_tested 13375\n", + "daily_positive 3440\n", + "dtype: int64\n" + ] + } + ], + "source": [ + "unique_values = df.nunique()\n", + "print(unique_values)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "378c8425", + "metadata": {}, + "outputs": [], + "source": [ + "data = df[['Country_Region', 'positive', 'total_tested']]" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "521c3d49", + "metadata": {}, + "outputs": [], + "source": [ + "df.rename(columns={'Country_Region': 'Country', 'positive': 'Positive Cases', 'total_tested': 'Total Tested'}, inplace=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "5f1bfd41", + "metadata": {}, + "outputs": [], + "source": [ + "df.dropna(inplace=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "3e96101c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
DateCountryProvince_StatePositive CasesactivehospitalizedhospitalizedCurrrecovereddeathTotal Testeddaily_testeddaily_positiveTesting Rate
28142020-03-25United StatesMinnesota502380.035.026.0121.01.0116905696.058.04.294269
29092020-03-26United StatesIowa179163.046.031.015.01.0275734.034.06.492564
30222020-03-27United StatesIowa235214.050.032.018.03.039751218.056.05.911950
30302020-03-27United StatesMinnesota640460.051.034.0176.04.0142451076.075.04.492804
30392020-03-27United StatesNew York4463542071.08732.06481.02045.0519.014575323649.07377.030.623726
\n", + "
" + ], + "text/plain": [ + " Date Country Province_State Positive Cases active \\\n", + "2814 2020-03-25 United States Minnesota 502 380.0 \n", + "2909 2020-03-26 United States Iowa 179 163.0 \n", + "3022 2020-03-27 United States Iowa 235 214.0 \n", + "3030 2020-03-27 United States Minnesota 640 460.0 \n", + "3039 2020-03-27 United States New York 44635 42071.0 \n", + "\n", + " hospitalized hospitalizedCurr recovered death Total Tested \\\n", + "2814 35.0 26.0 121.0 1.0 11690 \n", + "2909 46.0 31.0 15.0 1.0 2757 \n", + "3022 50.0 32.0 18.0 3.0 3975 \n", + "3030 51.0 34.0 176.0 4.0 14245 \n", + "3039 8732.0 6481.0 2045.0 519.0 145753 \n", + "\n", + " daily_tested daily_positive Testing Rate \n", + "2814 5696.0 58.0 4.294269 \n", + "2909 34.0 34.0 6.492564 \n", + "3022 1218.0 56.0 5.911950 \n", + "3030 1076.0 75.0 4.492804 \n", + "3039 23649.0 7377.0 30.623726 " + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df['Positive Cases'] = df['Positive Cases'].astype(int)\n", + "df['Total Tested'] = df['Total Tested'].astype(int)\n", + "df['Testing Rate'] = (df['Positive Cases'] / df['Total Tested']) * 100\n", + "df['Country'] = df['Country'].astype(str)\n", + "df.head()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "f7a60d61", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Date 0\n", + "Country 0\n", + "Province_State 0\n", + "Positive Cases 0\n", + "active 0\n", + "hospitalized 0\n", + "hospitalizedCurr 0\n", + "recovered 0\n", + "death 0\n", + "Total Tested 0\n", + "daily_tested 0\n", + "daily_positive 0\n", + "Testing Rate 0\n", + "dtype: int64\n" + ] + } + ], + "source": [ + "missing_values = df.isnull().sum()\n", + "print(missing_values)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "6099972b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Country Positive Cases\n", + "0 United States 419927821\n" + ] + } + ], + "source": [ + "total_positive_cases = df.groupby('Country')['Positive Cases'].sum().reset_index()\n", + "total_positive_cases = total_positive_cases.sort_values(by='Positive Cases', ascending=False)\n", + "print(total_positive_cases.head(10))" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "ac7586e0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Country Positive Cases\n", + "0 United States 419927821\n" + ] + } + ], + "source": [ + "df_total_positive_cases = df.groupby('Country')['Positive Cases'].sum().reset_index()\n", + "df_total_positive_cases = df_total_positive_cases.sort_values(by='Positive Cases', ascending=False)\n", + "print(df_total_positive_cases.head(10))" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "24e2f051", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Country Total Tested\n", + "0 United States 5521670375\n" + ] + } + ], + "source": [ + "top_tested_countries = df.groupby('Country')['Total Tested'].sum().reset_index()\n", + "top_tested_countries = top_tested_countries.sort_values(by='Total Tested', ascending=False)\n", + "print(top_tested_countries.head(10))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0b1e77f9", + "metadata": {}, + "outputs": [], + "source": [ + "print(top_tested_countries.head(10))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "341ee4ac", + "metadata": {}, + "outputs": [], + "source": [ + "data['positive_test_ratio'] = data['positive'] / data['total_tested']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "06581c7e", + "metadata": {}, + "outputs": [], + "source": [ + "data_sorted = data.copy()\n", + "data_sorted['positive_test_ratio'] = data_sorted['positive'] / data_sorted['total_tested']\n", + "data_sorted = data_sorted.sort_values(by='positive_test_ratio', ascending=False)\n", + "print(data_sorted.head())" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "d6c6a363", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Country_Region\n", + "Greece inf\n", + "Canada inf\n", + "Tanzania 0.780675\n", + "Name: positive_test_ratio, dtype: float64\n" + ] + } + ], + "source": [ + "# Calculate positive test ratio for each country in 'data'\n", + "country_ratio = data.dropna(subset=['positive', 'total_tested']).copy()\n", + "country_ratio['positive_test_ratio'] = country_ratio['positive'] / country_ratio['total_tested']\n", + "\n", + "# Group by country and get the mean ratio, then sort and display top 3\n", + "top3_ratio = country_ratio.groupby('Country_Region')['positive_test_ratio'].mean().sort_values(ascending=False).head(3)\n", + "print(top3_ratio)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "76a5d4c7", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Matplotlib is building the font cache; this may take a moment.\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAxYAAAHqCAYAAACZcdjsAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAQsNJREFUeJzt3Ql8TPf+//FPhCSWJoii0lRqqX3fGqqqlriU6mqpCrVc9KLyby9KqS4orUaLai3dVKlSFE0XSy+XUnRRRatUUhqiSAhNSOb/+Hx/j5k7iUlknCSTTF7Px+M0mTPnzPnOnEmd9/luPjabzSYAAAAAYEExKzsDAAAAgCJYAAAAALCMYAEAAADAMoIFAAAAAMsIFgAAAAAsI1gAAAAAsIxgAQAAAMAyggUAAAAAywgWAAAAACwjWACAl9iyZYv4+PiYn/nl2WefNcc8ffq0eJN33nnHvK/ff/+9QH7u+B8+f6DgIFgAuCb9RzsnS17/w37p0iUZNGiQ1K9fX4KCgqRMmTLSqFEjmT17tly+fDnHr3Py5El58sknpXbt2lKqVCkpXbq0NGvWTF544QU5d+6cFARLly6V6OhoTxejwLjrrrsyfNfKly8vLVq0kMWLF0t6enq+lGHevHkmcBQEYWFhOfqbzK3yTp06VVavXp2jbTWMOZehWLFi5nz94x//kB07dnjF5w/ANR+bzWbL4jkAMJYsWZLh8XvvvSdffvmlvP/++xnWd+rUSSpVqpRn5Thz5ox07dpV7rzzTnNhpRcs27dvN+Xr3bu3uRi/lm+//da8xoULF6Rfv34mUKjdu3fLsmXLpHXr1vLFF1+Ip91zzz3y008/5eiOuZ1eYKempoqfn5/5bPKrxmLKlCmSkJAgFSpUyNNg8dtvv8m0adPMYz2efg+///57GTt2rEyfPj1Xj5eWlmbCqr+/v7k4Vhpo9T1mDtCe+Nz1Il+/w3YbNmyQDz/8UF599dUM50G/z9WqVbN8PA3xDz74YI4u7PU7e+utt0qfPn3M35p+lr/88osJBnpzQP8GGzRo4HYZCtLnD8C14lmsBwAHvQB39s0335hgkXl9XtO7nnpsZ8OGDTO1F3PmzJFZs2ZJ5cqVs9xfayPuu+8+8fX1le+++87UWDh78cUXZcGCBVLY/P33346LqoCAAPFWep6dv3P//Oc/pVatWubcP//881KiRIlcO5Z+R3TJCU987j179szwOD4+3gQLXa+huyBo2rRphvPVtm1bU2vxxhtvmJCRW7z9ew8UJkR7ALkiOTlZ/t//+38SGhpq7vLqBd/LL78smStF9e7vv/71L/nggw/MNnpBoLUG//nPf6772PYLqWs1Y3rzzTfl+PHjJoBkDhVKa1smTpyYYZ1eANWrV8+8pypVqsjjjz9+1XH0+AMGDHB5l12XzG3BP/roIxNibr75ZvP+O3ToIIcPH86w3/r16+XYsWOO5iT292h/Da1d0bKGhISY5lxJSUlZtjXfuXOndOnSxVyY67bt2rWT//73vxm2OX/+vDzxxBPmOPpeK1asaGqg9u7dKzmhfSwefvhhCQwMlODgYBk9erQJPHZ6TG225op+DyIiIsRd+l5uv/12893TGgx15MgReeihh0wItT+vn2Vmr7/+ujmvuk25cuWkefPmGWq8Mvex0M9l//798vXXXzvOif3cZv7c9futd/gvXrx41XH1Lr6GX72Lb/fZZ5+Zi25tknfDDTdIt27dzLFyg9bm6d9XyZIlzWeiNXtxcXEZtvn111/lgQceMOXS76N+L3W7xMRE87y+N/2M3333Xcd7d/V9vxZ9j0prnpy9/fbbcvfdd5vvnH736tata8KHM3c+f7sVK1Y43rvWdGjI0b9/AHmHGgsAlml46NGjh2zevNn0gWjcuLF8/vnn8tRTT5l/yLV5hjO9OFi+fLmMGjXKXEjoxbte+O7atcs0d7gWbfagF9LarEKbMGmAqVq1qtSoUSPb/dauXWsuMrRJhzvNfDp27CjDhw+XQ4cOmQsebcqhF+bXe4dcm+3oXVbt56EXbzNmzJBHHnnEBAA1YcIEs/6PP/5wfHZ6oepM79BrLYW+RkpKivndlU2bNpm7xHqBNXnyZHNc+4Xc1q1bpWXLlo6an48//thcFOuF3V9//SXbtm2TAwcOmDvP16KhQi/+tKmS1iq99tprcvbsWdNcST366KMyZMgQ07zL+RzrZ6nNZDIHupzSIKE1C2XLljV9Z7Tpj17Q63dLA45eDOt3U9+b1lYprZXS5/V7YA9AP/74o/n8+/bt6/I42t9l5MiR5jzo+VFZNfvr1auXzJ071wQaDTl2Wq5PP/3UXJTba0O0OWFkZKQJVi+99JLZRr9jd9xxh6lVs1L7oOH1mWeeMedm8ODBJnxpoNKmhPra+pnp35IeW79D+v40XOjf7Lp160yA1jCqZdT99bsydOhQ89rVq1d3uzz2kKZBzpm+Xw15ep6KFy9uPqMRI0aYJk4a5N39/O3BcODAgaYfjn4n9buhfbH079b+3gHkAe1jAQDuePzxx7UawvF49erV5vELL7yQYbsHH3zQ5uPjYzt8+LBjnW6ny+7dux3rjh07ZgsICLDdd999OTr+hx9+6HgdXZo3b2778ccfr7lfuXLlbI0aNcrRMU6dOmXz8/Ozde7c2ZaWluZYP2fOHHPMxYsXO9ZVrVrVFhkZedVrtGvXzix2mzdvNvvWqVPHlpKS4lg/e/Zss37fvn2Odd26dTOvm5n9NapVq2a7ePGiy+f0p0pPT7fVrFnTFhERYX630/1uvfVWW6dOnRzrgoKCzHl11+TJk80xe/TokWH9iBEjzPoffvjBPD537pw5x2PHjs2w3ahRo2ylS5e2XbhwIdvj6OdYu3ZtW0JCglkOHDhg9tVjdO/e3WzzxBNPmMdbt2517Hf+/HnzXsPCwhzn8d5777XVq1cv2+O9/fbb5rWOHj3qWKf7OJ/P7D73kJAQ2wMPPJBhu48++shs95///MdRtrJly9qGDBmSYbv4+HhzPjKvz87MmTMzlPf333+3+fr62l588cUM2+l3rHjx4o713333ndlvxYoV2b6+niNX33FXtAz6mlOmTDHnSt+PnpMWLVq4PFbm77HS76x+x53l9PNPTU21VaxY0Va/fn3bpUuXHNutW7fObDdp0qQcvQ8A7qMpFADLtOOo3oHVu8DOtGmUZglt6uEsPDzc0Wla3XLLLXLvvfeaWg7nJiJZad++venjoU0d9E671hxoU41r0VoObWqSE1999ZW5m6vNg5w7hOpdd23u46p5TU7pnVTnGgZ7ExG9+55Tepdba1+yox2btZmL3oXXGghtrqSLflba/Eqbn9lHVNI7uHrH/sSJE9f1nux3lu307rL9u6H0zreeY+0HYG8ep+daa660X4A2A7qWgwcPyo033miWOnXqmLvv2mxIR4ayH0vvquvdfju9w6132fVu+c8//+x4r1obpLUleUGb5WhNhZbHuYO1vldtumYvn36HtVZAm0fZz40u+rfUqlUrUwN4vVatWmXOrdZWOL+21kjUrFnT8dp6XpT+7blqumWF1pDpudJj6ndca79eeeWVq2oMnb/HWlOn5dSmc/r3YG+O5Q6txTx16pSp9XDue6HfFW0CaeVvF0D2CBYALNO+ANr/IPNFu1782Z93phc2md12223mwsbeVj472gRCmyfpBYo2o9ARlLQ/gHZgzY4GAu1LkNP3ZG//70wDgY6yk/k9uUODlDN70xBtOpRTOurOtWiosIcQ+wW5fVm4cKFp/mK/cNPmWNpMSfvI6MW5NgNzJ+hkPqfaVEYDmfOoVv3795fY2FjTBMse3rSJijaTygltFqQX47qfNtPS861NduyjIOk5yXy+XH0PdRQpDRz6PrXcGooy9zmxSptDaVM9bX6nNGBo0NDAYR9lyn5+tFla5vOjI5PpxfH10tfWAKfvL/Nr6wW+/bX1exQVFWW+D/o5arMobcZ1PRf0mWmg0/OlTZvGjBljPg9XNw70s9e/Zw2XGvq0jE8//bR57nrKkdXfrtJgYeVvF0D26GMBoNDTgKHtrtesWWNGCsqKXlToXXz70JS5xX6hmJleRLkaWSir0YbcGf37WrUVyl4bMXPmTNPvxRV73w29s613lT/55BNzUav7aJt/vfOtfTRy4zPRi1YNhdqhWNv560+9m60XlTmhF5453TY7GjS0v4yGkpiYGFm5cqXp5zNp0iTTpyY3aKdxDULaUV9rjPTiWi+sNXBkPj/ah8HVaGba3+B66WvrOdDaQlffN+c+O1qLoP0+9O9Hz73WPNr7ymhH7uulocZ+vjT8aznGjRtnahy1s7y9I7fWnunfpg6qoMFW/zY1hGn/ovyaowRA7iBYALBMO07rXWStDXCutdCmK/bnndnv1DrTDrw6Qo/erXSXXrDl5O5m9+7dzQRdeiGpzU+yYy+zXoA6zwOgoeTo0aMZLnC1xsHViFR6Z/R65xDIKqy4w97BVmtqcnJBftNNN5nmI7roHW3ttK0dgHMSLPScOtei6ChXelHo3PlYLyz1Ils71mpo0bkYtGlZTod1vRY9Z3q+MnP1PdSQohf5uug5vf/++817HT9+fJZDl7p7TjSsaYdhbYKnzaD0s9DAkfn86GhIuRGYnOlra1DVc6K1gdei80roop3odW6YNm3ayPz5882kkbn1fdTwrx3n9Rga6JQGLq0505od55o8V83AcloG579drQ1ypusy//8IQO6hKRQAy+yTYOl8As70jqNeDGS+MNWLe+dhTHX4S71b2rlz52wvMrXttau7+tqMQ9nvgmZF+2PoxbP2/dAgk5leTNsvpPRCT++c6uhGzsdctGiRCTDaXtv5Ik7v7uoFqp3eDc88rKc79MLXanMU7ceiZdNRs5zb+tvZm53puct8LL3Y1eZtetGXE9p8xpn2f1CZz702e9ImX1qzZJ+kMDe/hzqymPPsztqf5K233jIX9TraldL+Js70POtzep6zm8Fdz4k7M7NraNHPT0em0gtpDRqZa3A09Oms1q6Om5NmgVnRoKR/S1oDk/lvRh/bPwMNPVeuXMnwvAYMbcbmfO7dfe+uaDMnPe/an0NrDpX97925jPpd1JHLMstpGfT/A/r91WDk/B609kabgTn/7QLIXdRYALBMawK0eYPekdQ29TpfgTap0LCgnZ8zD02pw43qRZXzcLPqWs1QtOmMXixoZ1+tCdAaEr1I0XbcWobMdycz05oFbeqjF6DaNMh55m0NOtqxWDuWK6050bvXWiYdCleHwtS7nVpWHcLS+YJYh+LU4Ux1O7141OYdWtbrGZLTTsuld7m1/bseT5uu6Ht0h14caujSi3sdzlM7jWvnYR1OVO8I60Wt3jHWz1GbvGiTMj13eiytgdLOzdpMJie0Fkc/I/0M9MJe37/WTmSeu6JJkybm/GvHe22SlJOhbHNKm9noOdT3q98tnbdBL+q1bFpLZe+ErwFWmx7pXXltmqUXmxqK9YIzu879ek60T4+GTx3aWC9es/vO6XvT7fTvQi9wnZtBKf389fU0bOm2OneEfu+0H4p2MNbyZQ7rOaXfPS2nfof1b1L/ZvS96WehfwPa/0GHKtbhiHWIYe37oTUbGjK0aZZe8OvcFs7vXb8T2lxJA6fWhGgHc3fp8L46dKwOuaxzsei50GCn32172NRaDf1s//zzz+v6/HUwB60R0++7dgLX2kn7cLMaMLW/B4A8ch0jSQEo4jIPN2sfOnPMmDG2KlWq2EqUKGGGOdUhMJ2HOVW6n+6/ZMkSs42/v7+tSZMmjqEis/Ptt9/aHnroIdstt9xi9tMhMJs2bWqbNWuW7fLlyzku/4kTJ0xZb7vtNjMEaqlSpWzNmjUzQ3AmJiZm2FaHl9VhTvU9VapUyTZ8+HDb2bNnr3rNV155xQwxquVq06aNGU43q+FmMw+3aR+eU4c4tdPhV/v27WuGI9Xn7EPPZvUazs9l/ix1SNH777/fFhwcbMqnr/Xwww/bNm7caJ7XoW+feuopMxTvDTfcYD5X/X3evHk5Hm72559/NsML6/46rO+//vWvDEN9OpsxY4bZZ+rUqbac0s/xWkPEqt9++82UQz83PbctW7Y0w4w6e/PNN2133nmn4/OoXr26ef/O597VcLM6bKoOA6zvUZ+zn9usPnc1YcIE81yNGjWyLLPup8Or6hCzWmYtz4ABAzIMyezucLN2K1eutN1xxx3mnOqi32X9+zt06JB5/siRI7bHHnvMHFOPXb58eVv79u1tX331VYbXOXjwoPnMSpYsaY6T3dCz9u+zlskVfW86FK59GOq1a9faGjZsaI6vwwK/9NJLZjhnq5//8uXLzf9b9Bzr+3rkkUdsf/zxR44/UwDu89H/5FVoAYDMtGmUjsJzvXdiUfjpnWO9a6x30jOPkAUAKLzoYwEAyDd6L0v7qWgTFUIFAHgX+lgAAPKcdqLWkX+0b8e+fftM/xsAgHchWAAA8pyOcKSduXVkIJ38TDt6AwC8C30sAAAAAFhGHwsAAAAAlhEsAAAAAFhW5PpYpKeny4kTJ8xEQTrsJQAAAADXtNeETqSqk2PaJxrNSpELFhoqQkNDPV0MAAAAoNCIi4uTm2++Odttilyw0JoK+4cTGBjo6eIAAAAABVZSUpK5KW+/hs5OkQsW9uZPGioIFgAAAMC15aQLAZ23AQAAAFhGsAAAAABQ+IPF3LlzJSwsTAICAqRVq1aya9eubLePjo6WWrVqScmSJU17rzFjxsjff/+db+UFAAAAUMCCxfLlyyUqKkomT54se/fulUaNGklERIScOnXK5fZLly6VcePGme0PHDggixYtMq/x9NNP53vZAQAAABSQYDFr1iwZMmSIDBw4UOrWrSvz58+XUqVKyeLFi11uv337dmnTpo307dvX1HJ07txZ+vTpc81aDgAAAABeGixSU1Nlz5490rFjx/8Vplgx83jHjh0u92ndurXZxx4kjhw5Ihs2bJCuXbvmW7kBAAAAFKDhZk+fPi1paWlSqVKlDOv18cGDB13uozUVut8dd9xhZgG8cuWKDBs2LNumUCkpKWZxHosXAAAAgJd13nbHli1bZOrUqTJv3jzTJ2PVqlWyfv16ef7557PcZ9q0aRIUFORYmHUbAAAAyH0+Nr3176GmUNqf4uOPP5aePXs61kdGRsq5c+dkzZo1V+3Ttm1buf3222XmzJmOdUuWLJGhQ4fKhQsXTFOqnNRYaLhITExkgjwAAAAgG3rtrDfnc3Lt7LEaCz8/P2nWrJls3LjRsS49Pd08Dg8Pd7nPxYsXrwoPvr6+5mdW+cjf398xyzazbQMAAABe1sdC6VCzWkPRvHlzadmypZmjIjk52YwSpfr37y8hISGmOZPq3r27GUmqSZMmZs6Lw4cPyzPPPGPW2wMGAAAAgCIWLHr16iUJCQkyadIkiY+Pl8aNG0tMTIyjQ3dsbGyGGoqJEyeKj4+P+Xn8+HG58cYbTah48cUXPfguAAAAAHisj0VhaCcGAAAAFGVJhaGPBQAAAADvQbAAAAAAYBnBAgAAAEDh7rxdpA3u4ukSAAAAoKBbGCOFBTUWAAAAACwjWAAAAACwjGABAAAAwDKCBQAAAADLCBYAAAAALCNYAAAAALCMYAEAAADAMoIFAAAAAMsIFgAAAAAsI1gAAAAAsIxgAQAAAMAyggUAAAAAywgWAAAAACwjWAAAAACwjGABAAAAwDKCBQAAAADLCBYAAAAALCNYAAAAALCMYAEAAADAMoIFAAAAAMsIFgAAAAAsI1gAAAAAsIxgAQAAAMAyggUAAAAAywgWAAAAACwjWAAAAACwjGABAAAAwDKCBQAAAADLCBYAAAAALCNYAAAAALCMYAEAAADAMoIFAAAAAO8IFnPnzpWwsDAJCAiQVq1aya5du7Lc9q677hIfH5+rlm7duuVrmQEAAAAUoGCxfPlyiYqKksmTJ8vevXulUaNGEhERIadOnXK5/apVq+TPP/90LD/99JP4+vrKQw89lO9lBwAAAFBAgsWsWbNkyJAhMnDgQKlbt67Mnz9fSpUqJYsXL3a5ffny5aVy5cqO5csvvzTbEywAAACAIhosUlNTZc+ePdKxY8f/FahYMfN4x44dOXqNRYsWSe/evaV06dJ5WFIAAAAA2SkuHnT69GlJS0uTSpUqZVivjw8ePHjN/bUvhjaF0nCRlZSUFLPYJSUlWSw1AAAAgALXFMoKDRQNGjSQli1bZrnNtGnTJCgoyLGEhobmaxkBAACAosCjwaJChQqm4/XJkyczrNfH2n8iO8nJybJs2TIZNGhQttuNHz9eEhMTHUtcXFyulB0AAABAAQkWfn5+0qxZM9m4caNjXXp6unkcHh6e7b4rVqwwTZz69euX7Xb+/v4SGBiYYQEAAADgRX0slA41GxkZKc2bNzdNmqKjo01thI4Spfr37y8hISGmSVPmZlA9e/aU4OBgD5UcAAAAQIEJFr169ZKEhASZNGmSxMfHS+PGjSUmJsbRoTs2NtaMFOXs0KFDsm3bNvniiy88VGoAAAAAznxsNptNihAdFUo7cWt/C482ixrcxXPHBgAAQOGwMKbQXDsX6lGhAAAAABQMBAsAAAAAlhEsAAAAAFhGsAAAAABgGcECAAAAgGUECwAAAACWESwAAAAAWEawAAAAAGAZwQIAAACAZQQLAAAAAJYRLAAAAABYRrAAAAAAYBnBAgAAAIBlBAsAAAAAlhEsAAAAAFhGsAAAAABgGcECAAAAgGUECwAAAACWESwAAAAAWEawAAAAAGAZwQIAAACAZQQLAAAAAJYRLAAAAABYRrAAAAAAYBnBAgAAAIBlBAsAAAAAlhEsAAAAAFhGsAAAAABgGcECAAAAgGUECwAAAACWESwAAAAAWEawAAAAAGAZwQIAAACAZQQLAAAAAJYRLAAAAABYRrAAAAAAUPiDxdy5cyUsLEwCAgKkVatWsmvXrmy3P3funDz++ONy0003ib+/v9x2222yYcOGfCsvAAAAgKsVFw9avny5REVFyfz5802oiI6OloiICDl06JBUrFjxqu1TU1OlU6dO5rmPP/5YQkJC5NixY1K2bFmPlB8AAABAAQgWs2bNkiFDhsjAgQPNYw0Y69evl8WLF8u4ceOu2l7XnzlzRrZv3y4lSpQw67S2AwAAAEARbQqltQ979uyRjh07/q8wxYqZxzt27HC5z9q1ayU8PNw0hapUqZLUr19fpk6dKmlpaflYcgAAAAAFpsbi9OnTJhBoQHCmjw8ePOhynyNHjsimTZvkkUceMf0qDh8+LCNGjJDLly/L5MmTXe6TkpJiFrukpKRcficAAAAAPN552x3p6emmf8Vbb70lzZo1k169esmECRNME6qsTJs2TYKCghxLaGhovpYZAAAAKAo8FiwqVKggvr6+cvLkyQzr9XHlypVd7qMjQekoULqfXZ06dSQ+Pt40rXJl/PjxkpiY6Fji4uJy+Z0AAAAA8Fiw8PPzM7UOGzduzFAjoY+1H4Urbdq0Mc2fdDu7X375xQQOfT1XdEjawMDADAsAAAAAL2oKpUPNLliwQN599105cOCADB8+XJKTkx2jRPXv39/UONjp8zoq1OjRo02g0BGktPO2duYGAAAAUESHm9U+EgkJCTJp0iTTnKlx48YSExPj6NAdGxtrRoqy0/4Rn3/+uYwZM0YaNmxo5rHQkDF27FgPvgsAAAAAPjabzSZFiI4KpZ24tb+FR5tFDe7iuWMDAACgcFgYU2iunQvVqFAAAAAACiaCBQAAAADLCBYAAAAALCNYAAAAALCMYAEAAADAMoIFAAAAAMsIFgAAAAAsI1gAAAAAsIxgAQAAAMAyggUAAAAAywgWAAAAACwjWAAAAACwjGABAAAAwDKCBQAAAADLCBYAAAAALCNYAAAAALCMYAEAAADAMoIFAAAAAMsIFgAAAAAsI1gAAAAAsIxgAQAAAMAyggUAAAAAy4pfz05paWmyevVqOXDggHlcr1496dGjh/j6+lovEQAAAADvDxaHDx+Wbt26yR9//CG1atUy66ZNmyahoaGyfv16qV69el6UEwAAAIA3NYUaNWqUVKtWTeLi4mTv3r1miY2NlVtvvdU8BwAAAKDocbvG4uuvv5ZvvvlGypcv71gXHBws06dPlzZt2uR2+QAAAAB4Y42Fv7+/nD9//qr1Fy5cED8/v9wqFwAAAABvDhb33HOPDB06VHbu3Ck2m80sWoMxbNgw04EbAAAAQNHjdrB47bXXTAft8PBwCQgIMIs2gapRo4bMnj07b0oJAAAAwLv6WJQtW1bWrFkjv/76qxw8eNCsq1OnjgkWAAAAAIqm65rHQtWsWdMsAAAAAJCjYBEVFSXPP/+8lC5d2vyenVmzZuVW2QAAAAB4U7D47rvv5PLly47fAQAAAMDtYLF582aXvwMAAADAdY0K9dhjj7mcxyI5Odk8BwAAAKDocTtYvPvuu3Lp0qWr1uu69957L7fKBQAAAMAbg0VSUpIkJiaaCfG0xkIf25ezZ8/Khg0bpGLFitdViLlz50pYWJiZE6NVq1aya9euLLd95513xMfHJ8Oi+wEAAAAoBMPN6vwV9gv522677arndf2UKVPcLsDy5cvNSFPz5883oSI6OloiIiLk0KFDWQaVwMBA87zzsQEAAAAUgmChnba1tuLuu++WlStXSvny5R3P+fn5SdWqVaVKlSpuF0CHpx0yZIgMHDjQPNaAsX79elm8eLGMGzfO5T4aJCpXruz2sQAAAAB4OFi0a9fO/Dx69KiEhoZKsWJud8+4SmpqquzZs0fGjx/vWKev27FjR9mxY0eW+124cMEEmfT0dGnatKlMnTpV6tWr53LblJQUs9hp0y0AAAAAHp55Wy/o1cWLFyU2NtaEA2cNGzbM8WudPn1a0tLSpFKlShnW6+ODBw+63KdWrVqmNkOPo30+Xn75ZWndurXs379fbr755qu2nzZt2nU10QIAAACQh8EiISHBNFv67LPPXD6vQSEvhYeHm8VOQ0WdOnXkzTffNLODZ6a1Ic6zhWuNhda4AAAAAMg9brdneuKJJ+TcuXOyc+dOKVmypMTExJghaGvWrClr165167UqVKggvr6+cvLkyQzr9XFO+1CUKFFCmjRpIocPH3b5vL+/v+ns7bwAAAAA8HCw2LRpk+lw3bx5c9MfQptG9evXT2bMmGGaHblDO303a9ZMNm7c6Fin/Sb0sXOtRHa0hmTfvn1y0003uftWAAAAAHgqWOgM2/ZhYMuVK2eaRqkGDRrI3r173S6ANlNasGCBqfU4cOCADB8+3BzDPkpU//79M3Tufu655+SLL76QI0eOmONpqDl27JgMHjzY7WMDAAAA8FAfC+08rXNI6IR2jRo1Mn0b9HcdJvZ6ag169eplwsmkSZMkPj5eGjdubJpX2Tt0awdx5xGodDI+HZ5Wt9VgozUe27dvl7p167p9bAAAAAC5w8emk1O4YcmSJXLlyhUZMGCAGSq2S5cucubMGdOsSWfF1qBQkGnn7aCgIDOilEf7Wwzu4rljAwAAoHBYGFNorp3drrHQpkd2WlugzZB0aNhbbrnFdMYGAAAAUPRYnuWuVKlSZpK6MmXKmDklAAAAABQ9bgUL7Quxbt0603naPl/F5cuXZfbs2aafxfTp0/OqnAAAAAAKsBw3hdq2bZvcc889pp2Vj4+PGW727bfflp49e0rx4sXl2WeflcjIyLwtLQAAAIDCXWMxceJE6dq1q/z4449miNhvv/1W7rvvPpk6dar8/PPPMmzYMDNhHgAAAICiJ8ejQgUHB8vWrVvNsK6XLl0yfSpWrVol9957rxQmjAoFAACAQmNh4RkVKsc1Fjp/hH3UJ62Z0E7b9evXt15aAAAAAIWeW8PNapMnnZhOaUWHTpSns2Q7a9iwYe6WEAAAAIB3BYsOHTqYQGGnnbmVdubW9frTPloUAAAAgKIjx8Hi6NGjeVsSAAAAAN4fLKpWrZq3JQEAAABQdGfeBgAAAACCBQAAAADLCBYAAAAALCNYAAAAAMj/YHH33XfLuXPnXM7Kp88BAAAAKHrcDhZbtmyR1NTUq9b//fffsnXr1twqFwAAAABvHG72xx9/dDkDt9JJ8WJiYiQkJCT3SwgAAADAe4JF48aNzczaurhq8lSyZEl5/fXXc7t8AAAAALxt5m2bzSbVqlWTXbt2yY033uh4zs/PTypWrCi+vr55VU4AAAAA3jTzdnp6el6WBwAAAEBR6Lz97rvvyvr16x2P//3vf0vZsmWldevWcuzYsdwuHwAAAABvDBZTp041/SnUjh07ZM6cOTJjxgypUKGCjBkzJi/KCAAAAMBbmkLZxcXFSY0aNczvq1evlgcffFCGDh0qbdq0kbvuuisvyggAAADA22osypQpI3/99Zf5/YsvvpBOnTqZ3wMCAuTSpUu5X0IAAAAA3ldjoUFi8ODB0qRJE/nll1+ka9euZv3+/fslLCwsL8oIAAAAwNtqLObOnSvh4eGSkJAgK1eulODgYLN+z5490qdPn7woIwAAAIACzsemk1MUIUlJSRIUFCSJiYkSGBjouYIM7uK5YwMAAKBwWBhTaK6d3a6xUFu3bpV+/fqZIWaPHz9u1r3//vuybdu26ysxAAAAgELN7WChzZ8iIiLMkLN79+6VlJQUs15TjA5FCwAAAKDocTtYvPDCCzJ//nxZsGCBlChRwrFeh5vVoAEAAACg6HE7WBw6dEjuvPPOq9Zr26tz587lVrkAAAAAeHOwqFy5shw+fPiq9dq/olq1arlVLgAAAADeGCzee+89059iyJAhMnr0aNm5c6f4+PjIiRMn5IMPPpAnn3xShg8fnrelBQAAAFC4J8gbOHCgdOnSRcaNGyfp6enSoUMHuXjxomkW5e/vb4LFyJEj87a0AAAAAAp3sLBPd6G1FBMmTJCnnnrKNIm6cOGC1K1bV8qUKZOX5QQAAADgLX0sNFTY+fn5mUDRsmVLy6FCZ/MOCwuTgIAAadWqlezatStH+y1btsyUqWfPnpaODwAAACCfaiyUNn8qXjz7Xdwdcnb58uUSFRVlhrDVUBEdHW3mydDRpypWrJjlfr///rtpftW2bVu3jgcAAADAw8FCL/hzu8nTrFmzTIdw7cOhNGCsX79eFi9ebPpzuJKWliaPPPKITJkyxcwCzjC3AAAAgGe5FSy0X0V2tQjuSk1NlT179sj48eMd64oVKyYdO3aUHTt2ZLnfc889Z8oxaNAgEyyyoyNZ2WcHV0lJSblUegAAAABu97Fw7l+RW06fPm1qHypVqpRhvT6Oj493uY/Ol7Fo0SIz83dOTJs2zUzeZ19CQ0NzpewAAAAAriNY2EeF8qTz58/Lo48+akJFhQoVcrSP1oYkJiY6lri4uDwvJwAAAFDU5Lgp1NGjR+XGG2/M1YNrOPD19ZWTJ09mWK+PdYbvzH777TfTabt79+6OdTqnhtJO5drhu3r16hn20Tk2dAEAAABQAGosqlatmuvNoXTI2mbNmsnGjRszBAV9HB4eftX2tWvXln379sn333/vWHr06CHt27c3v9PMCQAAACgEnbfzgg41GxkZKc2bNzdzYuhws8nJyY5Rovr37y8hISGmr4TOc1G/fv0M+5ctW9b8zLweAAAAQBEKFr169ZKEhASZNGmS6bDduHFjiYmJcXTojo2NNSNFAQAAACi4fGwFoVd2PtLhZnV0KO3IHRgY6LmCDO7iuWMDAACgcFgYU2iuna+rKkDnjujXr5/pB3H8+HGz7v333zdDwQIAAAAoetwOFitXrjQzcJcsWVK+++47x+RzmmKmTp2aF2UEAAAA4G3B4oUXXpD58+ebuSRKlCjhWN+mTRvZu3dvbpcPAAAAgDcGC50r4s4777xqvba9OnfuXG6VCwAAAIA3BwuduO7w4cNXrdf+FdWqVcutcgEAAADw5mAxZMgQGT16tOzcudNMmHfixAn54IMP5Mknn5Thw4fnTSkBAAAAeNc8FuPGjTOzY3fo0EEuXrxomkX5+/ubYDFy5Mi8KSUAAAAA75zHIjU11TSJunDhgtStW1fKlCkjhQHzWAAAAKDQWOjF81gsWbLE1FT4+fmZQNGyZctCEyoAAAAA5A23g8WYMWOkYsWK0rdvX9mwYYOkpaXlTckAAAAAeG+w+PPPP2XZsmWm4/bDDz8sN910kzz++OOyffv2vCkhAAAAAO8LFsWLF5d77rnHjAR16tQpefXVV+X333+X9u3bS/Xq1fOmlAAAAAC8a1QoZ6VKlZKIiAg5e/asHDt2TA4cOJB7JQMAAADgvTUWSjtva41F165dJSQkRKKjo+W+++6T/fv3534JAQAAAHhfjUXv3r1l3bp1prZC+1g888wzEh4enjelAwAAAOCdwcLX11c++ugj0wRKfwcAAAAAt4OFNoECAAAAALeDxWuvvSZDhw6VgIAA83t2Ro0alZOXBAAAAOBFfGw2m+1aG916662ye/duCQ4ONr9n+WI+PnLkyBEpyNyZljxPDe7iuWMDAACgcFgYU2iunXNUY3H06FGXvwMAAADAdQ03+9xzz5nhZjO7dOmSeQ4AAABA0eN2sJgyZYpcuHDhqvUaNvQ5AAAAAEWP28FCu2RoX4rMfvjhBylfvnxulQsAAACANw43W65cORModLntttsyhIu0tDRTizFs2LC8KicAAAAAbwgW0dHRprbiscceM02etHe4nZ+fn4SFhTEDNwAAAFBE5ThYREZGmp863Gzr1q2lRIkSeVkuAAAAAN4WLHT8Wvu4tU2aNDEjQOniikfnhgAAAABQcIOF9q/4888/pWLFilK2bFmXnbftnbq1vwUAAACAoiVHwWLTpk2OEZ82b96c12UCAAAA4I3Bol27di5/BwAAAIDrmsciJiZGtm3b5ng8d+5cady4sfTt21fOnj3LpwoAAAAUQW4Hi6eeesp05lb79u2TqKgo6dq1qxw9etT8DgAAAKDoyfFws3YaIOrWrWt+X7lypXTv3l2mTp0qe/fuNQEDAAAAQNHjdo2FToZ38eJF8/tXX30lnTt3Nr9r5257TQYAAACAosXtGos77rjDNHlq06aN7Nq1S5YvX27W//LLL3LzzTfnRRkBAAAAeFuNxZw5c6R48eLy8ccfyxtvvCEhISFm/WeffSZdunTJizICAAAA8LZgccstt8i6devkhx9+kEGDBjnWv/rqq/Laa69dVyF0ZKmwsDAJCAiQVq1amZqQrKxatUqaN29uJuorXbq0GZHq/fffv67jAgAAAPBQUyils2uvXr1aDhw4YB7Xq1dPevToIb6+vm6/ljal0qZV8+fPN6EiOjpaIiIi5NChQ2am78y0L8eECROkdu3apr+HhpyBAweabXU/AAAAAPnPx2az2dzZ4fDhw2b0p+PHj0utWrXMOg0BoaGhsn79eqlevbpbBdAw0aJFC9PESqWnp5vXGjlypIwbNy5Hr9G0aVPp1q2bPP/889fcVjuYBwUFSWJiogQGBorHDKbZGAAAAK5hYYx4kjvXzm43hRo1apQJD3FxcWaIWV1iY2Pl1ltvNc+5IzU1Vfbs2SMdO3b8X4GKFTOPd+zYcc39NRNt3LjRBJs777zT5TYpKSnmA3FeAAAAAHi4KdTXX38t33zzjWmSZBccHCzTp083I0W54/Tp06ZZVaVKlTKs18cHDx7Mcj9NTNppXEODNr+aN2+edOrUyeW206ZNkylTprhVLgAAAADucbvGwt/fX86fP3/V+gsXLpg+D/nhhhtukO+//16+/fZbefHFF00fjS1btrjcdvz48SaI2BetaQEAAADg4RqLe+65R4YOHSqLFi2Sli1bmnU7d+6UYcOGmQ7c7qhQoYKpcTh58mSG9fq4cuXKWe6nzaVq1KhhftdRobQTudZM3HXXXS6DkC4AAAAAClCNhQ4pq30swsPDzfCwumgTKL3Qnz17tluvpTUczZo1M/0k7LTztj7W188p3UebRQEAAAAoJDUWOn/EmjVrzOhQ9uFm69Sp46hBcJc2Y4qMjDRzU2gNiA43m5ycbIaQVf379zf9KbRGQulP3VbDjYaJDRs2mHksdLI+AAAAAAU8WGitwMyZM2Xt2rVmNKcOHTrI5MmTpWTJkpYK0KtXL0lISJBJkyZJfHy8adoUExPj6NCtI05p0yc7DR0jRoyQP/74wxxb57NYsmSJeR0AAAAABXweC50j4tlnnzVDweoF/eeffy59+vSRxYsXS2HCPBYAAAAoNBZ64TwW7733nhnWVQOFzrr96aefygcffGBqMgAAAAAUbTkOFtokSWfcttOaCx8fHzlx4kRelQ0AAACAtwWLK1eumBGgnJUoUUIuX76cF+UCAAAA4I2dt7UrxoABAzLMCfH333+b+StKly7tWLdq1arcLyUAAAAA7wgWOiRsZv369cvt8gAAAADw5mDx9ttv521JAAAAABSdmbcBAAAAIDOCBQAAAADLCBYAAAAALCNYAAAAALCMYAEAAADAMoIFAAAAAMsIFgAAAAAsI1gAAAAAsIxgAQAAAMAyggUAAAAAywgWAAAAACwjWAAAAACwjGABAAAAwDKCBQAAAADLCBYAAAAALCNYAAAAALCMYAEAAADAMoIFAAAAAMsIFgAAAAAsI1gAAAAAsIxgAQAAAMAyggUAAAAAywgWAAAAACwjWAAAAACwjGABAAAAwDKCBQAAAADLCBYAAAAALCNYAAAAALCMYAEAAADAMoIFAAAAAO8IFnPnzpWwsDAJCAiQVq1aya5du7LcdsGCBdK2bVspV66cWTp27Jjt9gAAAACKQLBYvny5REVFyeTJk2Xv3r3SqFEjiYiIkFOnTrncfsuWLdKnTx/ZvHmz7NixQ0JDQ6Vz585y/PjxfC87AAAAgP/jY7PZbOJBWkPRokULmTNnjnmcnp5uwsLIkSNl3Lhx19w/LS3N1Fzo/v3797/m9klJSRIUFCSJiYkSGBgoHjO4i+eODQAAgMJhYYxHD+/OtbNHayxSU1Nlz549pjmTo0DFipnHWhuRExcvXpTLly9L+fLlXT6fkpJiPhDnBQAAAEDu8miwOH36tKlxqFSpUob1+jg+Pj5HrzF27FipUqVKhnDibNq0aSZl2RetDQEAAADgZX0srJg+fbosW7ZMPvnkE9Px25Xx48ebqhv7EhcXl+/lBAAAALxdcU8evEKFCuLr6ysnT57MsF4fV65cOdt9X375ZRMsvvrqK2nYsGGW2/n7+5sFAAAAgJfWWPj5+UmzZs1k48aNjnXaeVsfh4eHZ7nfjBkz5Pnnn5eYmBhp3rx5PpUWAAAAQIGssVA61GxkZKQJCC1btpTo6GhJTk6WgQMHmud1pKeQkBDTV0K99NJLMmnSJFm6dKmZ+8LeF6NMmTJmAQAAAFAEg0WvXr0kISHBhAUNCY0bNzY1EfYO3bGxsWakKLs33njDjCb14IMPZngdnQfj2WefzffyAwAAACgA81jkN+axAAAAQKGxkHksAAAAABQhBAsAAAAAlhEsAAAAAFhGsAAAAABgGcECAAAAgGUECwAAAACWESwAAAAAWEawAAAAAGAZwQIAAACAZQQLAAAAAJYRLAAAAABYRrAAAAAAYBnBAgAAAIBlBAsAAAAAlhEsAAAAAFhGsAAAAABgGcECAAAAgGUECwAAAACWESwAAAAAWEawAAAAAGAZwQIAAACAZQQLAAAAAJYRLAAAAABYRrAAAAAAYBnBAgAAAIBlBAsAAAAAlhEsAAAAAFhGsAAAAABgGcECAAAAgGUECwAAAACWESwAAAAAWEawAAAAAGAZwQIAAACAZQQLAAAAAJYRLAAAAABYRrAAAAAAUPiDxdy5cyUsLEwCAgKkVatWsmvXriy33b9/vzzwwANmex8fH4mOjs7XsgIAAAAogMFi+fLlEhUVJZMnT5a9e/dKo0aNJCIiQk6dOuVy+4sXL0q1atVk+vTpUrly5XwvLwAAAIACGCxmzZolQ4YMkYEDB0rdunVl/vz5UqpUKVm8eLHL7Vu0aCEzZ86U3r17i7+/f76XFwAAAEABCxapqamyZ88e6dix4/8KU6yYebxjx45cO05KSookJSVlWAAAAAB4SbA4ffq0pKWlSaVKlTKs18fx8fG5dpxp06ZJUFCQYwkNDc211wYAAABQQDpv57Xx48dLYmKiY4mLi/N0kQAAAACvU9xTB65QoYL4+vrKyZMnM6zXx7nZMVv7YtAfAwAAAPDSGgs/Pz9p1qyZbNy40bEuPT3dPA4PD/dUsQAAAAAUphoLpUPNRkZGSvPmzaVly5ZmXork5GQzSpTq37+/hISEmH4S9g7fP//8s+P348ePy/fffy9lypSRGjVqePKtAAAAAEWaR4NFr169JCEhQSZNmmQ6bDdu3FhiYmIcHbpjY2PNSFF2J06ckCZNmjgev/zyy2Zp166dbNmyxSPvAQAAAICIj81ms0kRosPN6uhQ2pE7MDDQcwUZ3MVzxwYAAEDhsDCm0Fw7e/2oUAAAAADyHsECAAAAgGUECwAAAACWESwAAAAAWEawAAAAAGAZwQIAAACAZQQLAAAAAJYRLAAAAABYRrAAAAAAYBnBAgAAAIBlBAsAAAAAlhEsAAAAAFhGsAAAAABgGcECAAAAgGUECwAAAACWESwAAAAAWEawAAAAAGAZwQIAAACAZQQLAAAAAJYRLAAAAABYRrAAAAAAYBnBAgAAAIBlBAsAAAAAlhEsAAAAAFhGsAAAAABgGcECAAAAgGUECwAAAACWESwAAAAAWEawAAAAAGAZwQIAAACAZQQLAAAAAJYRLAAAAABYRrAAAAAAYBnBAgAAAIBlBAsAAAAAlhEsAAAAAHhHsJg7d66EhYVJQECAtGrVSnbt2pXt9itWrJDatWub7Rs0aCAbNmzIt7ICAAAAKIDBYvny5RIVFSWTJ0+WvXv3SqNGjSQiIkJOnTrlcvvt27dLnz59ZNCgQfLdd99Jz549zfLTTz/le9kBAAAA/B8fm81mEw/SGooWLVrInDlzzOP09HQJDQ2VkSNHyrhx467avlevXpKcnCzr1q1zrLv99tulcePGMn/+/GseLykpSYKCgiQxMVECAwPFYwZ38dyxAQAAUDgsjPHo4d25di4uHpSamip79uyR8ePHO9YVK1ZMOnbsKDt27HC5j67XGg5nWsOxevVql9unpKSYxU4/FPuH5FGpVzx7fAAAABR8SZ69ZrVfM+ekLsKjweL06dOSlpYmlSpVyrBeHx88eNDlPvHx8S631/WuTJs2TaZMmXLVeq0VAQAAAAq094OkIDh//rypuSiwwSI/aG2Icw2HNrU6c+aMBAcHi4+Pj0fLBgDIeFdMb/rExcV5tqkqAMBBayo0VFSpUkWuxaPBokKFCuLr6ysnT57MsF4fV65c2eU+ut6d7f39/c3irGzZspbLDgDIGxoqCBYAUHBcq6aiQIwK5efnJ82aNZONGzdmqFHQx+Hh4S730fXO26svv/wyy+0BAAAA5D2PN4XSZkqRkZHSvHlzadmypURHR5tRnwYOHGie79+/v4SEhJi+Emr06NHSrl07eeWVV6Rbt26ybNky2b17t7z11lseficAAABA0eXxYKHDxyYkJMikSZNMB2wdNjYmJsbRQTs2NtaMFGXXunVrWbp0qUycOFGefvppqVmzphkRqn79+h58FwAAq7TZqs5plLn5KgCgcPD4PBYAAAAACj+Pz7wNAAAAoPAjWAAAAACwjGABAAAAwDKCBQAAAADLCBYAAAAALCNYAAAAALCMYAEA8DqMpA4ARXCCPAAArIYIHx8fOXbsmJw9e1aCg4PlpptukuLF+ScOAPIT/9cFABT6ULF69WqJiooSX19fSUxMlKFDh0q/fv2kdu3ani4iABQZNIUCABQ6aWlp5qeGis8//1wGDBggTzzxhBw6dMgEjHnz5snMmTPlp59+8nRRAaDIIFgAAAqNZcuWmZ9aM6HOnTsnCxcuNKFi1KhR8ueff5rHdevWlS1btsiMGTPkwIEDHi41ABQNBAsAQKHw7bffysSJEyUuLs7ROdvf39/UVjzyyCPy119/SUREhLRv3162bdtm1q9du1YmT55MzQUA5AOCBQCgUNBaCA0XoaGhjqBQsmRJad26tdSsWVOWL18uFStWlOnTp5vntAN35cqV5fz586ZDNwAgbxEsAACFQunSpaVcuXJy/Phx6dChg/Tt29es13VKO21riEhOTjaPDx8+LCNHjpSlS5eakAEAyFsECwBAoRIYGChTp041zZ0GDx7sWF+lShUTLh5//HHp3r27vP7666ZZlD14AADyFsPNAgAKxZCyKj09XW644QZ5+OGHpUSJEjJ27FgZNGiQLFq0SCIjI01n7n379plai507d5rmUwCA/OFjY3pSAEABDxVffvmlxMTESFJSkhkBql69enLx4kVZsWKFCRddunSRd955x7GPDkfLBHkAkL9oCgUAKLA0VGzYsEHuvfdeOXjwoOzatUtuv/1201G7VKlSpubipZdeko0bN8pDDz3k2IdQAQD5j//zAgAKLO2MvWPHDpk9e7YMGTLErBszZoxp9qS1EtqBW8NFSkqKvPLKK2YeCzpqA4BnUGMBACiQvv/+e7nllltk/fr1GcLCq6++KsOHD5fHHntMPvzwQzPkbP/+/U1tBqECADyHGgsAQIGk/Si6detmhos9depUhj4XGi509m2dGE+bPWkzqICAAE8XGQCKNIIFAKBA0lGfdLQnHQnqySeflBo1asidd97peP7ll182M2/Xr1/fo+UEAPwfRoUCAHicvSbi999/l9TUVBMmateubZ7T33v16iWbNm2STz75JEO4AAAUHPSxAAAUiFCxZs0a6dy5s/To0UOaNm0qU6ZMkfj4eClWrJgZBeruu+82AUNHgAIAFDwECwCAR2mo+Oyzz0wHbJ2jQkeBio6ONsFCh5I9ceKEI1w0atRIhg4dKpcuXfJ0sQEAmdAUCgDgUWfOnJERI0ZIgwYNZMKECaY5VKdOnSQsLEw2b94s//znP2XcuHESGhpqmkXpkLIhISGeLjYAIBNqLAAA+UrvZ9nvaV2+fFnKlCkj//jHP+TRRx+V06dPm6ZQ7dq1M7NtT5s2TRYsWCDPPfecCRRac0GoAICCiWABAMg39kChzZ++/vprmTdvnvj5+ZmZtXXOCm3uVL58eZk6darZrnTp0lK3bl3TaRsAULARLAAA+R4qVq5cKe3bt5eoqCj5448/pGzZsua5I0eOmO20FkNps6inn35aYmNjmfwOAAo45rEAAOQbDRUrVqyQ3r17ywsvvCCffvqpGV7WToeS1cnvIiMjJSUlRbZs2WI6c5cqVcqj5QYAXBs1FgCAfA0VOmTsu+++a2oifv31Vzl06JB5Xjtma5OoJUuWyMWLF02txX//+18zAzcAoOCjxgIAkC/++usvWbVqlbzzzjvSr18/SU5OlqCgIPNTacds1bdvX7NcuXJFihfnnykAKCz4PzYAIF8EBwfL7NmzpWLFiqYfhXbM1hGetP+Enc5boTUVw4YNI1QAQCFDUygAQJ521j527Jjs3bvXDC2rocLe7EnpiFD6vJo0aZKMHz9e2rZtK76+vh4sOQDgehAsAAB5wj76kwYFnaeiZcuWZjhZbfpkDw5VqlQx4eKVV16RmTNnyu7du6Vhw4aeLjoA4DpQzwwAyPWaCg0VBw8eNLUQOqRs69atzdwU06dPN30t+vfvb5o8VatWzUx+FxAQIFu3bpWmTZt6uvgAgOtEjQUAIFdpqNCmTxs2bJCOHTvKE088YWorVq9eLY0bNzYzaeuoUDqcbIsWLcyoT3v27JHmzZt7uugAAAt8bPZGsAAA5ALtP6GBQueg0GZQmzdvdoz4pAYOHCj79u2TRx99VEaPHi2JiYlmdCgAQOFGjQUAIFdpiNDaivvvv9/MpK39KpwnwXv77bclLCzMzGlx7tw5QgUAeAlqLAAA103/CdHFuUbC7tKlS2bCu7Nnz5rRnrp37y4lSpRwPH/ixAnTeRsA4B0IFgAAt5s6aZDQ4WPtQWHTpk3yxRdfmFm0hwwZInXr1jW1EjqDtj1cTJw4Ubp165YhXAAAvAdNoQAAboeK/fv3m1Ge1CeffCI9e/aU+Ph4Exp0FKhXX33VbFOqVClZs2aNVKhQQZ588kn5/PPPPf0WAAB5hGABAHArVPzwww/SoEEDCQwMlB9//FHGjBljgsQ777wj77//vsTFxZkw8dprr5khZzVcrFq1yuyjI0ABALwT81gAAHIcKn7++WcJDw8381NooNDmT/fdd58MGjRIjh49KnfffbcMGDBAatWqJWPHjjUT4Q0dOtQMM6s1GwAA70UfCwBAjkLFTz/9JO3bt5cbb7zRBAylzZ8uXLggVatWlQcffFCCg4PNPBUaKOrXry8JCQnSr18/02xKZ9jWOS4AAN6JplAAgBw1f2rVqpUJCzrvhM4/oSpXriw1atQwnbO1xqJz584mVJw5c0YaNWokI0aMkFGjRom/vz+hAgC8HMECAJAlDRW7d+82M2T/+9//lq+++komT54sS5cudYQLpcFC/fLLLyaEaP8KHSFKZ93W2gwAgPejjwUAIFs6ZOzw4cNNoFC9evUyPydMmGB+zp492/Sp0HkqdPK7RYsWyZUrV+TTTz9l8jsAKELoYwEAyDH9J0ObNCUlJcmyZctMuNCgMWfOHPP81q1bTVOo0NBQswAAig5qLAAAOWbvJ6FDzfbu3dv8ruFCw4TWXLRt29bDJQQAeArBAgBwXezhQvth6JCyJUuWlOnTp3u6WAAADyFYAAAshYuHHnrIzLit81sAAIou+lgAAHKt7wUAoOhiuFkAgGWECgAAwQIAAACAZQQLAAAAAJYRLAAAAABYRrAAAAAAYBnBAgAAAIBlBAsAAAAAlhEsAAAAAFhGsAAAAABgGcECAAAAgGUECwAAAACWESwAAAAAWEawAAAAACBW/X9MifIeJeJjvQAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "# Prepare data for plotting\n", + "top3_ratio_clean = top3_ratio.replace([float('inf'), float('nan')], None).dropna()\n", + "countries = top3_ratio_clean.index.tolist()\n", + "ratios = top3_ratio_clean.values\n", + "\n", + "plt.figure(figsize=(8, 5))\n", + "plt.bar(countries, ratios, color='tomato')\n", + "plt.ylabel('Positive Test Ratio')\n", + "plt.title('Top 3 Countries by Positive Test Ratio')\n", + "plt.xticks(rotation=45)\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "17147104", + "metadata": { + "vscode": { + "languageId": "java" + } + }, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "# Get top countries by positive cases (up to 10)\n", + "top_countries = total_positive_cases.head(10)\n", + "\n", + "plt.figure(figsize=(10, 6))\n", + "plt.bar(top_countries['Country'], top_countries['Positive Cases'], color='skyblue')\n", + "plt.xlabel('Country')\n", + "plt.ylabel('Total Positive Cases')\n", + "plt.title('Top Countries by Total COVID-19 Positive Cases')\n", + "plt.xticks(rotation=45)\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "52888431", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA90AAAJOCAYAAACqS2TfAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAS/1JREFUeJzt3QeUFFXeN+BLkqAEMaOgKKCiIiria06orzms2TXHNYtp1V2zYlhzzjln14BiQF2zq6w5YUIXRUUEUUGhv/O/7+n5ZoY0A1MOzDzPOc1MV9dU366uLvpXNzUplUqlBAAAANS5pnW/SQAAACAI3QAAAFAQoRsAAAAKInQDAABAQYRuAAAAKIjQDQAAAAURugEAAKAgQjcAAAAUROgGAACAggjdAPxh1lprrXz7IzVp0iQdeOCBqTH47LPP8uu9/vrr67soMMueM+rKIossknbbbbf6LgYwExC6gZlWhIea3AYPHlx4WS677LK0zTbbpC5duuTnnNoXqVGjRqV99tknzTPPPGn22WdPa6+9dnr99ddr9Xz33Xdf2nDDDdPcc8+dZpttttSpU6e07bbbpqeeeirNDP773/+mE088MQ0ZMiQ1dvHFuibHaU2C8KWXXvqHBuZvvvkmHXHEEWmJJZZIbdq0ycfrCiuskE499dR8HFdWKpXSTTfdlNZYY43UoUOHvP4yyyyTTj755DR27NiK9e699978eq+++uopPu+gQYPyOhdeeGG+H5+nOeaYo8o6EbTK+65p06apXbt2afHFF08777xz/vvauOOOO9Kf//zn1L1797y9qYW4f//73+l///d/8/O1bds2rb/++tM8zuMcVNPzVV1499138+cvLrLUtZn93FPXHnnkkbwvAYrUvNCtA8yA+IJf2Y033pi/bFdfvuSSSxZeljPPPDONGTMm9e3bNw0fPnyK602cODFtvPHG6T//+U868sgj8xfXCFLxJT++zMeX/qmJYLPHHnvk4LXccsul/v37p/nnnz8/Z3wZXnfdddPzzz+fVllllVTfofukk07KgbN37941/rvHH388NTTnn39++umnn6p8ib/tttvSeeedl9//spq8Z3GsxN/8EbVjr776atpoo41y2SOQRtgOr732WjrjjDPSs88+W/F+TZgwIe24447pzjvvTKuvvnoOKRG6n3vuuXwc3HXXXemJJ55I8803Xz7+27dvn2699da01157Tfa547FmzZql7bfffqplXGihhdKAAQPy7xHsP/744xzqb7755hwE42eLFi1qdNEsPn8rrrhi+v7776e4XlwcW2211VLnzp3TCSeckD/P8Z6sueaa6ZVXXsmhf3LiHFT9vHTMMcfkCwnHHXdcqmsRumO/x3klPoN1YVY599S1+LxecsklgjdQrBLALOKAAw4o1ddp67PPPitNnDgx/z777LOXdt1118mud8cdd+Qy3nXXXRXLRowYUerQoUNphx12mObznH322fnvDz300Irnq+zGG28svfzyy6X69uqrr+ZyXnfddTVaf+zYsaX6EuWMY+ePUn4PP/3001r/7VJLLVVac801p/u54zlr8r788MMPpQUXXLA033zzld57771JHv/6669Lp5xySsX9008/PW/3iCOOmGTdBx98sNS0adPS//7v/1Ys23PPPfOyr776apL1f/nll1L79u2rrB+fp/hcVRb7IfZHdb///ntp//33z+U56qijSjXxxRdflCZMmDDNfbzRRhuV5pxzztJ3331Xsey///1vaY455ihttdVWpT/yvZyaOL/E63/66afrbJsz67kn9mFR+7Ho/1cWXnjhKf5fATQuQjcwy5jcl6Offvqp1L9//9JCCy1Umm222Uo9evTIXx6rf2ksB6+bb745r9OyZcvS8ssvX3rmmWdqXY6phe5tttkmB5nyF/yyffbZp9SmTZvSr7/+OsXt/vzzz6WOHTuWllhiiRwsamLo0KGlrbfeOgeF1q1bl1ZaaaXSQw89VGWdCGCTC4Hxhb36F/dy0HnnnXdKa621Vt5mp06dSmeeeeYkf1f9Vg565W289tprpdVXXz1v45BDDpniF+jYJ8cff3xpscUWy+9hvJdHHnnkJPvq8ccfL6266qo5sMV7EO/jMcccM819VJP3/qmnnsrr3XvvvZP8/S233JIfe+GFF0rTG7p/++230sknn1xadNFF82uML+NR9sqvMZZV36flffX999+XDj/88NLSSy+dX3vbtm1zaB0yZMh0he4zzjgjrxevbVriuIzjK/ZdvI7J2X333fP2XnzxxSrHyDnnnDPJunfffXd+7Kabbpqu0B3i89GzZ8/8mRo1alSprsJw7Nf4DFe38cYb5/dtzJgxM/Q8cbEjPgvl81Uc8/FeVD9f3HbbbfkYjbAfZYr3/fzzz6/yea5+K3+O44LY+uuvX5prrrlKrVq1Ki2yyCL5/Zmaos495eMgLkaeeuqp+UJPfP7WWWed0kcffTTJNq+44or8GYlyr7jiiqVnn312knNGbc5n4aWXXiptuOGG+cJnHC/LLLNMxb6M425y+7Is3pfzzjsvH2tR7nnnnTefy0eOHFnlOeL/m7hIFa8v9kWcO99++22hG6igTzcwy4o8tdlmm+VmvNEH89xzz83NP6NZdzSNrO6ZZ55Jhx56aG5KG/1Qo5lp/N3bb79dZ2V644030vLLL5/7oFYWzdJ//vnn9OGHH07xb//1r3+lkSNH5ma80fS2Jv1xo6nnY489lvbff/902mmnpV9//TXvk2gOOr1++OGHvF+WXXbZdM455+T+vkcffXR69NFHK5rSxv4L0Xc9mtWW+/qWxb6NfqHR9DyaX0e/9smJ5rtR3n/84x9p0003TRdddFHaYost8nu63XbbVaz3zjvvpE022SSNGzcuP3eUK/4umrvWxLTe+2imG02Kb7nllkn+NpYttthiaeWVV07TK5pZH3/88fnYiNcWzZWj2XTl5tWxn6I5dezv8j4tN03+5JNP0v3335/3QRzncYy/9dZbeTvR1L+2HnzwwdS6deu09dZbT3PdOC7jmIjjsnnzyfdK22WXXfLPhx56KP+MYyFeSzQjry6WRdP0eJ+nV3w+dthhh/yZivLVlTi+Yr9UF+UdP378DJ0roqzxfkWT+Nhf0Z991VVXzc3QK5+vogtNvLY555wzd2uJpv5xfJaP9di3Bx98cP792GOPrThW4nM5YsSI3Ac9+nr/9a9/zZ+nnXbaKb300ktTLVvR5554DbE8xg+I1xvliXJVds0116R99903N2k/66yz8r6J7Q0bNixNr9iXsb+iOf4hhxySzxtxLiofp/F86623Xv69vB8rdxOIx+OzFmW54IIL0u67757PBxtssEH67bffKtaLz/bf//73fM48++yz06KLLprfh8pjHQCN3P/P3wCzVk33/fffn+9HDUplUfvSpEmT0scff1yxrFyDEbWvZZ9//nmuUdlyyy3rrKY7Httjjz0mWf7www/n5x84cOAUt3vBBRfkde67774alSOagcb6zz33XMWyqInr2rVrrt0q157VtqY7lkVT0rJx48aV5p9//tKf/vSnGjUvL2/j8ssvn+xjlWutorYz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0L3SSiul66+/Pg0cODBddtll6dNPP02rr756GjNmzGTXHzBgQGrfvn3FrXPnzn94mQEAAKCmmpRKpVKaSYwaNSotvPDC6dxzz0177rnnZGu641YWNd0RvH/88cfUrl27NLNb/aH+9V0EAACAmd5zm5ybZnaRR6MyeFp5tN77dFfWoUOH1KNHj/Txxx9P9vGWLVvmGwAAAMwK6r1Pd2U//fRTGjp0aFpggQXquygAAAAwa4fuI444Ij3zzDPps88+Sy+88ELacsstU7NmzdIOO+xQn8UCAACAOlGvzcu//PLLHLC///77NM8886TVVlstvfTSS/l3AAAAmNXVa+i+/fbb6/PpAQAAoPH06QYAAICGROgGAACAggjdAAAAUBChGwAAAAoidAMAAEBBhG4AAAAoiNANAAAABRG6AQAAoCBCNwAAABRE6AYAAICCCN0AAABQEKEbAAAACiJ0AwAAQEGEbgAAACiI0A0AAAAFEboBAACgIEI3AAAAFEToBgAAgIII3QAAAFAQoRsAAAAKInQDAABAQYRuAAAAKIjQDQAAAAURugEAAKAgQjcAAAAUROgGAACAggjdAAAAUBChGwAAAAoidAMAAEBBhG4AAAAoiNANAAAABRG6AQAAoCBCNwAAABRE6AYAAICCCN0AAABQEKEbAAAACiJ0AwAAQEGEbgAAACiI0A0AAAAFEboBAACgIEI3AAAAFEToBgAAgIII3QAAAFAQoRsAAAAKInQDAABAQYRuAAAAKIjQDQAAAAURugEAAKAgQjcAAAAUROgGAACAggjdAAAAUBChGwAAAAoidAMAAEBBhG4AAAAoiNANAAAABRG6AQAAoCBCNwAAABRE6AYAAICCCN0AAABQEKEbAAAACiJ0AwAAQEGEbgAAACiI0A0AAAAFEboBAACgIEI3AAAANPTQfcYZZ6QmTZqkQw89tL6LAgAAAA0ndL/66qvpiiuuSL169arvogAAAEDDCd0//fRT2mmnndJVV12V5pxzzvouDgAAADSc0H3AAQekjTfeOPXr12+a644bNy6NHj26yg0AAABmVs3r88lvv/329Prrr+fm5TUxYMCAdNJJJxVeLgAAAJila7qHDRuWDjnkkHTLLbekVq1a1ehvjjnmmPTjjz9W3GIbAAAAMLOqt5ruf//732nEiBFp+eWXr1g2YcKE9Oyzz6aLL744NyVv1qxZlb9p2bJlvgEAAMCsoN5C97rrrpveeuutKst23333tMQSS6Sjjz56ksANAAAAs5p6C91t27ZNSy+9dJVls88+e5prrrkmWQ4AAACzonofvRwAAAAaqnodvby6wYMH13cRAAAAoM6o6QYAAICCCN0AAABQEKEbAAAACiJ0AwAAQH0OpNa/f/8ab/Dcc8+dkfIAAABA4wrdb7zxRpX7r7/+evr999/T4osvnu9/+OGHqVmzZmmFFVYoppQAAADQUEP3008/XaUmu23btumGG25Ic845Z172ww8/pN133z2tvvrqxZUUAAAAGnqf7nPOOScNGDCgInCH+P3UU0/NjwEAAADTGbpHjx6dvv3220mWx7IxY8bUdnMAAADQYNU6dG+55Za5Kfm9996bvvzyy3y755570p577pm22mqrYkoJAAAADbVPd2WXX355OuKII9KOO+6Yfvvtt//bSPPmOXSfffbZRZQRAAAAGkfobtOmTbr00ktzwB46dGhetthii6XZZ5+9iPIBAABA42leXjZ8+PB86969ew7cpVKpbksGAAAAjS10f//992nddddNPXr0SBtttFEO3iGalx9++OFFlBEAAAAaR+g+7LDDUosWLdIXX3yRm5qXbbfddmngwIF1XT4AAABoPH26H3/88fTYY4+lhRZaqMryaGb++eef12XZAAAAoHHVdI8dO7ZKDXfZyJEjU8uWLeuqXAAAAND4Qvfqq6+ebrzxxor7TZo0SRMnTkxnnXVWWnvtteu6fAAAANB4mpdHuI6B1F577bU0fvz4dNRRR6V33nkn13Q///zzxZQSAAAAGkNN99JLL50+/PDDtNpqq6XNN988Nzffaqut0htvvJHn6wYAAACms6Y7Ri3v3LlzOu644yb7WJcuXWq7SQAAAGiQal3T3bVr1/Ttt99Odv7ueAwAAACYztBdKpXy4GnV/fTTT6lVq1a13RwAAAA0WDVuXt6/f//8MwL33//+9yrThk2YMCG9/PLLqXfv3sWUEgAAABpy6I6B0so13W+99VaabbbZKh6L35dddtl0xBFHFFNKAAAAaMih++mnn84/d99993TBBRekdu3aFVkuAAAAaHx9us8///z0+++/T7I85ukePXp0XZULAAAAGl/o3n777dPtt98+yfI777wzPwYAAABMZ+iOAdPWXnvtSZavtdZa+TEAAABgOkP3uHHjJtu8/Lfffku//PJLbTcHAAAADVatQ3ffvn3TlVdeOcnyyy+/PK2wwgp1VS4AAACY5dV49PKyU089NfXr1y/95z//Seuuu25e9uSTT6ZXX301Pf7440WUEQAAABpHTfeqq66aXnzxxdS5c+c8eNo///nP1K1bt/Tmm2+m1VdfvZhSAgAAQGOo6Q69e/dOt9xyS92XBgAAABpz6P7iiy+m+niXLl1mpDwAAADQeEP3Iosskpo0aTLFxydMmDCjZQIAAIDGGbrfeOONSaYKi2XnnntuOu200+qybAAAANC4Qveyyy47ybI+ffqkTp06pbPPPjtttdVWdVU2AAAAaFyjl0/J4osvnqcNAwAAAKazpnv06NFV7pdKpTR8+PB04oknpu7du9d2cwAAANBg1Tp0d+jQYZKB1CJ4x7zdt99+e12WDQAAABpX6H766aer3G/atGmaZ555Urdu3VLz5tM17TcAAAA0SLVOyWuuuWYxJQEAAIDGGLoffPDBGm9ws802m5HyAAAAQOMK3VtssUWV+9GnO/pxV75fNmHChLosHwAAADTsKcMmTpxYcXv88cdT796906OPPppGjRqVb4888khafvnl08CBA4svMQAAADTUPt2HHnpouvzyy9Nqq61WsWyDDTZIbdq0Sfvss09677336rqMAAAA0HBruisbOnRonjasuvbt26fPPvusrsoFAAAAjS90r7jiiql///7pm2++qVgWvx955JGpb9++dV0+AAAAaDyh+9prr03Dhw9PXbp0yXNzxy1+/+qrr9I111xTTCkBAACgMfTpjpD95ptvpkGDBqX3338/L1tyySVTv379qoxiDgAAAI1drUN3iHC9/vrr5xsAAABQh6H7ySefzLcRI0bkacSqNz8HAAAApiN0n3TSSenkk09Offr0SQsssIAm5QAAAFBXoTvm6L7++uvTzjvvXNs/BQAAgEal1qOXjx8/Pq2yyirFlAYAAAAac+jea6+90q233lpMaQAAAKAxNy//9ddf05VXXpmeeOKJ1KtXr9SiRYsqj5977rl1WT4AAABoPKE75uju3bt3/v3tt9+u8phB1QAAAGAGQvfTTz9d2z8BAACARqnWfbor+/LLL/MNAAAAqIPQPXHixDxPd/v27dPCCy+cbx06dEinnHJKfgwAAACYzublxx13XLrmmmvSGWeckVZdddW87F//+lc68cQT8yBrp512Wm03CQAAAA1SrUP3DTfckK6++uq02WabVSyLUcwXXHDBtP/++wvdAAAAML3Ny0eOHJmWWGKJSZbHsngMAAAAmM7Qveyyy6aLL754kuWxLB6rjcsuuyzXkrdr1y7fVl555fToo4/WtkgAAADQMJqXn3XWWWnjjTdOTzzxRA7J4cUXX0zDhg1LjzzySK22tdBCC+W+4d27d0+lUik3Xd98883TG2+8kZZaaqnaFg0AAABm7ZruNddcM3344Ydpyy23TKNGjcq3rbbaKn3wwQdp9dVXr9W2Nt1007TRRhvl0N2jR4/cH3yOOeZIL730Um2LBQAAALN+TXfo1KlTnQ+YNmHChHTXXXelsWPHVtSgAwAAQKOo6f7oo4/SDjvskEaPHj3JYz/++GPacccd0yeffFLrArz11lu5drtly5Zpv/32S/fdd1/q2bPnZNcdN25cfv7KNwAAAJjlQ/fZZ5+dOnfunAc8q659+/b5sVinthZffPE0ZMiQ9PLLL6e//OUvadddd03vvvvuZNcdMGBAfq7yLZ4TAAAAZvnQ/cwzz6Rtttlmio9vu+226amnnqp1AWabbbbUrVu3tMIKK+RQHSOgX3DBBZNd95hjjsm16uVbDN4GAAAAs3yf7i+++CLNO++8U3x87rnnrpMQPHHixNyMfHKiCXrcAAAAoEGF7mjOPXTo0LTwwgtP9vGPP/54sk3PpyZqrjfccMPUpUuXNGbMmHTrrbemwYMHp8cee6xW2wEAAIBZOnSvscYa6aKLLkrrrLPOZB+/8MILaz1l2IgRI9Iuu+yShg8fnkN9r169cuBeb731arUdAAAAmKVDd9RKx1ReW2+9dTrqqKPyAGjh/fffT2eddVYOyy+88EKtnvyaa66pfYkBAACgoYXu5ZZbLt19991pjz32yNN6VTbXXHOlO++8My2//PJFlBEAAAAadugOm2yySfr888/TwIEDcx/uUqmUevTokdZff/3Upk2b4koJAAAADT10h9atW6ctt9yymNIAAABAY5ynGwAAAKgdoRsAAAAKInQDAABAQYRuAAAAmFlC9+uvv57eeuutivsPPPBA2mKLLdKxxx6bxo8fX9flAwAAgMYTuvfdd9/04Ycf5t8/+eSTtP322+fpwu6666501FFHFVFGAAAAaByhOwJ379698+8RtNdYY4106623puuvvz7dc889RZQRAAAAGkfoLpVKaeLEifn3J554Im200Ub5986dO6fvvvuu7ksIAAAAjSV09+nTJ5166qnppptuSs8880zaeOON8/JPP/00zTfffEWUEQAAABpH6D7vvPPyYGoHHnhgOu6441K3bt3y8rvvvjutssoqRZQRAAAAZknNa/sHyy67bJXRy8vOPvvs1Lx5rTcHAAAADVata7oXXXTR9P3330+y/Ndff009evSoq3IBAABA4wvdn332WZowYcIky8eNG5e+/PLLuioXAAAAzPJq3B78wQcfrPj9scceS+3bt6+4HyH8ySefTF27dq37EgIAAEBDD91bbLFF/tmkSZO06667VnmsRYsWaZFFFknnnHNO3ZcQAAAAGnroLs/NHbXZr776app77rmLLBcAAADM8mo93HjMx13dqFGjUocOHeqqTAAAANA4B1I788wz0x133FFxf5tttkkdO3ZMCy64YPrPf/5T1+UDAACAxhO6L7/88tS5c+f8+6BBg9ITTzyRBg4cmDbccMN05JFHFlFGAAAAaBzNy7/++uuK0P3QQw+lbbfdNq2//vp5ILWVVlqpiDICAABA46jpnnPOOdOwYcPy71HD3a9fv/x7qVSa7PzdAAAA0FjVuqZ7q622SjvuuGPq3r17+v7773Oz8vDGG2+kbt26FVFGAAAAaByh+7zzzstNyaO2+6yzzkpzzDFHXj58+PC0//77F1FGAAAAaByhu0WLFumII46YZPlhhx1WV2UCAACAxtmnO9x0001ptdVWS506dUqff/55Xnb++eenBx54oK7LBwAAAI0ndF922WWpf//+uS/3qFGjKgZP69ChQw7eAAAAwHSG7osuuihdddVV6bjjjkvNmjWrWN6nT5/01ltv1XZzAAAA0GDVOnR/+umnabnllptkecuWLdPYsWPrqlwAAADQ+EJ3165d05AhQyZZHnN2L7nkknVVLgAAAGg8o5effPLJedTy6M99wAEHpF9//TWVSqX0yiuvpNtuuy0NGDAgXX311cWWFgAAABpi6D7ppJPSfvvtl/baa6/UunXr9Le//S39/PPPaccdd8yjmF9wwQVp++23L7a0AAAA0BBDd9Rql+200075FqH7p59+SvPOO29R5QMAAICGH7pDkyZNqtxv06ZNvgEAAAAzGLp79OgxSfCubuTIkbXZJAAAADRYtQrd0a+7ffv2xZUGAAAAGmvojoHS9N8GAACAOp6ne1rNygEAAIDpDN2VRy8HAAAA6rB5+cSJE2u6KgAAAFCbmm4AAACgdoRuAAAAKIjQDQAAAAURugEAAKA+B1J78MEHa7zBzTbbbEbKAwAAAI0rdG+xxRY1nst7woQJM1omAAAAaDyh23RhAAAAUHv6dAMAAEB91nRXN3bs2PTMM8+kL774Io0fP77KYwcffHBdlQ0AAAAaV+h+44030kYbbZR+/vnnHL47duyYvvvuu9SmTZs077zzCt0AAAAwvc3LDzvssLTpppumH374IbVu3Tq99NJL6fPPP08rrLBC+sc//lHbzQEAAECDVevQPWTIkHT44Yenpk2bpmbNmqVx48alzp07p7POOisde+yxxZQSAAAAGkPobtGiRQ7cIZqTR7/u0L59+zRs2LC6LyEAAAA0lj7dyy23XHr11VdT9+7d05prrpmOP/743Kf7pptuSksvvXQxpQQAAIDGUNN9+umnpwUWWCD/ftppp6U555wz/eUvf0nffvttuuKKK4ooIwAAADSOmu4+ffpU/B7NywcOHFjXZQIAAIDGWdO9zjrrpFGjRk2yfPTo0fkxAAAAYDpD9+DBg9P48eMnWf7rr7+m5557rrabAwAAgAarxs3L33zzzYrf33333fT1119X3J8wYUJuZr7gggvWfQkBAACgoYfu3r17pyZNmuTb5JqRt27dOl100UV1XT4AAABo+KH7008/TaVSKS266KLplVdeSfPMM0/FY7PNNlseVK1Zs2ZFlRMAAAAabuheeOGF88+JEycWWR4AAABovFOGhaFDh6bzzz8/vffee/l+z5490yGHHJIWW2yxui4fAAAANJ7Ryx977LEcsqOJea9evfLt5ZdfTksttVQaNGhQrbY1YMCAtOKKK6a2bdvm5ulbbLFF+uCDD2pbJAAAAGgYNd1//etf02GHHZbOOOOMSZYfffTRab311qvxtp555pl0wAEH5OD9+++/p2OPPTatv/76eXT02WefvbZFAwAAgJlKk1KMjlYLrVq1Sm+99Vbq3r17leUffvhhrvWO+bqn17fffptrvCOMr7HGGtNcf/To0al9+/bpxx9/TO3atUszu9Uf6l/fRQAAAJjpPbfJuWlmV9M8Wuvm5TFq+ZAhQyZZHssiMM+IKGzo2LHjDG0HAAAAZqnm5SeffHI64ogj0t5775322Wef9Mknn6RVVlklP/b888+nM888M/XvP/01uTEq+qGHHppWXXXVtPTSS092nXHjxuVb5SsLAAAAMMuH7pNOOintt99+6e9//3se+Oycc85JxxxzTH6sU6dO6cQTT0wHH3zwdBck+na//fbb6V//+tdUB16LcgAAAECD6tPdtGnT9PXXX1dpQj5mzJj8M0L4jDjwwAPTAw88kJ599tnUtWvXKa43uZruzp0769MNAADQgDzXgPp012r08iZNmlS5P6NhO/L+QQcdlO677740ePDgqQbu0LJly3wDAACAWUGtQnePHj0mCd7VjRw5slZNym+99dZcyx0BPmrSQ1wtaN26dW2KBgAAALN26I7+1BGI68pll12Wf6611lpVll933XVpt912q7PnAQAAgJk+dG+//fYzPC1YZbWcIhwAAABmKTWep3tazcoBAACA6QzdaqUBAACgoOblEydOrOWmAQAAoHGrcU03AAAAUDtCNwAAABRE6AYAAICCCN0AAABQEKEbAAAACiJ0AwAAQEGEbgAAACiI0A0AAAAFEboBAACgIEI3AAAAFEToBgAAgIII3QAAAFAQoRsAAAAKInQDAABAQYRuAAAAKIjQDQAAAAURugEAAKAgQjcAAAAUROgGAACAggjdAAAAUBChGwAAAAoidAMAAEBBhG4AAAAoiNANAAAABRG6AQAAoCBCNwAAABRE6AYAAICCCN0AAABQEKEbAAAACiJ0AwAAQEGEbgAAACiI0A0AAAAFEboBAACgIEI3AAAAFEToBgAAgIII3QAAAFAQoRsAAAAKInQDAABAQYRuAAAAKIjQDQAAAAURugEAAKAgQjcAAAAUROgGAACAggjdAAAAUBChGwAAAAoidAMAAEBBhG4AAAAoiNANAAAABRG6AQAAoCBCNwAAABRE6AYAAICCCN0AAABQEKEbAAAACiJ0AwAAQEGEbgAAACiI0A0AAAAFEboBAACgIEI3AAAAFEToBgAAgIII3QAAAFAQoRsAAAAKInQDAABAQYRuAAAAaIih+9lnn02bbrpp6tSpU2rSpEm6//7767M4AAAA0HBC99ixY9Oyyy6bLrnkkvosBgAAABSieapHG264Yb4BAABAQ1Svobu2xo0bl29lo0ePrtfyAAAAQIMZSG3AgAGpffv2FbfOnTvXd5EAAACgYYTuY445Jv34448Vt2HDhtV3kQAAAKBhNC9v2bJlvgEAAMCsYJaq6QYAAIBZSb3WdP/000/p448/rrj/6aefpiFDhqSOHTumLl261GfRAAAAYNYO3a+99lpae+21K+73798//9x1113T9ddfX48lAwAAgFk8dK+11lqpVCrVZxEAAACgMPp0AwAAQEGEbgAAACiI0A0AAAAFEboBAACgIEI3AAAAFEToBgAAgIII3QAAAFAQoRsAAAAKInQDAABAQYRuAAAAKIjQDQAAAAURugEAAKAgQjcAAAAUROgGAACAggjdAAAAUBChGwAAAAoidAMAAEBBhG4AAAAoiNANAAAABRG6AQAAoCBCNwAAABRE6AYAAICCCN0AAABQEKEbAAAACiJ0AwAAQEGEbgAAACiI0A0AAAAFEboBAACgIEI3AAAAFEToBgAAgIII3QAAAFAQoRsAAAAKInQDAABAQYRuAAAAKIjQDQAAAAURugEAAKAgQjcAAAAUROgGAACAggjdAAAAUBChGwAAAAoidAMAAEBBhG4AAAAoiNANAAAABRG6AQAAoCBCNwAAABRE6AYAAICCCN0AAABQEKEbAAAACiJ0AwAAQEGEbgAAACiI0A0AAAAFEboBAACgIEI3AAAAFEToBgAAgIII3QAAAFAQoRsAAAAKInQDAABAQYRuAAAAKIjQDQAAAAURugEAAKAgQjcAAAAUROgGAACAggjdAAAAUBChGwAAAAoidAMAAEBDDt2XXHJJWmSRRVKrVq3SSiutlF555ZX6LhIAAADM+qH7jjvuSP37908nnHBCev3119Oyyy6bNthggzRixIj6LhoAAADM2qH73HPPTXvvvXfafffdU8+ePdPll1+e2rRpk6699tr6LhoAAADMkOapHo0fPz79+9//Tsccc0zFsqZNm6Z+/fqlF198cZL1x40bl29lP/74Y/45evToNCv4/ef/X3YAAAAmb1bIeOUylkqlmTd0f/fdd2nChAlpvvnmq7I87r///vuTrD9gwIB00kknTbK8c+fOhZYTAACAP077dGmaVYwZMya1b99+5gzdtRU14tH/u2zixIlp5MiRaa655kpNmjSp17IBwKwmrtDHhethw4aldu3a1XdxAGCWEjXcEbg7deo01fXqNXTPPffcqVmzZumbb76psjzuzz///JOs37Jly3yrrEOHDoWXEwAasgjcQjcA1N7UarhnioHUZptttrTCCiukJ598skrtddxfeeWV67NoAAAAMMPqvXl5NBffddddU58+fVLfvn3T+eefn8aOHZtHMwcAAIBZWb2H7u222y59++236fjjj09ff/116t27dxo4cOAkg6sBAHUrumydcMIJk3TdAgDqTpPStMY3BwAAAKZLvfbpBgAAgIZM6AYAAICCCN0AAABQEKEbAAAACiJ0AwAAQEGEbgDgD1GeMOX333+vch8AGjKhGwAoXATsJk2apEGDBqWDDjoojRw5Mt8HgIZO6AYAChcB+5577knbbrttmn322dPQoUMrwrgabwAasiYl/9MBAAV744030nrrrZdOP/30tM8++1QsHz16dGrXrl29lg0AiqSmGwAo3Ntvv52WWmqpHLh/+OGHdPvtt6dNNtkkLb300umcc86p6OcNAA1N8/ouAADQsPtxh/nnnz8999xz6YQTTkhPPfVUmnPOOdNCCy2UVlpppXTkkUem9ddfPy2zzDL1XWQAqHNCNwBQSNj+8ccfU+vWrdOvv/6am5afddZZ6aabbkprrLFG2m233dLyyy+f1/3nP/+Zxo4dW9/FBoBC6NMNANR54H744YfThRdemEaNGpXvR1/uddZZJ/3888+pTZs2Fesfe+yx6a677sq14FEbDgANjT7dAECdiYD90EMPpa233jrXbp966qlp8cUXT/369UtDhgypCNyPP/542mOPPdJVV12V7rzzToEbgAZL83IAoM6MHz8+XXvttelvf/tbOuKII9KwYcPS888/n/bee+/Uu3fvvE7Udn/yySe5SfngwYPzAGsA0FBpXg4AzJAJEyakZs2a5d9HjhyZ+vbtm6677ro8MnkMjrbxxhunK664Ij9+9dVX5/vzzDNP7us9xxxz1HPpAaBYmpcDALUyceLE/HPMmDE5OEfgfvrpp/Oyjh07pjXXXDPde++9qVevXmnTTTdNl1xySX4sBlaLZuUxcFr8jcANQGMgdAMAtdK0adP05Zdfps022yw9++yz6bbbbkvrrrtueuSRR/Lj3bt3TzfeeGP+GXNwN2/+f73ZzjzzzNyvO6YHK08lBgANneblAECtjRs3Lm244Ya5z/Znn32Wm4/HwGhle+21V+7Lvdxyy6Vu3brlPtwxonnUiJf7dgNAY6CmGwCodR/uli1bpqOPPjp9/vnnqVOnTnn08QjiZdF3e88998xTiEXQbteuXQ7hAjcAjY2abgBgurz88svpm2++ydN+ffXVV+mYY47JTc4jkFcW/b5jmSblADRGQjcAUCPxlaEcnGMwtejbHaKGe/PNN08jRozIU4VtsskmabbZZkvXX3992m233eq51ABQv4RuAKDGgfvJJ59Mjz32WHr//fdzv+2YEqxr164Vwfv7779PW265ZRo9enQ666yz8no9evSo7+IDQL0RugGAGrnvvvvSrrvumv70pz/lkP3666+n9dZbL+2zzz45fI8fPz7tvvvueWTzUaNGpRtuuEEfbgAaPaEbAJimf//732nrrbfOzcdjgLQI3TEn9zzzzJOD92GHHZZ69uyZB1n77rvvch/uDh061HexAaDe/d/EmQAA1VTut/3tt9/mZuMRuGOKsLXXXjvXakfQPvzww1OzZs3Sfvvtl2u255tvvvouOgDMNNR0AwBVQvZPP/2UWrRokWurow/30ksvndq2bZuGDx+eFl544VzjHbXcV155ZWrevHnq1atX+vrrr9NOO+2UzjzzzDyIGgDwf8zTDQBkEbhj6q8VVlghNye/7bbbctPx6Lvdpk2btNhii+W+2p9++mlad911c+CO+xG6DzrooHTooYcK3ABQjeblAECFBRdcMHXr1i3Pt/3DDz+kq6++Om244YZ59PIwZsyYPIr5hx9+mN544430wAMPpHfffTddcsklqX379vVdfACY6WheDgCNWLlJ+dixY9Ovv/6a5pprrvT888+n1VdfPTcpf/DBB9PKK69cpQb7lFNOSdddd10eNO33339P//znP9Pyyy9fr68DAGZWQjcANPLAHbXWJ598cg7X2223Xa7NHjp0aO6z/fTTT6frr78+NzOvHLzffvvt3LR8kUUWSQsttFC9vg4AmJnp0w0AjThwv/nmm2nNNdfMo4937tw513R37do19evXL915551p1VVXzXNzx4BqMQ93iPm355577rTaaqsJ3AAwDWq6AaCRiqm/1lhjjTzq+GmnnVYxPVj16cKif/crr7ySjj766PT555+nCy+8ML333ntp8cUXr8fSA8CsQegGgEbqggsuSI8++mi67777UuvWrfOyL7/8Mn3wwQe5+fj888+fm5uHXXbZJX300Ufpl19+yc3NYz5uAGDajF4OAI1UBOxQ7qt9++235yblMZBazNH97bff5unCYu7tG2+8Mc/THVOHGaUcAGpOn24AaKR69uyZnnjiidxsfIcddkgHHHBA6tKlS7rrrrvyNGDHHXdcuvfee/NAa2GBBRYQuAGgltR0A0Ajtfvuu6dvvvkmPfzww/n+rbfemlZcccXUsWPHfD+alzdv3jwPmgYATB99ugGggYv/6ps0aTLFx2OO7hYtWlSZEiwcccQR6f3330+33XZbnrMbAKg9Nd0A0IDFSOOLLbZY2mCDDXKt9eTC+Oyzz15l+Q8//JD7cV977bXp2WefFbgBYAao6QaABmzllVdOH3/8ca6tXmuttSYJ3tUdc8wxef3//Oc/eVA1o5QDwIwxkBoANEDla+ovvvhi6tu3b9p5553TU089lcaPHz/Vv1tyySVTjx490sCBAwVuAKgDaroBoIGKgF3up73KKqvkvttnn312WmeddaZa4/3bb7/lPt4AwIxT0w0ADVBcU4/AHXNvb7311nlE8nfeeSftu+++6emnn06///77FP9W4AaAuiN0A0ADFAOkRdPyPffcM2200UbpH//4R3rzzTfTIossknbbbbdpBm8AoG4I3QDQQMV0X927d0/bbrttWmKJJVLPnj1z2I5l5RrvafXxBgBmjNANAA1MebiWMWPGpBEjRqQ55pgj3//ll1/yz7POOit9/vnnadddd03PP/98vZYVABo6oRsAGmDT8rD99tvnAH7AAQfk+61bt67os73ddtul5ZdfPi244IL1WlYAaOimPlknADDTi2AdQfvVV19NL7/8cpo4cWJuQr7hhhumk046KZ177rnpL3/5S7rwwgvTqFGj0j333JP/7v7775/mvN0AwIzxPy0AzOIicEeQ3m+//VKfPn1Sq1at0tFHH50GDBiQ9t5779S0adN08sknp7vvvjuPYv7dd9+lQYMGCdwA8AcwTzcAzOLefffd1K9fv/S3v/0t7b///nlqsOWWWy7ts88+6eKLL04TJkxIP/30U7rvvvtS+/btU+/evVPXrl3ru9gA0CgI3QAwi4pm5FGL/dhjj+Va7cGDB+cB0lZbbbW06aabpksvvTSv99Zbb6VlllmmvosLAI2SgdQAYBYJ2JV/hp9//jn/jOvnMed29OleY4018rzcF110UX4s5uqO34cNG1ZPJQeAxk3oBoBZQNRof/TRR3nws3DXXXflGu3Ro0enBRZYIDcfX2+99XIz8yuuuCI1a9Ysr3fnnXem4cOHp7Zt29bzKwCAxknoBoBZRNRYb7311ql///5phx12SIcddlhq165dWnbZZdNuu+2WA/hiiy2Wm5N//PHH6cgjj0w33HBDbnreoUOH+i4+ADRK+nQDwEzs5ptvTr169cq3sO6666ZnnnkmD5JW7rNddvzxx+cRyr/44ou0+OKLp3HjxuW/j4HTAID6IXQDwEwo/nuO2uotttgiPfroo6lLly4VofuXX37JtdlXXXVVfjymCCv74IMP0n//+988NVinTp3SPPPMU4+vAgAQugFgJhZ9teeYY440ZMiQNNdcc6XOnTvn5bvvvnvu13311VenLbfcMrVs2TIvHzlyZA7cAMDMQZ9uAJiJtWnTJgfpzTbbLB188MF5hPJw3XXXpe222y7tvffe6d57700//vhjOu2009L666+fm5W7pg4AMwc13QAwk4r/ops0aZJ/f/zxx9OBBx6Y+vbtmw455JC04oor5uXRt/v2229PPXv2zE3LBw0alPr06VPPJQcAyoRuAJhJw3bMwx013RMmTMhTgD3xxBO5ZnvVVVetErxvvfXW3M97zTXXTN26davv4gMAlQjdADATeuSRR/Lo5C1atMhzb//5z39O7du3zzXZUbsdwfvQQw9Vqw0AMzl9ugFgJvPCCy/kwdF69OiRxowZk6f9iqbl0bd7vfXWS1deeWV65ZVX0imnnJLefPPN+i4uADAVQjcA1KNyg7OJEyfmnx999FF68cUX04ABA9K5556b+3JHLfcnn3xSJXiff/756fPPP09zzz13Pb8CAGBqhG4AqMewHX2xQ9OmTfNAaHvttVcO1NGUvLw8mpPvtNNO6bPPPstNyr///vu00UYb5RrxmIsbAJh5Cd0AUA9ioLRvvvkmLbPMMunBBx/MyxZYYIG00kor5UD+8MMPV9R+R7/ufffdN+2yyy7ptddeS3/961/zY61atarnVwEATEvzaa4BABQiarljCrCo3b722mvTJptsko4//vg8YvkDDzyQw/Wpp56aZptttjx6eawXAXzdddfNNeAAwMzP6OUAUI+ir/YZZ5yR7rrrrnTTTTfl4B2Dp5155pl5irDVV189nXbaaTl4AwCzHqEbAP4A0Ry8cu3077//npo3/78GZ0OHDs0h+84778wjlVcO3oMHD069evXK/bwFbwCY9WibBgB/gAjcw4YNS/fcc0++H4F7woQJ+ffFFlssHX300WnbbbfNTciffPLJ1LZt23TMMcfkPt4xovmoUaPq+RUAANNDTTcA/AGiZjsGQnv//ffTUUcdlbbffvu8PIJ39NcO7733XjrppJPSiBEj0h133JHmmWee9PPPP6exY8fm3wGAWY+abgD4A0TN9sknn5y6dOmSrrzyynTrrbfm5RG4yzXeSy65ZNp6663Tu+++m0aPHp2XxaBqAjcAzLqEbgD4g3Tr1i2dd955OUhfffXV6bbbbqsI3r/99lv+vUePHmneeeetmMcbAJi1Cd0A8Afq2rVruuiii3Lwvuqqq9INN9yQl8dUYOGWW27Jj80999z1XFIAoC7o0w0A9eDTTz9Nhx9+ePrqq6/S//zP/6RVVlklPffcc3nqsEGDBuURywGAWZ/QDQD15Msvv0zXXHNNuvfee3MT886dO6fTTz89LbXUUvVdNACgjgjdADATzOH9yy+/5ODdqlWr+i4OAFCHhG4AqEfx33CTJk3quxgAQEEMpAYA9UjgBoCGTegGAACAggjdAAAAUBChGwAAAAoidAMAAEBBhG4AAAAoiNANAAAABRG6AQAAoCBCNwAAABRE6AaAmdTXX3+dDjrooLToooumli1bps6dO6dNN900Pfnkk39oOZo0aZLuv//+P/Q5AaChaF7fBQAAJvXZZ5+lVVddNXXo0CGdffbZaZlllkm//fZbeuyxx9IBBxyQ3n///TQzGT9+fJptttnquxgAMNNR0w0AM6H9998/1zC/8sor6U9/+lPq0aNHWmqppVL//v3TSy+9lNf54osv0uabb57mmGOO1K5du7Ttttumb775pmIbu+22W9piiy2qbPfQQw9Na621VsX9+P3ggw9ORx11VOrYsWOaf/7504knnljx+CKLLJJ/brnllrk85fuxTu/evdPVV1+dunbtmlq1apVuvPHGNNdcc6Vx48ZVec4ow84771zQngKAmZvQDQAzmZEjR6aBAwfmGu3ZZ599ksej9nvixIk5cMe6zzzzTBo0aFD65JNP0nbbbVfr57vhhhvy87z88svprLPOSieffHLeXnj11Vfzz+uuuy4NHz684n74+OOP0z333JPuvffeNGTIkLTNNtukCRMmpAcffLBinREjRqSHH3447bHHHtO5NwBg1qZ5OQDMZCLMlkqltMQSS0xxnejX/dZbb6VPP/009/UOUdMcteERjFdcccUaP1+vXr3SCSeckH/v3r17uvjii/P211tvvTTPPPNUBP2oBa/epDyes7xO2HHHHXNAjwAebr755tSlS5cqtesA0Jio6QaAmUwE7ml57733ctguB+7Qs2fPHI7jsdqI0F3ZAgsskGuop2XhhReuErjD3nvvnR5//PH01Vdf5fvXX399buYeTdMBoDFS0w0AM5mobY6QOqODpTVt2nSSAB+DsVXXokWLKvfjuaP5+rRMrun7csstl5ZddtlcA77++uund955JzcvB4DGSk03AMxkYkCzDTbYIF1yySVp7Nixkzw+atSotOSSS6Zhw4blW9m7776bH4sa7xC10NEPu7Loe11bEcqjr3ZN7bXXXrmGO5qZ9+vXr0ptPAA0NkI3AMyEInBH0O3bt28erOyjjz7KzcYvvPDCtPLKK+cwG9OI7bTTTun111/Po5zvsssuac0110x9+vTJ21hnnXXSa6+9lmud4++j3/bbb79d67LEiOXRxzvmDf/hhx+muX706/7yyy/TVVddZQA1ABo9oRsAZkKLLrpoDtNrr712Ovzww9PSSy+dBzaL8HvZZZflJuAPPPBAmnPOOdMaa6yRQ3j8zR133FGxjagt//vf/56nA4uB1caMGZODeW2dc845eTTzqLGO5uPT0r59+zzNWUxlVn3KMgBobJqUajJaCwBALay77rp5JPWomQeAxkzoBgDqTDQ/Hzx4cNp6661zH/PFF1+8vosEAPXK6OUAQJ2J5ucRvM8880yBGwDUdAMAAEBxDKQGAAAABRG6AQAAoCBCNwAAABRE6AYAAICCCN0AAABQEKEbAAAACiJ0AwAAQEGEbgAAACiI0A0AAACpGP8PEYY9kDGWQcYAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.figure(figsize=(10, 6))\n", + "top10_tested = top_tested_countries.head(10)\n", + "plt.bar(top10_tested['Country'], top10_tested['Total Tested'], color='mediumseagreen')\n", + "plt.xlabel('Country')\n", + "plt.ylabel('Total Tests Conducted')\n", + "plt.title('Top 10 Countries by Total COVID-19 Tests Conducted')\n", + "plt.xticks(rotation=45)\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "17ebbe44", + "metadata": {}, + "source": [ + "Next steps:\n", + "- Clean country names and ensure consistent date parsing\n", + "- Fill or interpolate missing cumulative test counts\n", + "- Calculate per-capita testing by merging population data\n", + "- Add interactive visualizations with Plotly or Altair" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/exercisefiles/datascientist/DiabetesTreeClassifier.ipynb b/exercisefiles/datascientist/DiabetesTreeClassifier.ipynb new file mode 100644 index 00000000..1b446b74 --- /dev/null +++ b/exercisefiles/datascientist/DiabetesTreeClassifier.ipynb @@ -0,0 +1,509 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "487e0251", + "metadata": {}, + "source": [ + "# Diabetes Tree Classifier\n", + "\n", + "This notebook demonstrates a simple Decision Tree classifier on the `diabetes.csv` dataset included in this folder. It covers:\n", + "\n", + "- Data loading and inspection\n", + "- Train/test split\n", + "- Training a `DecisionTreeClassifier`\n", + "- Evaluation with accuracy and a classification report\n", + "\n", + "The dataset file `diabetes.csv` is expected to be in the same folder as this notebook." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "f162c781", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "from sklearn.model_selection import train_test_split\n", + "from sklearn.tree import DecisionTreeClassifier\n", + "from sklearn import metrics" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "c94e7e17", + "metadata": {}, + "outputs": [], + "source": [ + "df = pd.read_csv('diabetes.csv')" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "6d89d709", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
pregnantglucosebpskininsulinbmipedigreeagelabel
0PregnanciesGlucoseBloodPressureSkinThicknessInsulinBMIDiabetesPedigreeFunctionAgeOutcome
161487235033.60.627501
21856629026.60.351310
38183640023.30.672321
418966239428.10.167210
\n", + "
" + ], + "text/plain": [ + " pregnant glucose bp skin insulin bmi \\\n", + "0 Pregnancies Glucose BloodPressure SkinThickness Insulin BMI \n", + "1 6 148 72 35 0 33.6 \n", + "2 1 85 66 29 0 26.6 \n", + "3 8 183 64 0 0 23.3 \n", + "4 1 89 66 23 94 28.1 \n", + "\n", + " pedigree age label \n", + "0 DiabetesPedigreeFunction Age Outcome \n", + "1 0.627 50 1 \n", + "2 0.351 31 0 \n", + "3 0.672 32 1 \n", + "4 0.167 21 0 " + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Load the Diabetes Dataset\n", + "col_names = ['pregnant', 'glucose', 'bp', 'skin', 'insulin', 'bmi', 'pedigree', 'age', 'label']\n", + "# load dataset\n", + "diabetes_df = pd.read_csv(\"diabetes.csv\", header=None, names=col_names)\n", + "# Display the first 5 rows of the DataFrame\n", + "\n", + "#Display the first 5 rows of the dataframe\n", + "diabetes_df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "b1fec631", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Pregnancies int64\n", + "Glucose int64\n", + "BloodPressure int64\n", + "SkinThickness int64\n", + "Insulin int64\n", + "BMI float64\n", + "DiabetesPedigreeFunction float64\n", + "Age int64\n", + "Outcome int64\n", + "dtype: object\n", + "pregnant object\n", + "glucose object\n", + "bp object\n", + "skin object\n", + "insulin object\n", + "bmi object\n", + "pedigree object\n", + "age object\n", + "label object\n", + "dtype: object\n" + ] + } + ], + "source": [ + "print(df.dtypes)\n", + "print(diabetes_df.dtypes)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "ce25b8e4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "pregnant 0\n", + "glucose 0\n", + "bp 0\n", + "skin 0\n", + "insulin 0\n", + "bmi 0\n", + "pedigree 0\n", + "age 0\n", + "label 0\n", + "dtype: int64" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "diabetes_df.isnull().sum()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "aa3ec459", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "pregnant 18\n", + "glucose 137\n", + "bp 48\n", + "skin 52\n", + "insulin 187\n", + "bmi 249\n", + "pedigree 518\n", + "age 53\n", + "label 3\n", + "dtype: int64" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "diabetes_df.nunique()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "0a798870", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
pregnantglucosebpskininsulinbmipedigreeagelabel
count769769769769769769769769769
unique181374852187249518533
top11007000320.254220
freq135175722737413672500
\n", + "
" + ], + "text/plain": [ + " pregnant glucose bp skin insulin bmi pedigree age label\n", + "count 769 769 769 769 769 769 769 769 769\n", + "unique 18 137 48 52 187 249 518 53 3\n", + "top 1 100 70 0 0 32 0.254 22 0\n", + "freq 135 17 57 227 374 13 6 72 500" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "diabetes_df.describe()" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "18a220b9", + "metadata": {}, + "outputs": [], + "source": [ + "#split dataset in features and target variable\n", + "feature_cols = ['pregnant', 'insulin', 'bmi', 'age','glucose','bp','pedigree']\n", + "X = diabetes_df[feature_cols] # Features\n", + "y = diabetes_df.label # Target variable" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "8ecbaa3c", + "metadata": {}, + "outputs": [], + "source": [ + "# Split dataset into training set and test set\n", + "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=1) # 70% training and 30% test" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "d35f5819", + "metadata": {}, + "outputs": [], + "source": [ + "# Building Decision Tree Model\n", + "# Create Decision Tree classifer object\n", + "# Convert features to numeric\n", + "X_train_numeric = X_train.apply(pd.to_numeric, errors='coerce')\n", + "X_test_numeric = X_test.apply(pd.to_numeric, errors='coerce')\n", + "\n", + "clf = DecisionTreeClassifier()\n", + "clf = clf.fit(X_train_numeric, y_train)\n", + "y_pred = clf.predict(X_test_numeric)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "f897299c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Accuracy: 0.7186147186147186\n" + ] + } + ], + "source": [ + "# Evaluating Model\n", + "# Model Accuracy, how often is the classifier correct?\n", + "print(\"Accuracy:\", metrics.accuracy_score(y_test.astype(str), y_pred))" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "fe795596", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Accuracy: 0.7186147186147186\n" + ] + } + ], + "source": [ + "# Evaluating Model\n", + "# Model Accuracy, how often is the classifier correct?\n", + "print(\"Accuracy:\",metrics.accuracy_score(y_test, y_pred))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "689b134f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "id": "c9a921b8", + "metadata": {}, + "source": [ + "## Next steps\n", + "\n", + "- Try pruning the tree with `max_depth` or `min_samples_leaf` to reduce overfitting.\n", + "- Use cross-validation and grid search to find better hyperparameters.\n", + "- Compare with other classifiers such as `RandomForestClassifier` or `LogisticRegression`." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/exercisefiles/dotnet/Dockerfile b/exercisefiles/dotnet/Dockerfile new file mode 100644 index 00000000..799f2cb5 --- /dev/null +++ b/exercisefiles/dotnet/Dockerfile @@ -0,0 +1,20 @@ +# Use the official .NET SDK image as the base image +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +# Set the working directory in the container +WORKDIR /app +# Copy the project file(s) to the container +COPY *.csproj ./ +# Copy the remaining source code to the container +COPY . ./ +# Build the application +RUN dotnet build -c Release +# Publish the application +RUN dotnet publish -c Release --no-build -o out +# Use the official .NET runtime image as the base image for the final stage +FROM mcr.microsoft.com/dotnet/runtime:8.0 AS runtime +# Set the working directory in the container +WORKDIR /app +# Copy the published output from the build stage to the final stage +COPY --from=build /app/out ./ +# Set the entry point for the container +ENTRYPOINT ["dotnet", "MinimalAPI.dll"] \ No newline at end of file diff --git a/exercisefiles/dotnet/MinimalAPI.Tests/IntegrationTests.cs b/exercisefiles/dotnet/MinimalAPI.Tests/IntegrationTests.cs index 6e575fb3..ce6cacad 100644 --- a/exercisefiles/dotnet/MinimalAPI.Tests/IntegrationTests.cs +++ b/exercisefiles/dotnet/MinimalAPI.Tests/IntegrationTests.cs @@ -1,25 +1,98 @@ -namespace IntegrationTests; +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Testing; +using Xunit; -public class IntegrationTests : IClassFixture> +public class EndpointTests : IClassFixture> { + private readonly WebApplicationFactory _factory; private readonly HttpClient _client; - public IntegrationTests(TestWebApplicationFactory factory) + public EndpointTests(WebApplicationFactory factory) { - _client = factory.CreateClient(); + _factory = factory; + _client = _factory.CreateClient(); } [Fact] - public async Task Get_ReturnsHelloWorld() + public async Task Get_HelloWorld_ReturnsHelloWorld() { - // Arrange - - // Act var response = await _client.GetAsync("/"); + response.EnsureSuccessStatusCode(); + var stringResponse = await response.Content.ReadAsStringAsync(); + Assert.Equal("Hello World!", stringResponse); + } + + [Fact] + public async Task Get_ValidatePhoneNumber_ReturnsInvalid() + { + var response = await _client.GetAsync("/validatephonenumber?phonenumber=123456789"); + response.EnsureSuccessStatusCode(); + var stringResponse = await response.Content.ReadAsStringAsync(); + Assert.Equal("False", stringResponse); + } + + [Fact] + public async Task Get_ValidateSpanishDni_ReturnsValid() + { + var response = await _client.GetAsync("/validatespanishdni?dni=12345678Z"); + response.EnsureSuccessStatusCode(); + var stringResponse = await response.Content.ReadAsStringAsync(); + Assert.Equal("valid", stringResponse); + } + + [Fact] + public async Task Get_Color_ReturnsHexCode() + { + var response = await _client.GetAsync("/color?color=red"); + response.EnsureSuccessStatusCode(); + var stringResponse = await response.Content.ReadAsStringAsync(); + Assert.Equal("#FF0000", stringResponse); // assuming red color returns #FF0000 + } + + [Fact] + public async Task Get_TellMeAJoke_ReturnsJoke() + { + var response = await _client.GetAsync("/tellmeajoke"); + response.EnsureSuccessStatusCode(); + var stringResponse = await response.Content.ReadAsStringAsync(); + Assert.NotNull(stringResponse); // assuming the joke API always returns a joke + } + + [Fact] + public async Task Get_ParseUrl_ReturnsHost() + { + var response = await _client.GetAsync("/parseurl?someurl=https://www.example.com"); + response.EnsureSuccessStatusCode(); + var stringResponse = await response.Content.ReadAsStringAsync(); + Assert.Equal("www.example.com", stringResponse); + } + + [Fact] + public async Task Get_ListFiles_ReturnsFiles() + { + var response = await _client.GetAsync("/listfiles"); + response.EnsureSuccessStatusCode(); + var stringResponse = await response.Content.ReadAsStringAsync(); + Assert.NotNull(stringResponse); // assuming the API always returns a list of files + } + + [Fact] + public async Task Get_CalculateMemoryConsumption_ReturnsMemoryUsage() + { + var response = await _client.GetAsync("/calculatememoryconsumption"); + response.EnsureSuccessStatusCode(); + var stringResponse = await response.Content.ReadAsStringAsync(); + Assert.NotNull(stringResponse); // assuming the API always returns a memory usage + } - // Assert + [Fact] + public async Task Get_RandomEuropeanCountry_ReturnsCountry() + { + var response = await _client.GetAsync("/randomeuropeancountry"); response.EnsureSuccessStatusCode(); - var content = await response.Content.ReadAsStringAsync(); - Assert.Equal("Hello World!", content); + var stringResponse = await response.Content.ReadAsStringAsync(); + Assert.NotNull(stringResponse); // assuming the API always returns a country } -} +} \ No newline at end of file diff --git a/exercisefiles/dotnet/MinimalAPI/NuGet.Config b/exercisefiles/dotnet/MinimalAPI/NuGet.Config new file mode 100644 index 00000000..95e879ef --- /dev/null +++ b/exercisefiles/dotnet/MinimalAPI/NuGet.Config @@ -0,0 +1,6 @@ + + + + + + diff --git a/exercisefiles/dotnet/MinimalAPI/Program.cs b/exercisefiles/dotnet/MinimalAPI/Program.cs index d07de449..636ee0b1 100644 --- a/exercisefiles/dotnet/MinimalAPI/Program.cs +++ b/exercisefiles/dotnet/MinimalAPI/Program.cs @@ -1,3 +1,13 @@ +// Program.cs - Minimal API example +// +// This file configures a small ASP.NET Core Minimal API used by the +// integration tests in `MinimalAPI.Tests`. It registers a few simple +// endpoints (GET) demonstrating common operations: string response, +// date calculations, phone validation, JSON file access, external HTTP +// call, URL parsing, file listing, memory inspection, and a random +// country selection. +// +// Keep the app simple and HTTP-only to make test host access straightforward. var builder = WebApplication.CreateBuilder(args); // Add services to the container. @@ -10,16 +20,178 @@ // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { - app.UseSwagger(); - app.UseSwaggerUI(); + app.UseSwagger(); + app.UseSwaggerUI(); } -app.UseHttpsRedirection(); +// Note: Disabled HTTPS redirection to keep integration tests simple (tests use HTTP). +// If you want HTTPS in production, enable this in production environments. // ADD NEW ENDPOINTS HERE +// Hello World endpoint +// GET / +// Returns a simple plain-text "Hello World!" response. Used by tests +// to confirm the application and test host are wired correctly. +app.MapGet("/", () => "Hello World!"); + +// DaysBetweenDates endpoint +// GET /DaysBetweenDates?date1=2020-01-01&date2=2020-01-10 +// Returns the integer count of days between `date1` and `date2` as string. +app.MapGet("/DaysBetweenDates", (DateTime date1, DateTime date2) => + (date2 - date1).Days.ToString()); + +// ValidatePhoneNumber endpoint +// GET /validatephonenumber?phonenumber=+123456789 +// Returns "True" or "False" (string) depending on whether the phone +// number matches a simple pattern: a leading + and 9 digits. +app.MapGet("/validatephonenumber", (string phonenumber) => + Regex.IsMatch(phonenumber, @"^(\+[0-9]{9})$").ToString()); + +// ValidateSpanishDNI endpoint +// GET /validatespanishdni?dni=12345678Z +// Returns the string "valid" or "invalid" after checking the last +// letter of a Spanish DNI against the computed letter table. +app.MapGet("/validatespanishdni", (string dni) => +{ + var valid = false; + if (dni.Length == 9 && int.TryParse(dni.Substring(0, 8), out int number)) + { + var letters = "TRWAGMYFPDXBNJZSQVHLCKE"; + var letter = letters[number % 23]; + valid = dni.EndsWith(letter.ToString()); + } + + return valid ? "valid" : "invalid"; +}); + +// Color lookup endpoint +// GET /color?color=Blue +// Reads `colors.json` (in the app folder), deserializes to the local +// `Color` model array and returns the HEX code for the first matching +// color name. This is a synchronous file-read for simplicity. +app.MapGet("/color", (string color) => +{ + var colors = JsonSerializer.Deserialize(File.ReadAllText("colors.json")); + return colors.First(c => c.Name == color).Code.HEX; +}); + +// TellMeAJoke endpoint +// GET /tellmeajoke +// Performs a simple outgoing HTTP request to an external joke API and +// returns the raw response body. +app.MapGet("/tellmeajoke", async () => +{ + var client = new HttpClient(); + var response = await client.GetAsync("https://official-joke-api.appspot.com/jokes/random"); + var joke = await response.Content.ReadAsStringAsync(); + return joke; +}); + +// ParseUrl endpoint +// GET /parseurl?someurl=https://example.com:8080/path?x=1#frag +// Demonstrates simple URL parsing using `System.Uri` and returns the +// host portion of the URL. Other parsed parts are captured but not +// returned here; modify as needed. +app.MapGet("/parseurl", (string someurl) => +{ + var uri = new Uri(someurl); + var host = uri.Host; + var protocol = uri.Scheme; + var port = uri.Port; + var path = uri.AbsolutePath; + var query = uri.Query; + var hash = uri.Fragment; + return host; +}); + +// ListFiles endpoint +// GET /listfiles +// Returns the list of files in the current working directory where the +// application is running. Useful for simple filesystem integration tests. +app.MapGet("/listfiles", () => +{ + var currentDirectory = Directory.GetCurrentDirectory(); + var files = Directory.GetFiles(currentDirectory); + return files; +}); + +// Calculate memory consumption endpoint +// CalculateMemoryConsumption endpoint +// GET /calculatememoryconsumption +// Returns the working set memory (in GB) for the current process, +// rounded to two decimal places. +app.MapGet("/calculatememoryconsumption", () => +{ + var process = System.Diagnostics.Process.GetCurrentProcess(); + var memoryUsage = process.WorkingSet64 / (1024.0 * 1024 * 1024); // Convert to GB + return Math.Round(memoryUsage, 2); +}); + +// Random European Country endpoint +// RandomEuropeanCountry endpoint +// GET /randomeuropeancountry +// Chooses a random entry from an in-memory list of European countries +// and returns a string like "Spain (ES)". +app.MapGet("/randomeuropeancountry", () => +{ + var europeanCountries = new Dictionary + { + { "Albania", "AL" }, + { "Andorra", "AD" }, + { "Austria", "AT" }, + { "Belarus", "BY" }, + { "Belgium", "BE" }, + { "Bosnia and Herzegovina", "BA" }, + { "Bulgaria", "BG" }, + { "Croatia", "HR" }, + { "Cyprus", "CY" }, + { "Czech Republic", "CZ" }, + { "Denmark", "DK" }, + { "Estonia", "EE" }, + { "Finland", "FI" }, + { "France", "FR" }, + { "Germany", "DE" }, + { "Greece", "GR" }, + { "Hungary", "HU" }, + { "Iceland", "IS" }, + { "Ireland", "IE" }, + { "Italy", "IT" }, + { "Kosovo", "XK" }, + { "Latvia", "LV" }, + { "Liechtenstein", "LI" }, + { "Lithuania", "LT" }, + { "Luxembourg", "LU" }, + { "Malta", "MT" }, + { "Moldova", "MD" }, + { "Monaco", "MC" }, + { "Montenegro", "ME" }, + { "Netherlands", "NL" }, + { "North Macedonia", "MK" }, + { "Norway", "NO" }, + { "Poland", "PL" }, + { "Portugal", "PT" }, + { "Romania", "RO" }, + { "Russia", "RU" }, + { "San Marino", "SM" }, + { "Serbia", "RS" }, + { "Slovakia", "SK" }, + { "Slovenia", "SI" }, + { "Spain", "ES" }, + { "Sweden", "SE" }, + { "Switzerland", "CH" }, + { "Ukraine", "UA" }, + { "United Kingdom", "GB" }, + { "Vatican City", "VA" } + }; + + var random = new Random(); + var index = random.Next(europeanCountries.Count); + var country = europeanCountries.ElementAt(index); + return $"{country.Key} ({country.Value})"; +}); + app.Run(); // Needed to be able to access this type from the MinimalAPI.Tests project. -public partial class Program -{ } +public partial class Program { } diff --git a/exercisefiles/dotnet/MinimalAPI/Usings.cs b/exercisefiles/dotnet/MinimalAPI/Usings.cs index 09b16c96..351f679f 100644 --- a/exercisefiles/dotnet/MinimalAPI/Usings.cs +++ b/exercisefiles/dotnet/MinimalAPI/Usings.cs @@ -1,4 +1,8 @@ global using System.Diagnostics; global using System.Text.RegularExpressions; global using System.Text.Json; -global using System.Text.Json.Serialization; \ No newline at end of file +global using System.Text.Json.Serialization; +global using System.Linq; +global using System.IO; +global using System.Collections.Generic; +global using System.Net.Http; \ No newline at end of file diff --git a/exercisefiles/dotnet/NuGet.Config b/exercisefiles/dotnet/NuGet.Config new file mode 100644 index 00000000..95e879ef --- /dev/null +++ b/exercisefiles/dotnet/NuGet.Config @@ -0,0 +1,6 @@ + + + + + + diff --git a/exercisefiles/node/.dockerignore b/exercisefiles/node/.dockerignore new file mode 100644 index 00000000..ecce11ab --- /dev/null +++ b/exercisefiles/node/.dockerignore @@ -0,0 +1,10 @@ +node_modules +npm-debug.log +Dockerfile +dockerfile +.git +.gitignore +.env +dist +coverage +.vscode diff --git a/exercisefiles/node/dockerfile b/exercisefiles/node/dockerfile index 7caa765a..124adbaf 100644 --- a/exercisefiles/node/dockerfile +++ b/exercisefiles/node/dockerfile @@ -1,17 +1,27 @@ -# Create a dockerfile with node image +# Use an official Node runtime as a parent image +FROM node:18-alpine -# Create a directory to hold the application code inside the image, this will be the working directory for your application +# Create app directory +WORKDIR /usr/src/app -# Set the working directory to /usr/src/app +# Install app dependencies +# We copy package.json and package-lock.json first to leverage Docker cache +COPY package*.json ./ -# Copy package.json and package-lock.json to the working directory +# Install dependencies (use npm ci when package-lock.json is present for reproducible builds) +RUN if [ -f package-lock.json ]; then npm ci --only=production; else npm install --only=production; fi -# Install npm +# Bundle app source +COPY . . -# Copy the current directory contents into the container at /usr/src/app +# Expose the port the app runs on +EXPOSE 3000 -# Make port 3000 available to the world outside this container +# Default command +CMD ["node", "nodeserver.js"] -# Run Nodeserver.js when the container launches - -# Write a docker comand to build the image and tag it as mynodeapp +# Build command (run from the directory containing this Dockerfile): +# PowerShell example: +# docker build -t mynodeapp -f .\dockerfile . +# Run example (maps container port 3000 to host port 3000): +# docker run --rm -p 3000:3000 mynodeapp diff --git a/exercisefiles/node/nodeserver.js b/exercisefiles/node/nodeserver.js index f68e5b8f..6ec595a6 100644 --- a/exercisefiles/node/nodeserver.js +++ b/exercisefiles/node/nodeserver.js @@ -1,7 +1,346 @@ -// write a nodejs server that will expose a method call "get" that will return the value of the key passed in the query string -// example: http://localhost:3000/get?key=hello -// if the key is not passed, return "key not passed" -// if the key is passed, return "hello" + key -// if the url has other methods, return "method not supported" -// when server is listening, log "server is listening on port 3000" +/** + * nodeserver.js + * + * Small Node.js HTTP server with multiple utility endpoints used for exercises and demos. + * This file was reformatted to fix spacing and bracket issues and to make behavior consistent. + * + * Available endpoints (all use query string parameters unless stated): + * + * 1. GET /get?key=VALUE + * - Returns 'hello VALUE' when `key` is provided + * - Returns 'key not passed' when `key` is missing + * + * 2. GET /DaysBetweenDates?date1=YYYY-MM-DD&date2=YYYY-MM-DD + * - Calculates absolute days between `date1` and `date2` using Date.parse + * - Returns 'N days' or 400 on missing/invalid dates + * + * 3. GET /Validatephonenumber?phoneNumber=VALUE + * - Validates a Spanish mobile phone format (e.g. 34666666666 or +34666666666) + * - Returns 'valid' or 'invalid' + * + * 4. GET /ValidateSpanishDNI?dni=VALUE + * - Validates a Spanish DNI by checking the trailing letter against the numeric part + * - Returns 'valid' or 'invalid' + * + * 5. GET /ReturnColorCode?color=COLORNAME + * - Reads `colors.json` from the working directory and returns the matching color hex code + * - Returns 'not found' when color is missing or not present + * + * 6. GET /TellMeAJoke + * - Fetches a random joke from `official-joke-api` and returns it + * + * 7. GET /MoviesByDirector?director=NAME + * - Searches OMDB (requires a working API key in place of XXXXXXX) and returns movie titles + * + * 8. GET /ParseUrl?someurl=URL + * - Parses `someurl` and returns the host portion or 400 if the URL is invalid + * + * 9. GET /GetFullTextFile + * - Reads `sample.txt` and returns lines that contain the substring 'Fusce' + * + * 10. GET /GetLineByLinefromtTextFile + * - Streams `sample.txt` line-by-line and returns lines that contain 'Fusce' + * + * 11. GET /CalculateMemoryConsumption + * - Returns the process memory usage (heapUsed) in MB + * + * 12. GET /RandomEuropeanCountry + * - Returns a random country name and ISO code from a predefined list + * + * Default: returns 404 'method not supported'. + * + * Run: + * node nodeserver.js + * + * Notes / Assumptions: + * - The server expects `colors.json` and `sample.txt` to be present in the working folder for related endpoints. + * - External endpoints (joke API, OMDB) require network access and in the case of OMDB a valid API key. + */ + +const http = require('http'); +const url = require('url'); +const fs = require('fs'); + +const server = http.createServer((req, res) => { + const parsedUrl = url.parse(req.url, true); + const pathname = parsedUrl.pathname || req.url; + + // Simple /get endpoint + if (pathname === '/get') { + const key = parsedUrl.query && parsedUrl.query.key; + res.writeHead(200, { 'Content-Type': 'text/plain' }); + if (!key) { + res.end('key not passed'); + } else { + res.end('hello ' + key); + } + return; + } + + // /DaysBetweenDates?date1=...&date2=... + if (pathname === '/DaysBetweenDates') { + const query = parsedUrl.query || {}; + const date1 = query.date1; + const date2 = query.date2; + + if (!date1 || !date2) { + res.writeHead(400, { 'Content-Type': 'text/plain' }); + res.end('missing date1 or date2 query parameter'); + return; + } + + const date1_ms = Date.parse(date1); + const date2_ms = Date.parse(date2); + + if (isNaN(date1_ms) || isNaN(date2_ms)) { + res.writeHead(400, { 'Content-Type': 'text/plain' }); + res.end('invalid date format. Use a format recognized by Date.parse, e.g. 2020-01-02'); + return; + } + + const difference_ms = Math.abs(date2_ms - date1_ms); + const days = Math.round(difference_ms / 86400000); + + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end(days + ' days'); + return; + } + + // /Validatephonenumber?phoneNumber=... + if (pathname && pathname.startsWith('/Validatephonenumber')) { + const queryData = parsedUrl.query || {}; + const phoneNumber = queryData.phoneNumber || ''; + const regex = /^(\+34|0034|34)?[ -]*(6|7)[ -]*([0-9][ -]*){8}$/; + res.writeHead(200, { 'Content-Type': 'text/plain' }); + if (regex.test(phoneNumber)) { + res.end('valid'); + } else { + res.end('invalid'); + } + return; + } + + // /ValidateSpanishDNI?dni=... + if (pathname && pathname.startsWith('/ValidateSpanishDNI')) { + const queryData = parsedUrl.query || {}; + const dni = (queryData.dni || '').toString().toUpperCase(); + if (!dni || dni.length < 2) { + res.writeHead(400, { 'Content-Type': 'text/plain' }); + res.end('invalid'); + return; + } + + const dniLetter = dni.charAt(dni.length - 1); + const dniNumber = parseInt(dni.substring(0, dni.length - 1), 10); + if (isNaN(dniNumber)) { + res.writeHead(400, { 'Content-Type': 'text/plain' }); + res.end('invalid'); + return; + } + + const dniLetterCalc = 'TRWAGMYFPDXBNJZSQVHLCKE'.charAt(dniNumber % 23); + res.writeHead(200, { 'Content-Type': 'text/plain' }); + if (dniLetter === dniLetterCalc) { + res.end('valid'); + } else { + res.end('invalid'); + } + return; + } + + // /ReturnColorCode?color=... + if (pathname && pathname.startsWith('/ReturnColorCode')) { + try { + const colors = fs.readFileSync('colors.json', 'utf-8'); + const colorsObj = JSON.parse(colors); + const queryData = parsedUrl.query || {}; + const color = queryData.color; + let colorFound = 'not found'; + for (let i = 0; i < colorsObj.length; i++) { + if (colorsObj[i].color === color) { + colorFound = (colorsObj[i].code && colorsObj[i].code.hex) || colorFound; + break; + } + } + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end(colorFound); + } catch (err) { + res.writeHead(500, { 'Content-Type': 'text/plain' }); + res.end('error reading colors.json'); + } + return; + } + + // /TellMeAJoke + if (pathname && pathname.startsWith('/TellMeAJoke')) { + const axios = require('axios'); + axios.get('https://official-joke-api.appspot.com/random_joke') + .then(function (response) { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end(response.data.setup + ' ' + response.data.punchline); + }) + .catch(function (error) { + console.error(error); + res.writeHead(500, { 'Content-Type': 'text/plain' }); + res.end('error fetching joke'); + }); + return; + } + + // /MoviesByDirector?director=... + if (pathname && pathname.startsWith('/MoviesByDirector')) { + const queryData = parsedUrl.query || {}; + const director = queryData.director || ''; + const axios = require('axios'); + axios.get('http://www.omdbapi.com/?apikey=XXXXXXX&s=' + encodeURIComponent(director)) + .then(function (response) { + const search = response.data && response.data.Search; + let movies = ''; + if (Array.isArray(search)) { + for (let i = 0; i < search.length; i++) { + movies += search[i].Title + ', '; + } + } + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end(movies); + }) + .catch(function (error) { + console.error(error); + res.writeHead(500, { 'Content-Type': 'text/plain' }); + res.end('error fetching movies'); + }); + return; + } + + // /ParseUrl?someurl=... + if (pathname && pathname.startsWith('/ParseUrl')) { + const queryData = parsedUrl.query || {}; + const someUrl = queryData.someurl; + try { + const urlObj = new URL(someUrl); + const host = urlObj.host; + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end('host: ' + host); + } catch (err) { + res.writeHead(400, { 'Content-Type': 'text/plain' }); + res.end('invalid url'); + } + return; + } + + // /GetFullTextFile + if (pathname && pathname.startsWith('/GetFullTextFile')) { + try { + const text = fs.readFileSync('sample.txt', 'utf-8'); + const lines = text.split(/\r?\n/); + let linesFound = []; + for (let i = 0; i < lines.length; i++) { + if (lines[i].includes('Fusce')) { + linesFound.push(lines[i]); + } + } + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end(linesFound.join(', ')); + } catch (err) { + res.writeHead(500, { 'Content-Type': 'text/plain' }); + res.end('error reading file'); + } + return; + } + + // /GetLineByLinefromtTextFile + if (pathname && pathname.startsWith('/GetLineByLinefromtTextFile')) { + const readline = require('readline'); + const rs = fs.createReadStream('sample.txt'); + const lineReader = readline.createInterface({ input: rs }); + const lines = []; + lineReader.on('line', function (line) { + if (line.includes('Fusce')) { + lines.push(line); + } + }); + lineReader.on('close', function () { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end(lines.join(', ')); + }); + return; + } + + // /CalculateMemoryConsumption + if (pathname && pathname.startsWith('/CalculateMemoryConsumption')) { + const memory = process.memoryUsage().heapUsed / 1024 / 1024; // MB + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end(memory.toFixed(2) + ' MB'); + return; + } + + // /RandomEuropeanCountry + if (pathname && pathname.startsWith('/RandomEuropeanCountry')) { + const countries = [ + { country: 'Italy', iso: 'IT' }, + { country: 'France', iso: 'FR' }, + { country: 'Spain', iso: 'ES' }, + { country: 'Germany', iso: 'DE' }, + { country: 'United Kingdom', iso: 'GB' }, + { country: 'Greece', iso: 'GR' }, + { country: 'Portugal', iso: 'PT' }, + { country: 'Romania', iso: 'RO' }, + { country: 'Bulgaria', iso: 'BG' }, + { country: 'Croatia', iso: 'HR' }, + { country: 'Czech Republic', iso: 'CZ' }, + { country: 'Denmark', iso: 'DK' }, + { country: 'Estonia', iso: 'EE' }, + { country: 'Finland', iso: 'FI' }, + { country: 'Hungary', iso: 'HU' }, + { country: 'Ireland', iso: 'IE' }, + { country: 'Latvia', iso: 'LV' }, + { country: 'Lithuania', iso: 'LT' }, + { country: 'Luxembourg', iso: 'LU' }, + { country: 'Malta', iso: 'MT' }, + { country: 'Netherlands', iso: 'NL' }, + { country: 'Poland', iso: 'PL' }, + { country: 'Slovakia', iso: 'SK' }, + { country: 'Slovenia', iso: 'SI' }, + { country: 'Sweden', iso: 'SE' }, + { country: 'Belgium', iso: 'BE' }, + { country: 'Austria', iso: 'AT' }, + { country: 'Switzerland', iso: 'CH' }, + { country: 'Cyprus', iso: 'CY' }, + { country: 'Iceland', iso: 'IS' }, + { country: 'Norway', iso: 'NO' }, + { country: 'Albania', iso: 'AL' }, + { country: 'Andorra', iso: 'AD' }, + { country: 'Armenia', iso: 'AM' }, + { country: 'Azerbaijan', iso: 'AZ' }, + { country: 'Belarus', iso: 'BY' }, + { country: 'Bosnia and Herzegovina', iso: 'BA' }, + { country: 'Georgia', iso: 'GE' }, + { country: 'Kazakhstan', iso: 'KZ' }, + { country: 'Kosovo', iso: 'XK' }, + { country: 'Liechtenstein', iso: 'LI' }, + { country: 'Macedonia', iso: 'MK' }, + { country: 'Moldova', iso: 'MD' }, + { country: 'Monaco', iso: 'MC' }, + { country: 'Montenegro', iso: 'ME' }, + { country: 'Russia', iso: 'RU' }, + { country: 'San Marino', iso: 'SM' }, + { country: 'Serbia', iso: 'RS' }, + { country: 'Turkey', iso: 'TR' }, + { country: 'Ukraine', iso: 'UA' }, + { country: 'Vatican City', iso: 'VA' } + ]; + const randomCountry = countries[Math.floor(Math.random() * countries.length)]; + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end(randomCountry.country + ' ' + randomCountry.iso); + return; + } + + // default + res.writeHead(404, { 'Content-Type': 'text/plain' }); + res.end('method not supported'); +}); + +server.listen(3000, () => { + console.log('server is listening on port 3000'); +}); diff --git a/exercisefiles/node/test.js b/exercisefiles/node/test.js index 97fc8091..303f0319 100644 --- a/exercisefiles/node/test.js +++ b/exercisefiles/node/test.js @@ -1,40 +1,93 @@ -//write npm command line to install mocha -//npm install --global mocha - -//command to run this test file -//mocha test.js +// To run tests locally: +// 1. From `exercisefiles/node` run: `npm install --save-dev mocha` +// 2. Run tests with: `npx mocha test.js` const assert = require('assert'); const http = require('http'); -const server = require('./nodeserver'); +// Require the server so it starts before tests run. +require('./nodeserver'); + +describe('Node Server', function () { + // allow a small timeout for server startup and external calls + this.timeout(5000); + + before((done) => { + // small delay to allow server.listen callback to complete + setTimeout(done, 200); + }); -describe('Node Server', () => { - it('should return "key not passed" if key is not passed', (done) => { - http - .get('http://localhost:3000/get' , (res) => { + it('GET /get without key -> "key not passed"', (done) => { + http.get('http://localhost:3000/get', (res) => { let data = ''; - res.on('data', (chunk) => { - data += chunk; - }); + res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { - assert.equal(data, 'key not passed'); + assert.strictEqual(data, 'key not passed'); done(); }); - }); + }).on('error', done); }); - //add test to check get when key is equal to world - - //add test to check validatephoneNumber - - //write test to validate validateSpanishDNI - - - //write test for returnColorCode red should return code #FF0000 - + it('GET /get?key=world -> "hello world"', (done) => { + http.get('http://localhost:3000/get?key=world', (res) => { + let data = ''; + res.on('data', (chunk) => { data += chunk; }); + res.on('end', () => { + assert.strictEqual(data, 'hello world'); + done(); + }); + }).on('error', done); + }); - //write test for daysBetweenDates + it('GET /Validatephonenumber -> valid', (done) => { + http.get('http://localhost:3000/Validatephonenumber?phoneNumber=34666666666', (res) => { + let data = ''; + res.on('data', (chunk) => { data += chunk; }); + res.on('end', () => { + assert.strictEqual(data, 'valid'); + done(); + }); + }).on('error', done); + }); + it('GET /ValidateSpanishDNI valid and invalid', (done) => { + // valid DNI + http.get('http://localhost:3000/ValidateSpanishDNI?dni=86471508H', (res) => { + let d1 = ''; + res.on('data', (c) => { d1 += c; }); + res.on('end', () => { + assert.strictEqual(d1, 'valid'); + // invalid DNI + http.get('http://localhost:3000/ValidateSpanishDNI?dni=12345678A', (res2) => { + let d2 = ''; + res2.on('data', (c) => { d2 += c; }); + res2.on('end', () => { + assert.strictEqual(d2, 'invalid'); + done(); + }); + }).on('error', done); + }); + }).on('error', done); + }); + it('GET /ReturnColorCode?color=red -> #FF0000', (done) => { + http.get('http://localhost:3000/ReturnColorCode?color=red', (res) => { + let data = ''; + res.on('data', (chunk) => { data += chunk; }); + res.on('end', () => { + assert.strictEqual(data, '#FF0000'); + done(); + }); + }).on('error', done); + }); + it('GET /DaysBetweenDates returns days', (done) => { + http.get('http://localhost:3000/DaysBetweenDates?date1=2020-01-01&date2=2020-01-02', (res) => { + let data = ''; + res.on('data', (chunk) => { data += chunk; }); + res.on('end', () => { + assert.strictEqual(data, '1 days'); + done(); + }); + }).on('error', done); + }); }); diff --git a/exercisefiles/quarkus/copilot-demo/DOCKER.md b/exercisefiles/quarkus/copilot-demo/DOCKER.md index f0bd0d91..14049da3 100644 --- a/exercisefiles/quarkus/copilot-demo/DOCKER.md +++ b/exercisefiles/quarkus/copilot-demo/DOCKER.md @@ -3,13 +3,57 @@ ## Build the native executable -## Build the docker image using the Dockerfile.native-micro +This document explains how to build and run the Quarkus application inside Docker. +Options: +- Native image (uses `Dockerfile.native-micro`) - requires GraalVM/native build on host or CI +- JVM image (uses `Dockerfile.jvm`) - builds and runs the fat jar inside a JVM container (simpler) -## Run the docker image +1) Build and run (JVM, recommended for local dev) +PowerShell: + +```powershell +# Build the app (skip tests to speed up locally if desired) +mvn package -DskipTests + +# Build the docker image (uses Dockerfile.jvm) +docker build -f Dockerfile.jvm -t copilot-demo-jvm . + +# Run the container (maps host 8080 to container 8080) +docker run --rm -p 8080:8080 --name copilot-demo-jvm copilot-demo-jvm +``` + +Then test: + +```powershell +curl http://localhost:8080/hello?key=world +curl http://localhost:8080/colors/red +curl http://localhost:8080/joke +``` + +2) Build and run (native) + +On CI or a machine with GraalVM/native build configured: + +```powershell +# Produce native executable +mvn -Pnative -DskipTests package + +# Build docker image using the provided Dockerfile.native-micro +docker build -f Dockerfile.native-micro -t copilot-demo-native . + +# Run native container +docker run --rm -p 8080:8080 --name copilot-demo-native copilot-demo-native +``` + +Notes +- If running in a container, use `-Dquarkus.http.host=0.0.0.0` or the Dockerfiles will set it for you. +- For CI, prefer the native build step only when you need native performance/size advantages. + +Troubleshooting +- If you hit permission issues copying the jar in `Dockerfile.jvm`, ensure `target/*.jar` exists and is the Quarkus runnable jar (Quarkus builds `-runner.jar` or the fat jar depending on packaging). Adjust copy pattern if needed. -## Test the application diff --git a/exercisefiles/quarkus/copilot-demo/Dockerfile.jvm b/exercisefiles/quarkus/copilot-demo/Dockerfile.jvm new file mode 100644 index 00000000..bbe0fac8 --- /dev/null +++ b/exercisefiles/quarkus/copilot-demo/Dockerfile.jvm @@ -0,0 +1,10 @@ +# JVM Dockerfile - simple image to run the Quarkus fat jar +# Build the application first with: mvn package -DskipTests +# Then build image: docker build -f Dockerfile.jvm -t copilot-demo-jvm . + +FROM eclipse-temurin:17-jre-jammy +WORKDIR /work +# Copy the jar produced by 'mvn package'. This pattern picks the runnable jar produced by Quarkus. +COPY target/*.jar /work/application.jar +EXPOSE 8080 +ENTRYPOINT ["java","-jar","/work/application.jar","-Dquarkus.http.host=0.0.0.0"] diff --git a/exercisefiles/quarkus/copilot-demo/src/main/java/com/microsoft/hackathon/quarkus/DemoResource.java b/exercisefiles/quarkus/copilot-demo/src/main/java/com/microsoft/hackathon/quarkus/DemoResource.java index 32799871..c48675a0 100644 --- a/exercisefiles/quarkus/copilot-demo/src/main/java/com/microsoft/hackathon/quarkus/DemoResource.java +++ b/exercisefiles/quarkus/copilot-demo/src/main/java/com/microsoft/hackathon/quarkus/DemoResource.java @@ -1,17 +1,281 @@ package com.microsoft.hackathon.quarkus; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.PathParam; +import java.io.IOException; +import java.io.InputStream; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.net.URI; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.stream.Stream; /* -* The Demo resource should be mapped to the root path. -* -* Create a GET operation to return the value of a key passed as query parameter in the request. -* -* If the key is not passed, return "key not passed". -* If the key is passed, return "hello ". -* -*/ + * The Demo resource should be mapped to the root path. + * + * Create a GET operation to return the value of a key passed as query parameter in the request. + * + * If the key is not passed, return "key not passed". + * If the key is passed, return "hello ". + */ +@Path("/") +public class DemoResource { + + private static final Map COLORS = new HashMap<>(); + + static { + // Load colors.json from classpath and populate COLORS map (name -> hex) + try (InputStream is = DemoResource.class.getResourceAsStream("/colors.json")) { + if (is != null) { + ObjectMapper mapper = new ObjectMapper(); + List> items = mapper.readValue(is, new TypeReference>>(){}); + for (Map item : items) { + Object nameObj = item.get("color"); + Object codeObj = item.get("code"); + if (codeObj instanceof Map) { + Object hex = ((Map) codeObj).get("hex"); + if (nameObj instanceof String && hex instanceof String) { + COLORS.put(((String) nameObj).toLowerCase(), (String) hex); + } + } + } + } + } catch (IOException e) { + // If parsing fails, leave map empty. In real app we would log this. + } + } + + @GET + @Path("/hello") + @Produces(MediaType.TEXT_PLAIN) + public String hello(@QueryParam("key") String key) { + if (key == null || key.isEmpty()) { + return "key not passed"; + } + return "hello " + key; + } + + /* + * Create a GET operation to return the color passed as path parameter in the request. + * + * If the color is not passed, return "color not passed". + * If the color is passed, return "your favorite color is ". + * + */ + @GET + @Path("/color/{color}") + @Produces(MediaType.TEXT_PLAIN) + public String color(@jakarta.ws.rs.PathParam("color") String color) { + if (color == null || color.isEmpty()) { + return "color not passed"; + } + return "your favorite color is " + color; + } + + /** + * GET /colors/{name} - return hex code for the requested color name based on resources/colors.json + * Returns 200 with the hex string (e.g. "#FF0000") when found, or 404 when not found. + */ + @GET + @Path("/colors/{name}") + @Produces(MediaType.TEXT_PLAIN) + public Response getColorHex(@PathParam("name") String name) { + if (name == null || name.isEmpty()) { + return Response.status(Response.Status.BAD_REQUEST).entity("color not passed").build(); + } + String hex = COLORS.get(name.toLowerCase()); + if (hex == null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + return Response.ok(hex).build(); + } + + /** + * GET /joke - call external API https://api.chucknorris.io/jokes/random and return the joke text. + * On upstream failure or parsing error return 502 Bad Gateway. + */ + @GET + @Path("/joke") + @Produces(MediaType.TEXT_PLAIN) + public Response getChuckNorrisJoke() { + try { + HttpClient client = HttpClient.newHttpClient(); + HttpRequest req = HttpRequest.newBuilder() + .uri(URI.create("https://api.chucknorris.io/jokes/random")) + .GET() + .header("Accept", "application/json") + .build(); + + HttpResponse resp = client.send(req, HttpResponse.BodyHandlers.ofString()); + if (resp.statusCode() != 200) { + return Response.status(Response.Status.BAD_GATEWAY).entity("upstream error").build(); + } + + ObjectMapper mapper = new ObjectMapper(); + Map map = mapper.readValue(resp.body(), new TypeReference>(){}); + Object value = map.get("value"); + if (value instanceof String) { + return Response.ok((String) value).build(); + } else { + return Response.status(Response.Status.BAD_GATEWAY).entity("invalid upstream response").build(); + } + } catch (Exception e) { + return Response.status(Response.Status.BAD_GATEWAY).entity("upstream error").build(); + } + } + + /** + * GET /parseurl?url= - parse the provided URL and return JSON with protocol, host, port, path and query parameters. + */ + @GET + @Path("/parseurl") + @Produces(MediaType.APPLICATION_JSON) + public Response parseUrl(@QueryParam("url") String urlStr) { + if (urlStr == null || urlStr.isEmpty()) { + return Response.status(Response.Status.BAD_REQUEST).entity(Map.of("error", "url not passed")).build(); + } + try { + // The test may URL-encode the whole URL when passing it as a query param. + // Decode before parsing so URI.getScheme()/getHost() work as expected. + String decoded = URLDecoder.decode(urlStr, StandardCharsets.UTF_8); + URI uri = URI.create(decoded); + String protocol = uri.getScheme(); + String host = uri.getHost(); + int port = uri.getPort(); + Integer portObj = port == -1 ? null : Integer.valueOf(port); + String path = uri.getPath(); + String query = uri.getQuery(); + + Map queryParams = new HashMap<>(); + if (query != null && !query.isEmpty()) { + String[] pairs = query.split("&"); + for (String pair : pairs) { + if (pair.isEmpty()) continue; + String[] kv = pair.split("=", 2); + String k = URLDecoder.decode(kv[0], StandardCharsets.UTF_8); + String v = kv.length > 1 ? URLDecoder.decode(kv[1], StandardCharsets.UTF_8) : ""; + queryParams.put(k, v); + } + } + + Map resp = new HashMap<>(); + resp.put("protocol", protocol); + resp.put("host", host); + resp.put("port", portObj); + resp.put("path", path); + resp.put("queryParams", queryParams); + return Response.ok(resp).build(); + } catch (Exception e) { + return Response.status(Response.Status.BAD_REQUEST).entity(Map.of("error", "invalid url")).build(); + } + } + + /** + * GET /countword?path=&word= - count occurrences of word in the file at path. + * Returns JSON: { "path": "...", "word": "...", "count": } + * Matching is case-insensitive and counts occurrences of the word as a separate word. + */ + @GET + @Path("/countword") + @Produces(MediaType.APPLICATION_JSON) + public Response countWord(@QueryParam("path") String pathStr, @QueryParam("word") String word) { + if (pathStr == null || pathStr.isEmpty() || word == null || word.isEmpty()) { + return Response.status(Response.Status.BAD_REQUEST).entity(Map.of("error", "path and word are required")).build(); + } + try { + // Handle Windows-style resource paths returned by Class.getResource().getPath() + String candidate = pathStr; + if (candidate.matches("^/[A-Za-z]:/.*")) { + // strip leading slash, e.g. "/C:/..." -> "C:/..." + candidate = candidate.substring(1); + } + java.nio.file.Path p = java.nio.file.Path.of(candidate); + if (!Files.exists(p) || !Files.isRegularFile(p)) { + return Response.status(Response.Status.BAD_REQUEST).entity(Map.of("error", "file not found")) .build(); + } + + String target = word.toLowerCase(); + long count = 0; + try (Stream lines = Files.lines(java.nio.file.Path.of(candidate))) { + count = lines.flatMap(l -> Stream.of(l.split("\\W+"))) + .filter(t -> !t.isEmpty()) + .map(String::toLowerCase) + .filter(t -> t.equals(target)) + .count(); + } + + Map resp = new HashMap<>(); + resp.put("path", pathStr); + resp.put("word", word); + resp.put("count", count); + return Response.ok(resp).build(); + } catch (Exception e) { + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(Map.of("error", "internal error")) .build(); + } + } + + /** + * GET /diffdates?date1=dd-MM-yyyy&date2=dd-MM-yyyy + * Returns the difference in days between date1 and date2. + */ + @GET + @Path("/diffdates") + @Produces(MediaType.TEXT_PLAIN) + public String diffDates(@QueryParam("date1") String date1, @QueryParam("date2") String date2) { + if (date1 == null || date1.isEmpty() || date2 == null || date2.isEmpty()) { + return "date not passed"; + } + + DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd-MM-yyyy"); + try { + LocalDate d1 = LocalDate.parse(date1, fmt); + LocalDate d2 = LocalDate.parse(date2, fmt); + long days = Math.abs(ChronoUnit.DAYS.between(d1, d2)); + return "difference in days: " + days; + } catch (DateTimeParseException e) { + return "invalid date format, expected dd-MM-yyyy"; + } + } + + // Validate the format of a spanish phone number (+34 prefix, then 9 digits, starting with 6, 7 or 9) + @GET + @Path("/validatephone") + @Produces(MediaType.TEXT_PLAIN) + public String validatePhone(@QueryParam("phone") String phone) { + if (phone == null || phone.isEmpty()) { + return "false"; + } + // +34 followed by a 9-digit number starting with 6,7,or 9 + boolean ok = phone.matches("^\\+34[679]\\d{8}$"); + return ok ? "true" : "false"; + } + + // Validate the format of a spanish DNI (8 digits and 1 uppercase letter) + @GET + @Path("/validatedni") + @Produces(MediaType.TEXT_PLAIN) + public String validateDni(@QueryParam("dni") String dni) { + if (dni == null || dni.isEmpty()) { + return "false"; + } + boolean ok = dni.matches("^\\d{8}[A-Z]$"); + return ok ? "true" : "false"; + } +} diff --git a/exercisefiles/quarkus/copilot-demo/src/test/java/com/microsoft/hackathon/quarkus/DemoResourceTest.java b/exercisefiles/quarkus/copilot-demo/src/test/java/com/microsoft/hackathon/quarkus/DemoResourceTest.java index 7de17016..27a304b3 100644 --- a/exercisefiles/quarkus/copilot-demo/src/test/java/com/microsoft/hackathon/quarkus/DemoResourceTest.java +++ b/exercisefiles/quarkus/copilot-demo/src/test/java/com/microsoft/hackathon/quarkus/DemoResourceTest.java @@ -18,4 +18,139 @@ public void testHelloEndpoint() { .body(is("hello world")); } + @Test + public void testDiffDatesEndpoint() { + // normal case: difference between 01-01-2020 and 04-01-2020 is 3 days + given() + .when().get("/diffdates?date1=01-01-2020&date2=04-01-2020") + .then() + .statusCode(200) + .body(is("difference in days: 3")); + + // missing param + given() + .when().get("/diffdates?date1=01-01-2020") + .then() + .statusCode(200) + .body(is("date not passed")); + + // invalid format + given() + .when().get("/diffdates?date1=2020-01-01&date2=2020-01-04") + .then() + .statusCode(200) + .body(is("invalid date format, expected dd-MM-yyyy")); + } + + @Test + public void testValidatePhoneEndpoint() { + // valid spanish phone + given() + .when().get("/validatephone?phone=+34699123456") + .then() + .statusCode(200) + .body(is("true")); + + // invalid spanish phone (wrong start digit) + given() + .when().get("/validatephone?phone=+34599123456") + .then() + .statusCode(200) + .body(is("false")); + + // missing phone returns false + given() + .when().get("/validatephone") + .then() + .statusCode(200) + .body(is("false")); + } + + @Test + public void testValidateDniEndpoint() { + // valid DNI + given() + .when().get("/validatedni?dni=12345678A") + .then() + .statusCode(200) + .body(is("true")); + + // invalid DNI (lowercase letter) + given() + .when().get("/validatedni?dni=12345678a") + .then() + .statusCode(200) + .body(is("false")); + + // missing DNI + given() + .when().get("/validatedni") + .then() + .statusCode(200) + .body(is("false")); + } + + @Test + public void testGetColorHexFound() { + // 'red' is present in resources/colors.json and should return its hex + given() + .when().get("/colors/red") + .then() + .statusCode(200) + .body(is("#FF0000")); + } + + @Test + public void testGetColorHexNotFound() { + // unknown color should return 404 + given() + .when().get("/colors/notacolor") + .then() + .statusCode(404); + } + + @Test + public void testParseUrlValid() { + String inner = java.net.URLEncoder.encode("https://example.com:8080/path/to/resource?foo=bar&baz=qux", java.nio.charset.StandardCharsets.UTF_8); + given() + .when().get("/parseurl?url=" + inner) + .then() + .statusCode(200) + .body("protocol", is("https")) + .body("host", is("example.com")) + .body("port", is(8080)) + .body("path", is("/path/to/resource")) + .body("queryParams.foo", is("bar")) + .body("queryParams.baz", is("qux")); + } + + @Test + public void testParseUrlMissing() { + given() + .when().get("/parseurl") + .then() + .statusCode(400); + } + + @Test + public void testCountWordFound() { + // resolve the test resource file path + String path = this.getClass().getResource("/sample_text.txt").getPath(); + given() + .when().get("/countword?path=" + path + "&word=hello") + .then() + .statusCode(200) + .body("path", is(path)) + .body("word", is("hello")) + .body("count", is(5)); + } + + @Test + public void testCountWordFileNotFound() { + given() + .when().get("/countword?path=/non/existent/file.txt&word=hello") + .then() + .statusCode(400); + } + } \ No newline at end of file diff --git a/exercisefiles/quarkus/copilot-demo/src/test/resources/sample_text.txt b/exercisefiles/quarkus/copilot-demo/src/test/resources/sample_text.txt new file mode 100644 index 00000000..b3072445 --- /dev/null +++ b/exercisefiles/quarkus/copilot-demo/src/test/resources/sample_text.txt @@ -0,0 +1,4 @@ +hello world +this is a test file +hello again Hello HELLO +world hello diff --git a/exercisefiles/springboot/copilot-demo/Dockerfile b/exercisefiles/springboot/copilot-demo/Dockerfile index aa49f540..251d28ea 100644 --- a/exercisefiles/springboot/copilot-demo/Dockerfile +++ b/exercisefiles/springboot/copilot-demo/Dockerfile @@ -1 +1,23 @@ -# Build a java application image based on openjdk 17 and run it on port 8080 +### Multi-stage Dockerfile +# Stage 1: Build the Spring Boot fat JAR using Maven +FROM maven:3.9.4-eclipse-temurin-17 as builder +WORKDIR /workspace + +# Copy maven config and source +COPY pom.xml mvnw* ./ +COPY .mvn .mvn +# Copy source tree +COPY src ./src + +# Build the package (skip tests to speed up build; remove -DskipTests if you want tests run) +RUN mvn -B -DskipTests package + +# Stage 2: Create a small runtime image +FROM eclipse-temurin:17-jre-alpine +WORKDIR /app +EXPOSE 8080 + +# Copy the packaged jar from the builder stage +COPY --from=builder /workspace/target/*.jar app.jar + +ENTRYPOINT ["java","-jar","/app/app.jar"] \ No newline at end of file diff --git a/exercisefiles/springboot/copilot-demo/pom.xml b/exercisefiles/springboot/copilot-demo/pom.xml index 789e9112..fdc29483 100644 --- a/exercisefiles/springboot/copilot-demo/pom.xml +++ b/exercisefiles/springboot/copilot-demo/pom.xml @@ -41,6 +41,15 @@ org.springframework.boot spring-boot-maven-plugin + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 17 + 17 + + diff --git a/exercisefiles/springboot/copilot-demo/src/main/java/com/microsoft/hackathon/copilotdemo/controller/DemoController.java b/exercisefiles/springboot/copilot-demo/src/main/java/com/microsoft/hackathon/copilotdemo/controller/DemoController.java index 60068a51..40b8a492 100644 --- a/exercisefiles/springboot/copilot-demo/src/main/java/com/microsoft/hackathon/copilotdemo/controller/DemoController.java +++ b/exercisefiles/springboot/copilot-demo/src/main/java/com/microsoft/hackathon/copilotdemo/controller/DemoController.java @@ -1,12 +1,240 @@ package com.microsoft.hackathon.copilotdemo.controller; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.client.RestClientException; +import org.springframework.http.ResponseEntity; +import org.springframework.core.io.ClassPathResource; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.concurrent.TimeUnit; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; /* * Create a GET operation to return the value of a key passed as query parameter. * If the key is not passed, return "key not passed". * If the key is passed, return "hello ". * +import java.nio.charset.StandardCharsets; */ +@RestController +public class DemoController { + + @GetMapping("/hello") + public String demo(@RequestParam(value = "key", defaultValue = "key not passed") String key) { + if (key.equals("key not passed")) { + return key; + } + return "hello " + key; + } + + @GetMapping("/diffdates") + public String diffdates(@RequestParam(name = "date1", required = false) String date1, + @RequestParam(name = "date2", required = false) String date2) throws ParseException { + if (date1 == null || date2 == null) { + return "date not passed"; + } + SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy"); + Date date1Obj = sdf.parse(date1); + Date date2Obj = sdf.parse(date2); + long diffInMillies = Math.abs(date2Obj.getTime() - date1Obj.getTime()); + long diff = TimeUnit.DAYS.convert(diffInMillies, TimeUnit.MILLISECONDS); + return "difference in days: " + diff; + } + @GetMapping("/validatephone") + public String validatePhone(@RequestParam String phone) { + if (phone.matches("\\+34[679]\\d{8}")) { + return "Valid phone number"; + } + + return "Invalid phone number"; + + } + // Validate the format of a spanish DNI (8 digits and 1 letter). The operation should receive a DNI as parameter and return true if the format is correct, false otherwise. + @GetMapping("/validatedni") + public boolean validatedni(@RequestParam(name = "dni", required = false) String dni) { + if (dni == null || dni.isEmpty()) { + return false; + } + String regex = "^\\d{8}[A-Z]$"; + return dni.matches(regex); + } + + // Based on existing colors.json file under resources, given the name of the color as path parameter, return the hexadecimal code. If the color is not found, return 404. + @GetMapping("/color/{color}") + public ResponseEntity getColorHex(@PathVariable String color) { + try { + ObjectMapper mapper = new ObjectMapper(); + ClassPathResource resource = new ClassPathResource("colors.json"); + InputStream inputStream = resource.getInputStream(); + JsonNode colorsArray = mapper.readTree(inputStream); + + for (JsonNode colorNode : colorsArray) { + String colorName = colorNode.get("color").asText(); + if (colorName.equalsIgnoreCase(color)) { + String hexCode = colorNode.get("code").get("hex").asText(); + return ResponseEntity.ok(hexCode); + } + } + + return ResponseEntity.notFound().build(); + } catch (IOException e) { + return ResponseEntity.status(500).body("Error reading colors file"); + } + } + + // Call the API https://api.chucknorris.io/jokes/random and return the joke + @GetMapping(value = "/joke", produces = "text/plain;charset=UTF-8") + public ResponseEntity getRandomJoke() { + RestTemplate restTemplate = new RestTemplate(); + String apiUrl = "https://api.chucknorris.io/jokes/random"; + org.springframework.http.MediaType utf8Plain = org.springframework.http.MediaType.parseMediaType("text/plain;charset=UTF-8"); + + try { + // Get the raw response from the API + String responseBody = restTemplate.getForObject(apiUrl, String.class); + + if (responseBody == null || responseBody.isEmpty()) { + return ResponseEntity.status(500) + .contentType(utf8Plain) + .body("Empty response from Chuck Norris API"); + } + + // Try to parse as JSON and extract the "value" field. If parsing fails or no value field, + // fall back to returning the raw response body as plain text with UTF-8 charset. + try { + ObjectMapper mapper = new ObjectMapper(); + JsonNode jsonNode = mapper.readTree(responseBody); + if (jsonNode.has("value")) { + String joke = jsonNode.get("value").asText(); + return ResponseEntity.ok() + .contentType(utf8Plain) + .body(joke); + } + } catch (Exception parseEx) { + // ignore parsing error and fall through to return raw body + } + + // If we reach here, parsing didn't yield a 'value' field or parsing failed. + String fallback = responseBody.trim(); + if (!fallback.isEmpty()) { + return ResponseEntity.ok() + .contentType(utf8Plain) + .body(fallback); + } + + // Empty response - treat as server error + return ResponseEntity.status(500) + .contentType(utf8Plain) + .body("Empty response from Chuck Norris API"); + } catch (RestClientException e) { + return ResponseEntity.status(503) + .contentType(utf8Plain) + .body("Service unavailable: Unable to connect to Chuck Norris API - " + e.getMessage()); + } + } + // Given a url as query parameter, parse it and return the protocol, host, port, path and query parameters. The response should be in Json format. + @GetMapping("/parseurl") + public ResponseEntity parseurl(@RequestParam(name = "url", required = false) String url) { + if (url == null || url.isEmpty()) { + return ResponseEntity.ok("url not passed"); + } + + org.springframework.http.MediaType jsonUtf8 = org.springframework.http.MediaType.parseMediaType("application/json;charset=UTF-8"); + + try { + URL urlObj = new URL(url); + String protocol = urlObj.getProtocol(); + String host = urlObj.getHost(); + int port = urlObj.getPort(); + String portStr = (port == -1) ? "" : String.valueOf(port); + String path = urlObj.getPath(); + String query = urlObj.getQuery(); + if (path == null) path = ""; + if (query == null) query = ""; + + String json = String.format( + "{\"protocol\":\"%s\",\"host\":\"%s\",\"port\":\"%s\",\"path\":\"%s\",\"query\":\"%s\"}", + escapeJson(protocol), escapeJson(host), escapeJson(portStr), escapeJson(path), escapeJson(query) + ); + + return ResponseEntity.ok() + .contentType(jsonUtf8) + .body(json); + } catch (MalformedURLException e) { + return ResponseEntity.badRequest().contentType(jsonUtf8).body("{\"error\":\"Malformed URL\"}"); + } + } + + // Minimal helper to escape JSON string values (handles quotes and backslashes) + private String escapeJson(String s) { + if (s == null) return ""; + return s.replace("\\", "\\\\").replace("\"", "\\\""); + } + + // Given the path of a file and a word as query parameters, count the number of occurrences + // of the provided word. The `path` and `word` should be query parameters. The response is JSON. + // Example: /count?path=/tmp/sample.txt&word=hello + @GetMapping("/count") + public ResponseEntity countWordOccurrences(@RequestParam(name = "path", required = false) String path, + @RequestParam(name = "word", required = false) String word) { + org.springframework.http.MediaType jsonUtf8 = org.springframework.http.MediaType.parseMediaType("application/json;charset=UTF-8"); + + if (path == null || path.isEmpty()) { + return ResponseEntity.badRequest().contentType(jsonUtf8).body("{\"error\":\"path not passed\"}"); + } + if (word == null || word.isEmpty()) { + return ResponseEntity.badRequest().contentType(jsonUtf8).body("{\"error\":\"word not passed\"}"); + } + + try { + String content = null; + + // Try filesystem first + java.nio.file.Path fsPath = java.nio.file.Paths.get(path); + if (java.nio.file.Files.exists(fsPath) && java.nio.file.Files.isRegularFile(fsPath)) { + byte[] bytes = java.nio.file.Files.readAllBytes(fsPath); + content = new String(bytes, java.nio.charset.StandardCharsets.UTF_8); + } else { + // Try classpath resource + ClassPathResource resource = new ClassPathResource(path); + if (resource.exists()) { + try (InputStream is = resource.getInputStream()) { + content = new String(is.readAllBytes(), java.nio.charset.StandardCharsets.UTF_8); + } + } + } + + if (content == null) { + String msg = String.format("{\"error\":\"file not found: %s\"}", escapeJson(path)); + return ResponseEntity.status(404).contentType(jsonUtf8).body(msg); + } + + // Count whole-word occurrences, case-insensitive + String pattern = "\\b" + java.util.regex.Pattern.quote(word) + "\\b"; + java.util.regex.Pattern p = java.util.regex.Pattern.compile(pattern, java.util.regex.Pattern.CASE_INSENSITIVE | java.util.regex.Pattern.UNICODE_CASE); + java.util.regex.Matcher m = p.matcher(content); + int count = 0; + while (m.find()) count++; + + String json = String.format("{\"path\":\"%s\",\"word\":\"%s\",\"count\":%d}", escapeJson(path), escapeJson(word), count); + return ResponseEntity.ok().contentType(jsonUtf8).body(json); + } catch (IOException e) { + String msg = String.format("{\"error\":\"io error: %s\"}", escapeJson(e.getMessage())); + return ResponseEntity.status(500).contentType(jsonUtf8).body(msg); + } catch (Exception e) { + String msg = String.format("{\"error\":\"unexpected error: %s\"}", escapeJson(e.getMessage())); + return ResponseEntity.status(500).contentType(jsonUtf8).body(msg); + } + } + +} \ No newline at end of file diff --git a/exercisefiles/springboot/copilot-demo/src/test/java/com/microsoft/hackathon/copilotdemo/CopilotDemoApplicationTests.java b/exercisefiles/springboot/copilot-demo/src/test/java/com/microsoft/hackathon/copilotdemo/CopilotDemoApplicationTests.java index 060d6157..ae196fe9 100644 --- a/exercisefiles/springboot/copilot-demo/src/test/java/com/microsoft/hackathon/copilotdemo/CopilotDemoApplicationTests.java +++ b/exercisefiles/springboot/copilot-demo/src/test/java/com/microsoft/hackathon/copilotdemo/CopilotDemoApplicationTests.java @@ -6,7 +6,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import org.springframework.test.web.servlet.result.MockMvcResultMatchers;; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; @SpringBootTest() @@ -23,4 +23,497 @@ void hello() throws Exception { .andExpect(MockMvcResultMatchers.content().string("hello world")); } + @Test + void helloNoKey() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/hello")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("key not passed")); + } + + // Tests for /diffdates endpoint + @Test + void diffDatesValidDates() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/diffdates") + .param("date1", "01-01-2023") + .param("date2", "02-01-2023")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("difference in days: 1")); + } + + @Test + void diffDatesLargerDifference() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/diffdates") + .param("date1", "01-01-2023") + .param("date2", "01-02-2023")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("difference in days: 31")); + } + + @Test + void diffDatesReversedOrder() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/diffdates") + .param("date1", "02-01-2023") + .param("date2", "01-01-2023")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("difference in days: 1")); + } + + @Test + void diffDatesSameDates() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/diffdates") + .param("date1", "01-01-2023") + .param("date2", "01-01-2023")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("difference in days: 0")); + } + + @Test + void diffDatesNoDate1() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/diffdates") + .param("date2", "01-01-2023")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("date not passed")); + } + + @Test + void diffDatesNoDate2() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/diffdates") + .param("date1", "01-01-2023")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("date not passed")); + } + + @Test + void diffDatesNoDates() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/diffdates")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("date not passed")); + } + + @Test + void diffDatesWrongDateFormat() throws Exception { + // This test shows that SimpleDateFormat is lenient and parses "2023-01-01" as year 2023, day 1, month 1 + // The large difference is expected because of the way SimpleDateFormat parses the date + mockMvc.perform(MockMvcRequestBuilders.get("/diffdates") + .param("date1", "2023-01-01") + .param("date2", "01-01-2023")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("difference in days: 736500")); + } + + @Test + void diffDatesLeapYear() throws Exception { + // Test leap year calculation + mockMvc.perform(MockMvcRequestBuilders.get("/diffdates") + .param("date1", "28-02-2024") + .param("date2", "01-03-2024")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("difference in days: 2")); + } + + @Test + void diffDatesYearDifference() throws Exception { + // Test year difference + mockMvc.perform(MockMvcRequestBuilders.get("/diffdates") + .param("date1", "01-01-2023") + .param("date2", "01-01-2024")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("difference in days: 365")); + } + + // Tests for Spanish phone number validation + // Valid Spanish phone format: +34 prefix + 9 digits starting with 6, 7, or 9 + + @Test + void validatePhoneValidNumberStartingWith6() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatephone") + .param("phone", "+34612345678")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("Valid phone number")); + } + + @Test + void validatePhoneValidNumberStartingWith7() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatephone") + .param("phone", "+34712345678")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("Valid phone number")); + } + + @Test + void validatePhoneValidNumberStartingWith9() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatephone") + .param("phone", "+34912345678")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("Valid phone number")); + } + + @Test + void validatePhoneInvalidMissingPrefix() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatephone") + .param("phone", "612345678")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("Invalid phone number")); + } + + @Test + void validatePhoneInvalidWrongPrefix() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatephone") + .param("phone", "+33612345678")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("Invalid phone number")); + } + + @Test + void validatePhoneInvalidStartingWith5() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatephone") + .param("phone", "+34512345678")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("Invalid phone number")); + } + + @Test + void validatePhoneInvalidStartingWith8() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatephone") + .param("phone", "+34812345678")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("Invalid phone number")); + } + + @Test + void validatePhoneInvalidTooShort() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatephone") + .param("phone", "+3461234567")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("Invalid phone number")); + } + + @Test + void validatePhoneInvalidTooLong() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatephone") + .param("phone", "+346123456789")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("Invalid phone number")); + } + + @Test + void validatePhoneInvalidWithLetters() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatephone") + .param("phone", "+34612345abc")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("Invalid phone number")); + } + + @Test + void validatePhoneInvalidWithSpaces() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatephone") + .param("phone", "+34 612 345 678")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("Invalid phone number")); + } + + @Test + void validatePhoneInvalidEmpty() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatephone") + .param("phone", "")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("Invalid phone number")); + } + + @Test + void validatePhoneInvalidStartingWith0() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatephone") + .param("phone", "+34012345678")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("Invalid phone number")); + } + + // Tests for Spanish DNI validation + // Valid Spanish DNI format: 8 digits followed by 1 uppercase letter + + @Test + void validateDniValidFormat() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatedni") + .param("dni", "12345678A")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("true")); + } + + @Test + void validateDniValidWithZ() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatedni") + .param("dni", "87654321Z")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("true")); + } + + @Test + void validateDniValidAllZeros() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatedni") + .param("dni", "00000000T")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("true")); + } + + @Test + void validateDniInvalidTooShort() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatedni") + .param("dni", "1234567A")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("false")); + } + + @Test + void validateDniInvalidTooLong() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatedni") + .param("dni", "123456789A")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("false")); + } + + @Test + void validateDniInvalidLowercaseLetter() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatedni") + .param("dni", "12345678a")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("false")); + } + + @Test + void validateDniInvalidMultipleLetters() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatedni") + .param("dni", "12345678AB")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("false")); + } + + @Test + void validateDniInvalidNoLetter() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatedni") + .param("dni", "12345678")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("false")); + } + + @Test + void validateDniInvalidOnlyNumbers() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatedni") + .param("dni", "123456789")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("false")); + } + + @Test + void validateDniInvalidLetterAtBeginning() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatedni") + .param("dni", "A12345678")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("false")); + } + + @Test + void validateDniInvalidLetterInMiddle() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatedni") + .param("dni", "1234A5678")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("false")); + } + + @Test + void validateDniInvalidWithSpaces() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatedni") + .param("dni", "12345678 A")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("false")); + } + + @Test + void validateDniInvalidWithHyphen() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatedni") + .param("dni", "12345678-A")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("false")); + } + + @Test + void validateDniInvalidEmpty() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatedni") + .param("dni", "")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("false")); + } + + @Test + void validateDniInvalidNull() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatedni")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("false")); + } + + @Test + void validateDniInvalidSpecialCharacters() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatedni") + .param("dni", "12345678@")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("false")); + } + + @Test + void validateDniInvalidMixedCharacters() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/validatedni") + .param("dni", "1234567aA")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("false")); + } + + // Tests for color endpoint + // Based on colors.json file, returns hex code for valid colors, 404 for invalid ones + + @Test + void getColorValidBlack() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/color/black")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("#000000")); + } + + @Test + void getColorValidWhite() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/color/white")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("#FFFFFF")); + } + + @Test + void getColorValidRed() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/color/red")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("#FF0000")); + } + + @Test + void getColorValidBlue() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/color/blue")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("#0000FF")); + } + + @Test + void getColorValidYellow() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/color/yellow")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("#FFFF00")); + } + + @Test + void getColorValidGreen() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/color/green")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("#00FF00")); + } + + @Test + void getColorValidCaseInsensitive() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/color/BLACK")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("#000000")); + } + + @Test + void getColorValidMixedCase() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/color/Red")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("#FF0000")); + } + + @Test + void getColorValidLowerCase() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/color/blue")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string("#0000FF")); + } + + @Test + void getColorInvalidNotFound() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/color/purple")) + .andExpect(MockMvcResultMatchers.status().isNotFound()); + } + + @Test + void getColorInvalidEmpty() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/color/")) + .andExpect(MockMvcResultMatchers.status().isNotFound()); + } + + @Test + void getColorInvalidNonExistent() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/color/orange")) + .andExpect(MockMvcResultMatchers.status().isNotFound()); + } + + @Test + void getColorInvalidRandomString() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/color/xyz123")) + .andExpect(MockMvcResultMatchers.status().isNotFound()); + } + + @Test + void getColorInvalidSpecialCharacters() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/color/@#$")) + .andExpect(MockMvcResultMatchers.status().isNotFound()); + } + + @Test + void getColorInvalidNumber() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/color/123")) + .andExpect(MockMvcResultMatchers.status().isNotFound()); + } + + // Tests for Chuck Norris joke endpoint + // Note: These are integration tests that call the actual API + // In a production environment, you would mock the external API calls + + @Test + void getRandomJokeSuccess() throws Exception { + // This test calls the actual Chuck Norris API + // It should return a 200 status with joke content + mockMvc.perform(MockMvcRequestBuilders.get("/joke")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string(org.hamcrest.Matchers.not(""))); + } + + @Test + void getRandomJokeContentType() throws Exception { + // Test that the response content type is text/plain + mockMvc.perform(MockMvcRequestBuilders.get("/joke")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().contentType("text/plain;charset=UTF-8")); + } + + @Test + void getRandomJokeMultipleCalls() throws Exception { + // Test multiple calls to ensure the endpoint is stable + for (int i = 0; i < 3; i++) { + mockMvc.perform(MockMvcRequestBuilders.get("/joke")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().string(org.hamcrest.Matchers.not(""))); + } + } + + // Tests for parseurl endpoint + @Test + void parseUrlValid() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/parseurl").param("url", "https://example.com:8080/path/to/resource?foo=bar&baz=qux")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().contentType("application/json;charset=UTF-8")) + .andExpect(MockMvcResultMatchers.content().string(org.hamcrest.Matchers.containsString("\"protocol\":\"https\""))) + .andExpect(MockMvcResultMatchers.content().string(org.hamcrest.Matchers.containsString("\"host\":\"example.com\""))); + } + + @Test + void parseUrlMalformed() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.get("/parseurl").param("url", "ht!tp://bad-url")) + .andExpect(MockMvcResultMatchers.status().isBadRequest()) + .andExpect(MockMvcResultMatchers.content().string(org.hamcrest.Matchers.containsString("Malformed URL"))); + } + } \ No newline at end of file