BLOG

Custom knowledge chatbot in terminal using langchainrb and pgvector

Custom knowledge chatbot in terminal using langchainrb and pgvector

Introduction

Short (but long…) weekend project, let’s scratch the langchain framework. Cause Yes you have one with ruby! The python project is of course way more advanced but you can still build RAG with it!

In this article:

  • Write a custom knowledge chatbot from your terminal in Ruby!
  • Chat with your stored pdf

Requirements

Install first (for linux ubuntu/debian):

Before starting with the installation process, make sure you have the following pre-installation requirements:

  • Update and upgrade your system:
  $ sudo apt update
  $ sudo apt upgrade
  $ psql --version
  # psql (PostgreSQL) XX.X (...)
  # your_pg_version_number = XX
  $ sudo apt install postgresql-your_pg_version_number-pgvector

pgvector SUPERUSER issue

If you encounter any issues with superuser permissions, follow the steps mentioned in the Server Fault post to add a superuser.

What you should do is follow Chris James’s answer:

Open a new terminal:

sudo -u postgres psql postgres

# type "\password" then postgres

Enter new password: 

# type "ALTER ROLE postgres SUPERUSER;"

Open AI API Key

Make sure you also have your API key for OpenAI ready before proceeding with the installation.
If not visit https://platform.openai.com/api-keys, register, create and copy your newly created key nearby (it’s coming!).

Let’s write some code!

First steps – your project files

Create a directory and a Gemfile inside:

# Change directory to the desired project directory
cd go/to/my/project/directory   
# Create a new directory named my_weekend_project
mkdir my_weekend_project        
# Create a new empty file named Gemfile
touch Gemfile                   
# Create a new empty file named .gitignore
touch .gitignore                
# Create a new empty file named .env
touch .env                      

Optional:

# Create a new empty directory named lib to include your Plain Old Ruby Objects!
$ mkdir lib 

With Ruby, it’s Objects everywhere! Object Oriented Programing (what is it? read here: https://www.freecodecamp.org/news/what-is-object-oriented-programming/) advised but optionnal here.

Open the Gemfile, paste it this:

source 'https://rubygems.org'

gem 'langchainrb', "~> 0.9.2"
gem "sequel", "~> 5.77.0"
gem "pg", "~> 1.5.5"
gem "pgvector", "~> 0.2.2"

gem 'ruby-openai', "~> 6.3.1"
gem 'dotenv', '~> 3.0.2'
gem "pdf-reader", "~> 2.12.0"

In your terminal (in the project directory):

$ bundle install

Optionnal – Github
Write in your « .gitignore » file (just skip if you don’t want to add it to GitHub)

.env

Not sending your .env file (that contains the precious api key) is just mandatory <3
Showing a private credential publicly on your Github repository is really sad :,O
But it’s ok now, with the .gitignore !

Not Optionnal
Write in your « .env »:

OPENAI_API_KEY=nowayimwritingmyreelapikeyinthisarticlebutyesinthefile

Using environment variables, like the one in the .env file, is a common practice in software development for storing sensitive information such as API keys, database credentials, and other configuration values. By using environment variables, you can keep this sensitive information out of your codebase and separate from your application logic. This adds an extra layer of security and makes it easier to manage and update these values without having to modify your code. Additionally, using environment variables helps to keep your codebase clean and maintainable by centralizing configuration settings in one place…. toooo long! Thanks GPT !

Nice ! Solve the bugs (coding world, sorry), then create a new file:

$ touch index.rb

Starting to code !

In your index.rb file:

# This line is used to load the Bundler gem and set up the project's load path.
require 'bundler/setup'
# This line tells Bundler to require all the gems specified in the Gemfile under the `:default` group, all gems actually...
Bundler.require(:default)
# This line loads the environment variables from the `.env` file into the project. BOOM!
Dotenv.load

Start configuring the Model you will use:

# rest of the code...

# Initialize a new instance of the Langchain OpenAI LLM
gpt_llm = Langchain::LLM::OpenAI.new(  
    # Set the API key using the value from the environment variable
    api_key: ENV["OPENAI_API_KEY"],  
    llm_options: { 
      # Set the model temperature parameter, 0.0 = few hallucination risk
      temperature: 0.0, 
      # Specify the chat completion model, who talks, 16k context window and cheap
      chat_completion_model_name: "gpt-3.5-turbo",  
      # Set the dimension parameter, so the database will better choose the right text sequence to retrieve
      dimension: 1536,
      # Specify the embeddings model, who changes text to vectors in your vector database, super cheap one (02/2024) 
      embeddings_model_name: "text-embedding-3-small"  
    }
  )
puts "LLM ready!"

Ok, in just a few lines your parameters, chat model, embeddings model and options set!

Next start the Vector Database (below gpt_llm):

That should be done, if not create your db from the terminal:

$ createdb my_cute_pg_vector_db

index.rb

# rest of the code...

# Define the name of the PostgreSQL database
db_name = "my_cute_pg_vector_db"  
# Retrieve the PostgreSQL credentials from the environment variable, like a pro
username = ENV["POSTGRES_USER"] 
password = ENV["POSTGRES_PASSWORD"]
# Construct the connection URL for PostgreSQL 
url = "postgres://#{username}:#{password}@localhost:5432/#{db_name}"  

# Initialize a new instance of Langchain Pgvector
pgvector = Langchain::Vectorsearch::Pgvector.new(  
  url: url,  # Set the PostgreSQL connection URL
  index_name: "Documents",  # Specify the index name, add whatever
  llm: llm,  # Pass the previously initialized Langchain OpenAI LLM instance
  namespace: nil  # Set the namespace to nil, not using it, such a shame... kidding!
)


# No table yet, creating one will be better to save your vectors ;)
pgvector.create_default_schema
# Define the name of the PostgreSQL database
db_name = "my_cute_pg_vector_db"  
# Retrieve the PostgreSQL credentials from the environment variable, like a pro
username = ENV["POSTGRES_USER"] 
password = ENV["POSTGRES_PASSWORD"]
# Construct the connection URL for PostgreSQL 
url = "postgres://#{username}:#{password}@localhost:5432/#{db_name}"  

# Initialize a new instance of Langchain Pgvector
pgvector = Langchain::Vectorsearch::Pgvector.new(  
  url: url,  # Set the PostgreSQL connection URL
  index_name: "Documents",  # Specify the index name, add whatever
  llm: llm,  # Pass the previously initialized Langchain OpenAI LLM instance
  namespace: nil  # Set the namespace to nil, not using it, such a shame... kidding!
)


puts "Database instance is on!"

# No table yet, creating one will be better to save your vectors ;)
pgvector.create_default_schema

puts "Schema created!"

Put credentials in your ‘.env’ file:

POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres

Look at the « pgvector SUPERUSER issue » if you don’t know how to update your credentials… « How to change my postgres password? » in your search engine is working nicely 😉

Add the knowledge as pdf documents

Now your db is ready… ok, just a bit empty. Better to embed a pdf file 😀 (skip if you don’t have pdf)

It’s pretty easy, if you use what’s included:

# rest of the code...

my_pdf = Langchain.root.join("/path/to/a/file.pdf")
pgvector.add_data(paths: [my_pdf])
puts "Loaded!"

But it’s an array… 2 files? Below it you can code:

my_pdf_2 = Langchain.root.join("/path/to/a/file/in/pdf.pdf")
my_pdf_3 = Langchain.root.join("/path/to/a/third/file/in/pdf.pdf")
pgvector.add_data(paths: [my_pdf_2, my_pdf_3])
puts "More inside! Finally!"

Cooool! (sorry i’m old, lol :3) 3 pdfs memorized!

Add the knowledge as text

No pdf? You have text? Nice! The langchainrb gem just gets your back:

# Add plain text data to your vector search database:
pgvector.add_texts(
  texts: [
    "Chuck Norris does in fact use a stunt double, but only for crying scenes.",
    "Chuck Norris went skydiving and his parachute didn't open. Chuck took it back for a refund."
  ]
)
puts "Text added!"

Search the database

Now is the time to ask a question to the database.

# rest of the code...

# Display a message prompting the user to enter a query
puts "Write the query: \n"  
# Read and store the input as the query
query = gets.chomp  
# Set the value of k to specify the number of results to be retrieved
# 1 = 1 chuck norris fact
k = 1  

# Perform a similarity search using the Pgvector instance
search_result = pgvector.similarity_search(  
  query: query,  # Pass the user-entered query
  k: k  # Specify the number of results to be retrieved
)

Share with the ai

Cool, and now question + entry from the database. Just need your favorite (or not?) gpt model to get both and answer with the help of its « long-term » memory.

# rest of the code...

# Retrieve the content from the search result
content = search_result[1].content  
puts "Retrieved: #{content}

# Initialize the thread, keeps track of messages in a conversation
thread = Langchain::Thread.new
# Initialize a new instance of Langchain Assistant
assistant = Langchain::Assistant.new(  
              llm: llm,  # Pass the Langchain OpenAI LLM instance
              thread: thread  # Specify the thread for the conversation
            )

# Construct the prompt with context, query, and placeholder for the answer
prompt = "Context: \n
      #{content}\n
      Query: \n
      #{query}\n
      Answer: "  

puts prompt

# Add the prompt as a user message to the conversation
assistant.add_message(content: (prompt), role: "user")  
# Run the assistant to generate a response based on the context and query
assistant.run
puts "Waiting for answer..."

puts "Assistant's Response: #{assistant.thread.messages.last.content}"

Wooohoo ! Finally, it is aliiiive! You can only ask one question actually…
Put in a loop, more interface, features to show colors, as you like 😉

To run it:

$ ruby index.rb

Get the code (bit updated) on: https://github.com/alegarn/langchainrb_pgvector_test

First on blog: alegar.ch/blog

Acephal

En partenariat avec Alveol !

Une plateforme pour créer et afficher des formulaires

Acephal est une application web qui permet aux utilisateurs de créer des formulaires avec une interface glisser-déposer, de générer des formulaires HTML et de faire des appels API. Elle est construite sur les technologies NextJs, Ruby on Rails et KeyCloak.

Technologie

NextJs pour le front-end

Ruby on Rails pour le back-end

KeyCloak pour la gestion et l’authentification des utilisateurs

Objectif

L’objectif principal d’Acephal est de fournir une interface conviviale pour créer des formulaires et les afficher avec du code HTML généré automatiquement et des appels d’API. Cela signifie que les utilisateurs peuvent facilement concevoir des formulaires sans code et les afficher avec un minimum d’effort.

Fonctionnalités

Certaines des fonctionnalités clés d’Acephal incluent :

– Interface sans code pour la création de formulaire

– Création facile de formulaires avec la fonction glisser-déposer

– Tableau pour afficher les réponses des utilisateurs

– Exportation des réponses des utilisateurs sous forme de fichiers CSV ou XLS

– Champ de fichier avec la possibilité de modifier la taille du fichier

– Tous les formulaires enregistrés pour un accès et une gestion faciles

Avec Acephal, les utilisateurs peuvent facilement créer des formulaires pour une variété de fins, y compris les sondages, les formulaires de feedback et bien plus encore.

Malheureusement Acephal n’est plus en ligne depuis un certain temps.

Une analyse basique: formation à THP

Ouverture de fichier CSV

Répertoire GitHub:

  • GitHub GHub : https://github.com/alegarn/S4J2-THP2-Pandas-Basics

Language:

  • Python Python

Librairies:

Type de projet:

  • Petits projets
  • Data Analyse

La librairie Pandas

Pandas est une librairie Python très utilisée en Data Analyse car elle comporte les outils nécessaires à une analyse de donnée (effectuer un chargement, un nettoyage, une transformation et une analyse).

Toute donnée est transformée en « Dataframe », bloc de base avec la librairie Pandas. Les Dataframes sont des tableaux pouvant accueillir des millions de lignes et de colonnes… si vous avez assez de mémoire RAM.

Une analyse basique

Le but de cet exercice est de manipuler Pandas directement en effectuant une analyse. Ici peu de nettoyage, et des commandes de base.

Et voici un intitulé comportant les questions.

Répondre aux questions posées par ton manager

Ton manager t’a donné une liste de questions pour te rendre la tâche plus concrète. Il t’a demandé de répondre à ces questions dans un premier temps en laissant apparentes tes requêtes sur le Notebook.

  • Combien y-a-t-il de musées en France métropolitaine ?
  • Dans quelle(s) ville(s) y-a-t-il de plus de musées ?
  • Quel est le nombre moyen de musées par ville ?
  • Quel est le nombre médian de musées par ville ?
  • Comment sont répartis les musées par type (en pourcentage) ?
  • Combien y-a-t-il de musées dont le nom commence par « Château » ?
  • Pour combien de musées dispose-t-on de l’adresse du site web ?
  • Quel département français possède le plus de musées sur son territoire ?
  • Quel département français possède le moins de musées sur son territoire ?
  • Combien de musées ont « Napoléon » dans leur nom ?

Charger et nettoyer les données

Toute analyse commence par importer les librairies, chargement, le nettoyage de donnée, cette fois ci très succint: enlever les colonnes en trop.

import seaborn as sns
import numpy as np
import pandas as pd

import csv
df = pd.read_csv(r'liste-des-musees-de-france-2021-point-virgules.csv', sep=";")

columns_to_drop = ['osm_id', 'country_code', 'country', 'lat','lon', 'wikidata', 'fax', 'description', 'date_added' ]
data_dropcol = df.drop(columns_to_drop,axis=1)

print(data_dropcol.head(10))

Manipuler les Dataframes

  • Combien y-a-t-il de musées en France métropolitaine ?
index = data_dropcol.index
number_of_rows = len(index)
total_museum = number_of_rows

museum_number_template = """Il y a au total {} musées en France."""

museum_number_message = museum_number_template.format(total_museum)
print(museum_number_message)
Il y a au total 3784 musées en France.
  • Dans quelle(s) ville(s) y-a-t-il de plus de musées ?
data_cities_all_col = pd.DataFrame(data_dropcol, columns= ['name','city'])

city_museum_number_template = """Il y a au total {} musées en France."""


museum_number_each_city = data_cities_all_col.pivot_table( columns=['city'], aggfunc='size').sort_values
number = data_cities_all_col['city'].str.split(' ').str[0:3].value_counts()

number_top_10_text = """Le top 10 'villes avec le plus de musées en France: 
{} """

number_top_10_message = number_top_10_text.format(number[0:11])


print (number_top_10_message)
Le top 10 'villes avec le plus de musées en France: 
[Paris]         175
[Lyon]           33
[Marseille]      28
[Grenoble]       22
[Bordeaux]       20
[Toulouse]       20
[Nice]           16
[Strasbourg]     16
[Rouen]          15
[Lille]          15
[Reims]          13
Name: city, dtype: int64 
  • Quel est le nombre moyen de musées par ville ?
per_city_number_museum_mean = """Il y a en moyenne {} musées par ville."""
museum_number_each_city_mean = data_cities_all_col.pivot_table( columns=['city'], aggfunc='size').mean(axis=0)
museum_mean_message = per_city_number_museum_mean.format(museum_number_each_city_mean)

print(museum_mean_message)
Il y a en moyenne 1.647930283224401 musées par ville.
  • Quel est le nombre médian de musées par ville ?
per_city_number_museum_median = """Nombre médian de musées par ville: {}"""
museum_number_each_city_median = data_cities_all_col.pivot_table( columns=['city'], aggfunc='size').median(axis=0)
museum_median_message = per_city_number_museum_median.format(museum_number_each_city_median)

print(museum_median_message)
Nombre médian de musées par ville: 1.0
  • Comment sont répartis les musées par type (en pourcentage) ?
data_tags = pd.DataFrame(data_dropcol, columns = ['name','tags'])

museum_number_each_tag = data_tags.pivot_table(columns=['tags'], aggfunc='size')

museum_number_each_tag_percent = pd.DataFrame(museum_number_each_tag, columns=['size'])

museum_number_each_tag_percent['percent'] = (museum_number_each_tag_percent['size'] / museum_number_each_tag_percent['size'].sum()) * 100

sample = museum_number_each_tag_percent.loc[museum_number_each_tag_percent["percent"]>5]

museum_tag_message_string = """Voici la répartition (> 5%) des types de musée: 
{}"""
museum_tag_message = museum_tag_message_string.format(sample)

print(museum_tag_message)
Voici la répartition (> 5%) des types de musée: 
                                               size    percent
tags                                                          
osm:museum                                     2175  57.509254
osm:museum;type:ecomusee                        387  10.232681
osm:museum;type:musee technique et industriel   194   5.129561
  • Combien y-a-t-il de musées dont le nom commence par « Château » ?
name_castle_sentence = """Il y a un nombre de 'Château' musée de {} ."""
sample
name_castle = pd.DataFrame(data_dropcol, columns= ['name']).add_prefix('name_')
name_castle['name'] = data_dropcol['name'].str.replace('�', "â")
new = name_castle['name'].str.split(' ').str[0].value_counts()

print(name_castle_sentence.format(new['Château']))
Il y a un nombre de 'Château' musée de 48 .
  • Pour combien de musées dispose-t-on de l’adresse du site web ?
with_website_template = """Il y a {} musées possédant un site internet."""

frame_internet = data_dropcol.pivot_table(index="website", values="name", aggfunc=np.count_nonzero)


with_website = with_website_template.format(frame_internet.sum(axis=0)['name'])

print(with_website)
Il y a 1636 musées possédant un site internet.
  • Quel département français possède le plus de musées sur son territoire ?
city_zipcode_text = """Le département français ayant le plus de musées est le {} avec {} musées."""

city_zipcode = pd.DataFrame(data_dropcol, columns= ['name', 'postal_code']) 
city_zipcode['department'] = city_zipcode['postal_code'].astype(str).str[:2]
museum_each_dep = city_zipcode.pivot_table(columns=['department'], aggfunc='size').add_prefix('zip_')

city_zipcode_message_zip = city_zipcode_text.format(museum_each_dep.loc[museum_each_dep == museum_each_dep.max()].to_string()[15:18], museum_each_dep.loc[museum_each_dep == museum_each_dep.max()][0])
                                          
print(city_zipcode_message_zip)
Le département français ayant le plus de musées est le 75  avec 180 musées.
  • Quel département français possède le moins de musées sur son territoire ?
city_zipcode_text_lower = """Le département français ayant le moins de musées est le {} avec {} musées."""
dep_zip_less = museum_each_dep.loc[museum_each_dep == museum_each_dep.min()].to_string()
dep_zip_total_min = museum_each_dep.loc[museum_each_dep == museum_each_dep.min()][0]

city_zipcode_full_message = city_zipcode_text_lower.format(dep_zip_less[15:18], dep_zip_total_min)


print(city_zipcode_full_message)
Le département français ayant le moins de musées est le 98  avec 8 musées. 

Déduction: Les outre-mer.

  • Combien de musées ont « Napoléon » dans leur nom ?
number_museum_emperor_template = """Il y a {} musées 'Napoléon'."""

search_name = pd.DataFrame(data_dropcol, columns= ['name']) 
search_name['name'] = data_dropcol['name'].str.replace('�', "é")
contain_values = search_name[search_name['name'].str.contains('Napoléon')].count()

number_museum_emperor_text = number_museum_emperor_template.format(contain_values.to_string())

print(number_museum_emperor_text)
Il y a name    4 musées 'Napoléon'.

Voilà, c’était une des premières utilisations de Pandas! (la 1ère…)

Le Bonus

Du SQL sur les Panama Papers: https://github.com/alegarn/S4J4-THP2-Basic-SQL

SQL_Panama_Papers: du SQL / Python et SQLite3

L’exploration de la base de donnée (retourner chaque table, leur relations et toutes les colonnes).

import sqlite3

def sqlite_table_schema(conn, name):
    """Return a string representing the table's CREATE"""
    cursor = conn.execute("SELECT sql FROM sqlite_master WHERE name=?;", [name])
    sql = cursor.fetchone()[0]
    cursor.close()
    return sql

# the database connection
with sqlite3.connect('databases/panamapapers.sqlite3') as conn:
    print("Opened database successfully")

# all tables
    cursor = conn.execute("SELECT name FROM sqlite_master WHERE type='table';")
    table_names = []
    for row in cursor:
        table_names.append(row)
       
    for row in table_names:

        transform = ''.join(row)
        
        exe = "SELECT * FROM {};"
        cursor = conn.execute(exe.format(transform))
        names = list(map(lambda x: x[0], cursor.description))
        print("\nNom de la table: " + transform + "\n")
        print("A complete table scheme : ")
        print(sqlite_table_schema(conn, transform), "\n")
        print(" Et voici chaque colonnes:")

        for columns in names:
            print(" - " + str(columns))

    print("Operation done successfully")
Opened database successfully

Nom de la table: source

A complete table scheme : 
CREATE TABLE source (
  id INTEGER PRIMARY KEY,
  source TEXT
) 

 Et voici chaque colonnes:
 - id
 - source

Nom de la table: status

A complete table scheme : 
CREATE TABLE status (
  status TEXT,
  id INTEGER PRIMARY KEY
) 

 Et voici chaque colonnes:
 - status
 - id

Nom de la table: country

A complete table scheme : 
CREATE TABLE country (
  code TEXT,
  country TEXT,
  id INTEGER PRIMARY KEY
) 

 Et voici chaque colonnes:
 - code
 - country
 - id

Nom de la table: address

A complete table scheme : 
CREATE TABLE address  (
  address TEXT,
  countries TEXT,
  country_codes TEXT,
  id_address BIGINT PRIMARY KEY,
  source_id INTEGER
  ,FOREIGN KEY (source_id) REFERENCES source(id)
) 

 Et voici chaque colonnes:
 - address
 - countries
 - country_codes
 - id_address
 - source_id

Nom de la table: intermediary

A complete table scheme : 
CREATE TABLE intermediary (
  id INTEGER  
	PRIMARY KEY
  ,name TEXT,
  source_id INTEGER,
  note TEXT,
  /* url TEXT, */
  id_address BIGINT,
  status_id INTEGER
  , url TEXT,FOREIGN KEY (id_address) REFERENCES address(id_address)
  ,FOREIGN KEY (source_id) REFERENCES source(id)
  ,FOREIGN KEY (status_id) REFERENCES status(id)
) 

 Et voici chaque colonnes:
 - id
 - name
 - source_id
 - note
 - id_address
 - status_id
 - url

Nom de la table: entity

A complete table scheme : 
CREATE TABLE entity (
  id INTEGER PRIMARY KEY,
  name TEXT,
  jurisdiction TEXT,
  jurisdiction_description TEXT,
  incorporation_date DATE,
  status TEXT,
  service_provider TEXT,
  source TEXT,
  note TEXT,
  id_address BIGINT,
  end_date DATE
  , url TEXT, lifetime INTEGER,FOREIGN KEY (id_address) REFERENCES address(id_address)
) 

 Et voici chaque colonnes:
 - id
 - name
 - jurisdiction
 - jurisdiction_description
 - incorporation_date
 - status
 - service_provider
 - source
 - note
 - id_address
 - end_date
 - url
 - lifetime

Nom de la table: officer

A complete table scheme : 
CREATE TABLE officer (
  id INTEGER  
	PRIMARY KEY
  ,name TEXT,
  source_id INTEGER,
  note TEXT,
  country_id INTEGER
  ,FOREIGN KEY (country_id) REFERENCES country(id)
  ,FOREIGN KEY (source_id) REFERENCES source(id)
) 

 Et voici chaque colonnes:
 - id
 - name
 - source_id
 - note
 - country_id

Nom de la table: assoc_officer_entity

A complete table scheme : 
CREATE TABLE assoc_officer_entity (
  officer INTEGER,
  assoc_type TEXT,
  entity INTEGER,
  source_id INTEGER,
  start_date DATE,
  end_date DATE
  ,FOREIGN KEY (officer) REFERENCES officer(id)
  ,FOREIGN KEY (entity) REFERENCES entity(id)
  ,FOREIGN KEY (source_id) REFERENCES source(id)
) 

 Et voici chaque colonnes:
 - officer
 - assoc_type
 - entity
 - source_id
 - start_date
 - end_date

Nom de la table: assoc_inter_entity

A complete table scheme : 
CREATE TABLE assoc_inter_entity (
  inter INTEGER ,
  entity INTEGER,
  source_id INTEGER,
  FOREIGN KEY (inter) REFERENCES intermediary(id),
  FOREIGN KEY (entity) REFERENCES entity(id),
  FOREIGN KEY (source_id) REFERENCES source(id)
) 

 Et voici chaque colonnes:
 - inter
 - entity
 - source_id

Nom de la table: assoc_officers

A complete table scheme : 
CREATE TABLE assoc_officers (
  officer1 INTEGER ,
  assoc_type TEXT,
  officer2 INTEGER ,
  start_date DATE,
  end_date DATE,
FOREIGN KEY (officer1) REFERENCES officer(id),
FOREIGN KEY (officer2) REFERENCES officer(id)
) 

 Et voici chaque colonnes:
 - officer1
 - assoc_type
 - officer2
 - start_date
 - end_date

Nom de la table: assoc_intermediaries

A complete table scheme : 
CREATE TABLE assoc_intermediaries (
  interm1 INTEGER ,
  assoc_type TEXT,
  interm2 INTEGER ,
  start_date DATE,
  end_date DATE,
FOREIGN KEY (interm1) REFERENCES intermediary(id),
FOREIGN KEY (interm2) REFERENCES intermediary(id)
) 

 Et voici chaque colonnes:
 - interm1
 - assoc_type
 - interm2
 - start_date
 - end_date

Nom de la table: assoc_entities

A complete table scheme : 
CREATE TABLE assoc_entities (
  entity1 INTEGER ,
  assoc_type TEXT,
  entity2 INTEGER ,
  start_date DATE,
  end_date DATE,
FOREIGN KEY (entity1) REFERENCES entity(id),
FOREIGN KEY (entity2) REFERENCES entity(id)
) 

 Et voici chaque colonnes:
 - entity1
 - assoc_type
 - entity2
 - start_date
 - end_date

Nom de la table: assoc_inter_offi

A complete table scheme : 
CREATE TABLE assoc_inter_offi (
  inter INTEGER ,
  assoc_type TEXT,
  officer INTEGER,
  start_date DATE,
  end_date DATE,
  FOREIGN KEY (inter) REFERENCES intermediary(id),
  FOREIGN KEY (officer) REFERENCES officer(id)
) 

 Et voici chaque colonnes:
 - inter
 - assoc_type
 - officer
 - start_date
 - end_date

Nom de la table: assoc_officer_interm

A complete table scheme : 
CREATE TABLE assoc_officer_interm (
  officer INTEGER ,
  assoc_type TEXT,
  interm INTEGER,
  start_date DATE,
  end_date DATE,
  FOREIGN KEY (officer) REFERENCES officer(id),
  FOREIGN KEY (interm) REFERENCES intermediary(id)
) 

 Et voici chaque colonnes:
 - officer
 - assoc_type
 - interm
 - start_date
 - end_date
Operation done successfully

Pour les Panama Papers, je vous donne les questions:

  • Combien la base de données contient-elle de sociétés offshores différentes dont la source est « Panama Papers » ?
  • Quel intermédiaire a créé le plus de sociétés offshores ? A-t-on son adresse et son pays ?
  • Combien la base contient-elle de bénéficiaires avec un nom unique ? Quel est le bénéficiaire dont le nom revient le plus souvent ?
  • Donner la liste des juridictions avec le nombre d’entreprises offshores enregistrées sur chaque territoire, triée par ordre décroissant.
  • Regrouper les sociétés offshores par statut, et trier la liste par ordre décroissant.
  • Trouver la liste des bénéficiaires dont le nom contient « BNP » et ajouter, pour chaque bénéficiaire, le nom des sociétés offshores.
  • Trouver la liste des sociétés dont la juridiction est « France », « Monaco » ou « Réunion ».
  • Trouver la liste des sociétés dont le pays de l’adresse et le pays de la juridiction sont différents.
  • Trouver la liste des bénéficiaires qui ont des sociétés au même nom et enregistrée à la même date, trier la liste par odre décroissant.
  • Donner la liste des intermédiaires qui ont aussi été bénéficiaires, en ajoutant leur nom de bénéficiaire et leur adresse.

Beaucoup de similarités entre le premier et le deuxième exercice, seulement les 2 dernières seront exposées:

  • Donner le top 10 des bénéficiaires qui ont le plus d’identités différentes (similar name and address) et le nombre d’identités correspondant.
# the database connection
with sqlite3.connect('database_sqlite3/panamapapers.sqlite3') as conn:
    print("Opened database successfully")
    sentence = "Top {} officer with similar name and address (multiple identities): \n officer: {} , {} identities."
# all tables
    i = 1
    cursor = conn.execute("SELECT O.name, count(O.id) FROM officer as O, assoc_officers as AO WHERE O.id = AO.officer1 AND AO.assoc_type = 'similar name and address as' GROUP BY O.name ORDER BY COUNT(O.id) DESC LIMIT 10 ;")
    for row in cursor:
        print(sentence.format(i, row[0], row[1]))
        i = i + 1
Opened database successfully
Top 1 officer with similar name and address (multiple identities): 
 officer: NORTH ATLANTIC SERVICES LIMITED , 811 identities.
Top 2 officer with similar name and address (multiple identities): 
 officer: BROCK NOMINEES LIMITED , 575 identities.
Top 3 officer with similar name and address (multiple identities): 
 officer: TENBY NOMINEES LIMITED , 529 identities.
Top 4 officer with similar name and address (multiple identities): 
 officer: MOHUL NOMINEES LIMITED , 513 identities.
Top 5 officer with similar name and address (multiple identities): 
 officer: SCIVIAS TRUST MANAGEMENT LTD , 369 identities.
Top 6 officer with similar name and address (multiple identities): 
 officer: FORMIA LIMITED , 363 identities.
Top 7 officer with similar name and address (multiple identities): 
 officer: RICHMOND NOMINEES LIMITED , 355 identities.
Top 8 officer with similar name and address (multiple identities): 
 officer: ELCAN NOMINEES LIMITED , 312 identities.
Top 9 officer with similar name and address (multiple identities): 
 officer: DORCHESTER INTERNATIONAL INC. , 311 identities.
Top 10 officer with similar name and address (multiple identities): 
 officer: CAVERSHAM NOMINEES LIMITED , 300 identities.
  • Donner le top 10 des bénéficiaires qui ont le plus de parts toujours valides dans des entreprises offshores (dont la date de fin n’est pas encore passée).

with sqlite3.connect('database_sqlite3/panamapapers.sqlite3') as conn:
    print("Opened database successfully")
    sentence = "Number {} officer {}, Total offshore societies owned, still active : {}."
    print("Officers with entities still 'Valid'")

    cursor = conn.execute("SELECT O.name, count(O.id) FROM entity as E, officer as O, assoc_officer_entity as AOE WHERE O.id = AOE.officer and AOE.entity = E.id and E.status = 'Active' GROUP BY O.id ORDER BY COUNT (O.id) DESC LIMIT 10.")
    i = 1
    for row in cursor:
        print(sentence.format(i, row[0], row[1]))
        i = i + 1
Opened database successfully
Officers with entities still 'Valid'
Number 1 officer MOSSFON SUBSCRIBERS LTD., Total offshore societies owned, still active : 2111.
Number 2 officer BOS NOMINEES (JERSEY) LIMITED, Total offshore societies owned, still active : 294.
Number 3 officer BOS SECRETARIES (JERSEY) LIMITED, Total offshore societies owned, still active : 284.
Number 4 officer MOSTALINA INVESTMENTS S.A, Total offshore societies owned, still active : 113.
Number 5 officer BROCK NOMINEES LIMITED, Total offshore societies owned, still active : 96.
Number 6 officer Dorchester International Inc, Total offshore societies owned, still active : 95.
Number 7 officer Cannon Nominees Limited, Total offshore societies owned, still active : 93.
Number 8 officer INTERCON LIMITED, Total offshore societies owned, still active : 87.
Number 9 officer MAYTREE OVERSEAS S.A., Total offshore societies owned, still active : 87.
Number 10 officer TENBY NOMINEES LIMITED, Total offshore societies owned, still active : 82.

Réponses: https://github.com/alegarn/S4J4-THP2-Basic-SQL

La base de Dash

Pourquoi ce projet?

Dans tous les outils Python existant pour faire un dashboard interactif sans certaines connaissances en HTML/CSS ou Javascript, se trouve une librairie du nom de Dash.

Et combiné avec Plotly (qui s’occupe des graphiques), réaliser des dashboards est relativement simple.

Tableau Tableau est très puissant, toutefois plutôt gourmant et coûteux. Pour réaliser des dashboards, il n’est toutefois pas indispensable.

Répertoire GitHub:

  • GHub https://github.com/alegarn/basic_dash

Language:

  • Python Python

Type de projet:

  • Petit projet
  • Data Visualisation

Libraries:

Aperçu

0 – Ouvrir les données et faire une Dataframe

import pandas as pd

# 1: simple chart
import plotly.express as px
# 1 + 2: the pie chart
import plotly.graph_objects as go

# to create the final dashboard
from dash import Dash, dcc, html, Input, Output
from dash.dependencies import Input, Output

with open('data/combined_final_last_10_years.csv', 'r') as file:
    data_last = pd.read_csv(file, sep=',')

# save the csv datas, and put it in the last graph
total_data = data_last

#a slice of data, new dataframe
data_last = data_last[['continent', 'country', 'year', 'income_per_person', 'gini_index']]
df = pd.DataFrame(data_last)

# to display 10 lines
df.head(10)

1 – Réaliser un graphique en bar simple

fig1 = px.bar(gini_con_mean_2006, x='continent', y='gini_index')

fig1.show()

Ce premier graphique construit avec Plotly, pas de CSS, et seulement un croisement de 2 colonnes.

Ici le continent le plus inégalitaire en 2006, possédant un indice de Gini de prêt de 50 est le continent Américain, ce qui n’est pas le cas de l’Europe, son indice étant proche de 32.

2 – Construire un graphique camembert plus compliqué

fig2 = go.Figure(data=[go.Pie(labels = labels,
                             values = values)])
colors = ['gold', 'mediumturquoise', 'darkorange', 'lightgreen']

fig2.update_traces(
  hoverinfo='label', 
  textinfo='value', 
  textfont_size=20,
  marker=dict(
    colors=colors, 
    line=dict(color='#000000', 
    width=2)))

fig2.show()

Un autre test de Plotly. Par contre, il y a un rajout de CSS (des couleurs, titre stylisé).

Le camembert permet de comparer facilement des données, « parties » d’un même total. L’Océanie, Nouvelle Zélande et Australie, est loin devant avec un revenue moyen de 35 950$ par an.

3 – Commencer le dashboard

Une fois les 2 premiers graphiques affichés, commence la construction du Layout (la page du Dashboard) contenant les éléments (graphiques et explications).

# new Dash app
app = Dash(__name__)

# CSS 
colors = {
    'background': '#111111',
    'text': '#7FDBFF'
}


# HTML page/layout

app.layout = html.Div([
    # Dashboard title
    html.Div([
        html.H1(
            children='Countries Plotly Visualization',
            style={'textAlign': 'center','color': '#2E10E6'})
        ]),

    # the first figure div, a simple one
    html.Div([
        html.H1(
            children='Bar chart: continent Gini index average in 2006'),
        html.Div(
            dcc.Graph(
                id='gini_index_per_continent',
                figure=fig1)
        )]),

    # the second figure div, simple with CSS
    html.Div([
        html.H1(
            children='Pie chart: The 2006 average income per person and continent',
            style={'textAlign': 'center','color': colors['text']}),
        html.Div(
            dcc.Graph(
                id='income_per_continent',
                figure=fig2)
        )]),

    # the third figure div, but this one can be changed
    html.Div([
        html.H1(
            children='Scatter chart: Yearly internationals income per person compare to the Gini index',
            style={'textAlign': 'center','color': colors['text']}),
        # https://dash.plotly.com/basic-callbacks
        html.Div([
            dcc.Graph(
            id='gini_with_income_per_continent'),
            dcc.Slider(
                total_data['year'].min(),
                total_data['year'].max(),
                step=None,
                value=total_data['year'].min(),
                marks={str(year): str(year) for year in total_data['year'].unique()},
                id='year-slider'
            )
        ])
    ])
])

Le code semble plutôt simple, on se perd dans les parenthèses. Cependant lorsqu’on sait faire un peu de HTML / CSS, écrire les balises est plus simple.

4 – Le graphique « dispersé » (Scatter)

Pour ce dernier graphique, c’est le moment d’en mettre plein la vue!

Voici un graphique type « dispersé », contenant 2 axes, des couleurs et des bulles de taille différentes!

Niveau visualisation, il permet de découper certains « groupes » (la plupart des pays Européens ont un index de Gini faible, contrairement à l’Amérique), en plus de permettre des rapprochements variés (on remarque les pays d’Afrique les plus riches sont aussi les plus inégalitaires, ce qui n’est pas le cas de l’Asie).


# that's the same for every graph, unique parameter to change 
@app.callback(
    # graph show
    Output('gini_with_income_per_continent', 'figure'),
    # object to update a graph
    Input('year-slider', 'value'))

# a unique function, even with multiple graphs
def update_figure(selected_year):

    # updated data
    filtered_df = total_data[total_data.year == selected_year]

    # graphic result
    fig3 = px.scatter(
       filtered_df, 
       x="income_per_person",
       y="gini_index",
       size="demox_eiu", 
       color="continent",
       hover_name="country",
       log_x=True, 
       size_max=60
     )

    # change parameters
    fig3.update_layout(transition_duration=300)
    
    return fig3

# to run the server
if __name__ == '__main__':
    app.run_server(debug=True, use_reloader = False) # use_reloader for Jupyter Notebook

Ci-dessus, on peut voir une introduction aux callbacks, le back-end qui permet quelques fonctionnalités rendant notre dashboard interactif (le slider avec les années partant de 2006 à 2016, s’ajoute à cela des données au survol d’une bulle).

Lancer le serveur, cela prend 2 lignes de codes. Avec un Jupyter-Notebook une petite subtilité, « use_reloader = False » pour ne pas faire se relancer le code tout le temps, puis s’interroger « pas de serveur??? WT.??? »

Une conclusion

Test concluant, la prise en main (à l’aide de tutoriaux) se fait sans trop de difficultés. De plus le rendu est satisfaisant. Très bonne librairie gratuite et open-source!

Sing those words! (0)

Apprendre une langue peut s’avérer compliqué. Pouvoir écrire, lire, parler, choisir le vocabulaire à apprendre… beaucoup de choses à mettre en place. Mais il existe des outils dans le but de faciliter ce processus (flashcards, de la mise en relation avec des tuteurs, mnémotechnique …).

Le vocabulaire n’est pas toujours facile à apprendre. Toutefois, il se trouve que certaines personnes s’aident des paroles de chansons pour s’en souvenir. Avec ce programme, le but est d’optimiser la recherche de paroles à apprendre (qui se trouvent dans la langue voulue), pour choisir une ou plusieurs chansons, et les écouter le plus possible.

Répertoire GitHub:

Language de programmation:

  • Ruby Ruby

Techniques utilisées:

  • Scrapping (la gem: Nokogiri)
  • Production de fichiers textes
  • Calculs et CSV (gem: csv)
  • Programmation Orientée Objet
  • Manipulation de données (arrays, tables, formats, et autres)

Aperçu

Le programme est un mvp(p), qui valide la faisabilité de notre concept! L’UX est quasi inexistante, seulement 50 chansons disponibles, toutes venant du groupe a1, entres autres…

Pour commencer, il faut un menu:

Menu: le choix d’actions à effectuer

Plusieurs choix:

  • 1- Prendre chaque paroles de chanson qu’il nous faut (première version: seulement des paroles du groupe a1), le scrapping d’un site web.
  • 2- Pouvoir choisir son propre vocabulaire en l’écrivant sur le terminal, avec aussi le choix de scraper un dictionnaire de fréquence.
  • 3 – Calculer tous les « scores » (toutes les chansons)
  • 4 – Comparer ces ensembles de paroles, choisir finalement ce qu’on veut
  • 5 – Exécuter 1, 3 et 4.
  • 6 – Sortir du programme

1 – Trouver les paroles

Méthode: Le scrapping

Outils: ‘gem’ Nokogiri

Contenu:

  • Prendre les adresses de plusieurs pages
  • VPN (pour changer d’adresse IP)
  • Manipulation et création de dossiers / textes

2 – Choisir son vocabulaire

Méthode: Scrapping / Formulaire

Outils: gem ‘Nokogiri‘ / gem ‘CSV

Contenu:

  • Un scrapper basique (page unique)
  • Transformation en CSV

3 – Scores des paroles

Méthode: Dossiers / Parsing

Outils: gem ‘CSV

Contenu:

  • Manipulation de textes / CSV
  • Analyse de texte (parsing)
  • String en array
Les mots sont comptés dans chaque chanson.

4 – Sélectionner des musiques

Méthode: Dossiers / Parsing / Calculs

Outils: gem ‘CSV

Contenu:

  • Manipulation de textes / CSV
  • Analyse de texte (parsing)
  • Comparaison
  • Trie
Le comparateur de paroles

Conclusion

Ce projet valide le concept, rappelle des bases de programmation (sorting, poo, arrays) et de data (tables, csv), mais trouve ses limites avec toutes les limites (1 langue, a1 comme seul groupe) ainsi qu’un design limité (console).

How to quickly connect Notion to Twitter and LinkedIn with Zapier

The article on your blog is finished? Need to communicate on multiple channels? You can write some quick stuff and copy and paste, on Twitter and LinkedIn… the first, second, third time… Time lost…

Is there any solution to do that in once? Yes, actually you could automate that by yourself (nice dev project). If you need rapid solution, Zapier could do the trick.

On zapier.com it’s possible to connect thousands (that’s a lot) web/desktop app’s, then choose a repetitive task and automate it (that’s a “Zap”). With a little need) for automation (annoying stuff, repetitive, short stuffs), it’s free (only 100 tasks/actions a month with 5 “zaps”).

I – Writing your message – Database on Notion

1) Have a Notion account

2) You need to create a new page “database” type (a normal page will not work in Zapier)

https://s3.us-west-2.amazonaws.com/secure.notion-static.com/2ace2f33-7bf5-45e2-a00d-5a2137b890bf/page.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220622%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220622T021938Z&X-Amz-Expires=86400&X-Amz-Signature=fb3e3808b863bfbe42d500e0467d9a22ba78fa4f0438bca4298f5e8c5608cb7f&X-Amz-SignedHeaders=host&response-content-disposition=filename%20%3D%22page.png%22&x-id=GetObject
Where you want to write: a new article group (”Share” as you can see)

Where you want to write: a new article group (”Share” as you can see)

https://s3.us-west-2.amazonaws.com/secure.notion-static.com/b024995b-3075-4b38-af29-65f69db48318/new_page.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220622%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220622T022308Z&X-Amz-Expires=86400&X-Amz-Signature=ca4d13ed6284da3e03dd972bc675ee2eea00f7c1050fea23c141d1ec5498eb78&X-Amz-SignedHeaders=host&response-content-disposition=filename%20%3D%22new_page.png%22&x-id=GetObject
Create a new page where you want

Create a new page where you want

https://s3.us-west-2.amazonaws.com/secure.notion-static.com/f46247ab-4eea-4cc1-9d41-f08a4ba16e1a/gallery.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220622%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220622T022309Z&X-Amz-Expires=86400&X-Amz-Signature=3f2612c7730d374842a50103cb80837df2dffb794703e0eb67ef5713e1bc1955&X-Amz-SignedHeaders=host&response-content-disposition=filename%20%3D%22gallery.png%22&x-id=GetObject
Database options: The option “Gallery” is choosed here (image + few text)

Database options: The option “Gallery” is choosed here (image + few text)

3) You have a new page of Database’s type! Let’s do your first quick message!

In your Gallery page, click on “New page”

In your Gallery page, click on “New page”

Once opened, put a Title, a quick text (Property 1 section), add your link inside.

One picture in icon section can be more appealing.

Once completed (the icon should be enough)

Once completed (the icon should be enough)

4) You can save your work. Notion’s part end’s here! 👍

II – Let’s automate!

0) Create a Zapier account

1) Do your first Zap! Step 1: Trigger

Choose your app, add a Trigger

Choose your app, add a Trigger

In your Zapier account, find “+ Create Zap”, choose it. You can add Notion to be the first step. One option possible, your message is being send at the “save” step of a new Database Item (for example: new Gallery item).

Next add your Notion account. You have to manage the access from your Notion’s Database items.

Great, before you added a new page who has a Database Item! So choose only an item you are sure that you want to share (Sending every little bit of new script in your Notion… that’s costly, hundreds of $ for Zapier with a really productive workday)

Page to select (at each “save” one “send”)

Page to select (at each “save” one “send”)

Trigger on a Database item

Upper: Trigger on a Database item

Now, let’s test. Zapier is really looking to the good Database? Look at it, if “Property 1” got the god text, you did it!

2) Do your first Zap! Step 2: let’s take Twitter.

Write “Twitter” in the field, choose it. Click on “Create Tweet” in Action Event

Choose « Create Tweet » when in Action Event

Up: Choose « Create Tweet » when in Action Event

Time to choose your account, i let you do that 😉

So it’s Action time! Export your Notion page to a Tweet.

New message should begins your new tweet. Write that short… super short text, plus your Notion Title, with “Property 1”’s text.

If you have “No Data” near “1. Title”:

  • you didn’t wrote anything
  • a problem with the database occured (that’s the wrong one? a wifi problem?…)

Put Data from Page in Notion

Icon time, add it when you see the “Image, Video or GIF” section.

Problem, there is no Icon…

Up: Problem, there is no Icon…

You are close to finish!

Time for “Test Action”

Check your Twitter posts!

Choose “Test & Review”, you can now check Twitter, “Posts” section. There is? Nice!

3) LinkedIn: cf “section 2)” steps are the same 😉

4) Time to work on your articles!

Easy sharing 👌

Conclusion

That’s it for today, so you have the power to automate (using Zapier)!

Play with it during first 14 days, free paid plan 😉 So it’s possible creating “Multiple steps” zaps, or modifications with the zaps!

Data analyse : Ligue 1

Après 2 mois de formation Data à The Hacking Project, pour valider cette formation, un projet final est requis. Le travail en équipe est requis, je l’ai donc réalisé avec 4 autres « moussaillons » (« apprenants » de THP).

La visualisation des données de la Ligue 1 de football a obtenu tous les critères nécessaires d’un projet possédant les critères nécessaires afin d’être validé.

Derrière ce dashboard interactif il y a une idée: visualiser de manière ludique des données qui ne sont pas ou peu accessibles chez des fans de football ne regardant pas des statistiques tous les jours.

Les outils techniques utilisés

Languages de programmation:

  • Ruby Ruby: scrapper des données (gem: Nokogiri)
  • Python Python: nettoyer les données

Outils utilisés:

  • Jup_notebk Jupyter Notebook: nettoyer la donnée
  • Tableau Tableau software: visualiser des données
  • Excel Excel: nettoyage, transformation et stock de nos données
  • Google Slides Google Slides: logiciel de présentation

Caractéristiques du projet:

Cahier des charges

Un cahier des charges est mis en place, répondant aux besoins d’un « célèbre » client dans le monde du football (demande fictive).

Voici le cahier des charges
Un sommaire, 7 parties.

Organisation

Outils contenant les projets: Google Dri Google drive

Communication vocale:Discord Discord

Outils d’organisation des tâches: Trello Trello

Extraction des données

Quand la donnée est disponible au format csv, téléchargement simple:

  • https://thesportsdb.com/

Parfois les données ne sont pas disponibles en csv, on code alors un scrapper avec Ruby Ruby (gem: Nokogiri):

  • https://www.ligue1.fr/
  • https://thesportsdb.com/

GHub Github: scrapper

Transformation de la donnée

Après l’étape de récupération des données, l’étape de transformation. Dans ce projet, il a suffit de peu de transformation, le scrapper faisant une bonne partie du travail.

Stack transformation:

  • Excel Excel: créer visuellement les colonnes avec des formules
  • Python Python + Jup_notebk Jupyter Notebook: Mise en forme de la donnée, peu de statistiques.
Modification des données avec Jupyter et Python

Le but du projet: présenter des données de façon visuelle. Finalement il y a peu de statistiques, peu de transformation des données (des dataframes, quelques jonctions, remplacement de caractères…).

Chargement dans une « base de données »

Le fichier final contenant peu de lignes (681) et quelques 66 colonnes, le choix se porte donc sur un fichier Excel (.xlsx) comme « base de données ». Il est facile à manipuler et à parcourir, dans ce cas pas besoin d’utliser un Notebook Jupyter.

Le fichier Excel final

Comme certaines données étaient manquantes (thesportsdb.com), c’est à la main qu’il a fallu écrire quelques lignes de données (du site ligue1.fr). Dans ce cas-ci, travailler dans un tableau Excel est plus pratique.

Visualisation

Choisi pour son caractère pratique et puissant :

  • Tableau Tableau software.

Les filtres et des formules (MAKEPOINT / MAKELINE…) ont été utilisées pour créer la visualisation finale, la rendant interactive.

Tableau 1: Première partie

Ci-dessus, la visualisation contenant les clubs jouant en Ligue 1, leur classement général, avec les statistiques cumulées par journée. La journée de Ligue 1 correspond à une semaine de rencontres.

Tableau 2: deuxième partie

Ensuite, sont visualisées des statistiques tout au long du championnat, le classement général, le nombre de buts marqués et encaissés par match, la possession par journée terminant ce premier tableau.

Tableau 3: troisième partie

On peut accéder à la dernière partie de la visualisation en cliquant sur le lien « Accéder aux statistiques par journée » (Tableau 1 et Tableau 3). Cette partie présente certaines données sélectionnées par match, pour chaque équipe (précision par équipe des tirs, ou les tirs convertis en buts).

Tester ce projet c’est par ici!

Présentation des données

Lorsque le processus ETL est finis, et après visualisation, une présentation Google Slides afin de présenter le produit au client. L’environnement de travail utilisant Google, Google Slides fut choisi.

Le plan de la présentation du projet

Pour conclure, ce projet mêle un peu de code, un peu de visualisation et un peu de compétences comme la présentation orale ou l’organisation.

Une bonne équipe ça fait beaucoup, merci à vous!

ScoreIT

Jumbotron

Vers le projet: https://scoreit-thp.herokuapp.com/

Le projet:

3 mois de bootcamp The Hacking Project, 10 heures de code par jour… bienvenue à THP. Le projet final, ce dernier projet pour valider notre formation doit se faire en équipe et mêler le framework Ruby on Rails, HTML, CSS, Bootstrap pour la rapidité, un peu de Javascript.

ScoreIT: Certifiez la solidité de votre startup en vous faisant noter.

https://scoreit-thp.herokuapp.com/

Les startup se font noter sur ScoreIT dans le but d’obtenir plus facilement des financements de potentiels investisseurs. Un score global est donné en remplissant un questionnaire par catégorie (Finance, Stratégie, …). Après notation, il est possible de récupérer un certificat (format pdf) résumant score globale et scores par catégorie.

Notation finale (le dashboard fait de Js), peut être convertie en pdf.

Un espace administrateur est prévu, contenant toutes données concernant les utilisateurs.

Une vue du dashboard admin (ScoreIT)

Informations

  • Temps: 2 semaines
  • Équipe: 5 personnes
  • : https://github.com/guillaume-rygn/ScoreIT
  • Frameworks: Ruby on Rails
  • Base de donnée: PostGreSQL PostGreSQL

Caractéristiques du site

La spécificité:

Système de notation / calculs maisons, temps pour chaque notation (combiné avec Stripe pour ne pas attendre de se faire re-noter).

Liste des languages utilisés:

  • html HTML
  • CSS CSS
  • Javascript Javascript
  • Ruby Ruby

Base de donnée:

  • PostGreSQL PosteGreSQL

Framework:

  • Ruby on Rails

Librairies:

  • Bootstra Bootstrap: Adaptation mobile / grand écran
  • Ruby gem: Devise Devise: Authentification / session administrateur
  • Omniauth: Se connecter avec Github ou LinkedIn
  • wicked_pdf: Transformer du html en pdf

API:

  • SendGrid SendGrid: Mailer
  • Stripe Stripe: Paiement

Outils de travail en équipe:

  • Trello Trello: Organisation des tâches (lien)
  • Git Git: Gestion des versions
  • GitHub: Partage de code
  • Discord Discord: Discussions vocales